Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Javascript 函数表达式

Yuheng Zhang
November 08, 2013
390

Javascript 函数表达式

Yuheng Zhang

November 08, 2013
Tweet

Transcript

  1. 基本介绍 Javascript 是一门函数式面向对象编程语言 Javascript 中函数是一等公民 ( ) First-class function Javascript

    中函数本身就是对象 Javascript 是第一个成为主流的 Lambda 语言 函数表达式是 Javascript 中既强大又容易令人困惑的特性
  2. 函数定义的两种方式 一、函数声明 f u n c t i o n

    f u n c t i o n N a m e ( a g r 0 , a r g 1 , a r g 2 ) { / / . . . } 二、函数表达式 v a r f u n c t i o n N a m e = f u n c t i o n ( a r g 0 , a r g 1 , a r g 2 ) { / / . . . } 这种形式是创建了一个 (有时也叫 )并将它赋值给变量 functionName 匿名函数 Lambda
  3. 两种方式的区别 函数声明不同于函数表达式的特征:前者的函数声明提升 意思是在执行代码前会先读取函数声明,可以把函数声明放在调用它的语句后面 可以这样写 s a y H i (

    ) ; f u n c t i o n s a y H i ( ) { c o n s o l e . l o g ( " O K " ) ; } 不能这样写 s a y H i ( ) ; / / e r r o r 函数还不存在 v a r s a y H i = f u n c t i o n ( ) { c o n s o l e . l o g ( " O K " ) ; }
  4. 递归 递归阶乘函数,通过名字调用自身 f u n c t i o n

    f a c t o r i a l ( n u m ) { i f ( n u m < = 1 ) { r e t u r n 1 ; } e l s e { r e t u r n n u m * f a c t o r i a l ( n u m - 1 ) ; } } 表面上看没什么问题,但是下面的代码会出错 v a r a n o t h e r F a c t o r i a l = f a c t o r i a l ; f a c t o r i a l = n u l l ; c o n s o l e . l o g ( a n o t h e r F a c t o r i a l ( 4 ) ) ; 以上代码把函数保存在变量 anotherFactorial 中,然后将 factorial 变量设置为 null,结果指向原始函数 的引用只剩下一个。而接下来调用 anotherFactorial() 时,由于必须执行 factorial(),所以就会报错
  5. 好的做法 使用 a r g u m e n t

    s . c a l l e e (一个指向正在执行的函数的指针) f u n c t i o n f a c t o r i a l ( n u m ) { i f ( n u m < = 1 ) { r e t u r n 1 ; } e l s e { r e t u r n n u m * a r g u m e n t s . c a l l e e ( n u m - 1 ) ; } } 但是在严格模式下( ),不能直接访问 arguments.callee ES5 strict mode 更好的做法,在严格和非严格模式都行得通 创建名为 f() 的命名函数表达式,即使把重新赋值 factorial,anotherFactorial 中函数名字 f 仍然有效 v a r f a c t o r i a l = ( f u n c t i o n f ( n u m ) { i f ( n u m < = 1 ) { r e t u r n 1 ; } e l s e { r e t u r n n u m * f ( n u m - 1 ) ; } } ) ;
  6. 闭包 闭包是指有权访问另一个函数作用域中的变量的函数 在一个函数内部创建另一个函数就创建了闭包 f u n c t i o

    n c r e a t e C o u n t F u n c t i o n ( n u m ) { r e t u r n f u n c t i o n ( ) { n u m + + ; c o n s o l e . l o g ( n u m ) ; } ; } / / 创建函数 v a r c o u n t _ a = c r e a t e C o u n t F u n c t i o n ( 0 ) ; v a r c o u n t _ b = c r e a t e C o u n t F u n c t i o n ( 1 0 ) ; / / 调用函数 f o r ( v a r i = 0 ; i < 3 ; i + + ) { c o u n t _ a ( ) ; c o u n t _ b ( ) ; }
  7. 原理 f u n c t i o n c

    r e a t e C o u n t F u n c t i o n ( n u m ) { r e t u r n f u n c t i o n ( ) { n u m + + ; / / 关键在这里,n u m 不是在内部定义的 / / 因此会将 c r e a t e C o u n t F u n c t i o n ( ) 函数的活动对象添加到它的作用域链中 / / 这样匿名函数就可以访问在 c r e a t e C o u n t F u n c t i o n ( ) 中定义的所有变量 c o n s o l e . l o g ( n u m ) ; } ; } v a r c o u n t _ a = c r e a t e C o u n t F u n c t i o n ( 0 ) ; v a r c o u n t _ b = c r e a t e C o u n t F u n c t i o n ( 1 0 ) ; f o r ( v a r i = 0 ; i < 3 ; i + + ) { c o u n t _ a ( ) ; / / 调用三次,输出保持了对同一个变量的引用,值会递增 n u m = > 1 , 2 , 3 c o u n t _ b ( ) ; } 内部匿名函数的作用域链中保持了对外部函数变量 num 的引用,在外部函数退出后,外部函数的活动 对象不会被销毁,仍然留在内存中,直到匿名函数被销毁 / / 解除对匿名函数的引用(以便释放内存) c o u n t _ a = n u l l ; c o u n t _ b = n u l l ;
  8. 使用前先理解 - 避免出错 闭包保存的是外层函数的变量对象,保存的是引用的值 如果使用不当,可能会出现下面的错误 f u n c t

    i o n c r e a t e F u n c t i o n s ( ) { v a r r e s u l t = [ ] ; / / v a r r e s u l t = n e w A r r a y ( ) ; f o r ( v a r i = 0 ; i < 5 ; i + + ) { r e s u l t [ i ] = f u n c t i o n ( ) { c o n s o l e . l o g ( i ) ; } ; } r e t u r n r e s u l t ; } / / 创建函数数组 v a r f u n c t i o n L i s t = c r e a t e F u n c t i o n s ( ) ; / / 检查函数输出 f o r ( v a r i = 0 ; i < f u n c t i o n L i s t . l e n g t h ; i + + ) { f u n c t i o n L i s t [ i ] ( ) ; } 表面上看,返回的每个函数都应该返回自己的索引值,即索引值 0 的函数输出 0,以此类推 实际上每个函数都输出 5,每个函数的作用域链中都保存着 createFunctions() 函数的活动对象,它们引 用的都是同一个变量 i 当 createFunctions() 返回后,变量 i 的值是 5
  9. 理解之后使用 - 让结果符合预期 解决方案:创建另一个匿名函数 f u n c t i

    o n c r e a t e F u n c t i o n s ( ) { v a r r e s u l t = [ ] ; / / v a r r e s u l t = n e w A r r a y ( ) ; f o r ( v a r i = 0 ; i < 5 ; i + + ) { r e s u l t [ i ] = f u n c t i o n ( n u m ) { r e t u r n f u n c t i o n ( ) { c o n s o l e . l o g ( n u m ) ; } ; } ( i ) ; } r e t u r n r e s u l t ; } v a r f u n c t i o n L i s t = c r e a t e F u n c t i o n s ( ) ; f o r ( v a r i = 0 ; i < f u n c t i o n L i s t . l e n g t h ; i + + ) { f u n c t i o n L i s t [ i ] ( ) ; } 在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数 的结果赋值给数组。 匿名函数有一个参数 num,每次调用时传入了变量 i,由于参数是按照值传递的,所以 i 的当前值就复制 给了 num。 而在这个函数内部,又创建了一个访问 num 的闭包,这样一来,result 数组中的每个函数都有自己 num 变量的一个副本,可以输出不同的值
  10. THIS 对象和闭包 v a r n a m e =

    " g l o b a l " ; v a r o b j = { n a m e : " l o c a l " , g e t N a m e F u n c : f u n c t i o n ( ) { r e t u r n f u n c t i o n ( ) { r e t u r n t h i s . n a m e ; } } } c o n s o l e . l o g ( o b j . g e t N a m e F u n c ( ) ( ) ) ; / / " g l o b a l " this 对象是在运行时基于函数的执行环境绑定的:在全局函数中,this 等于 window 而当函数被作为某个对象的方法调用时,this 等于那个对象 匿名函数的执行环境具有全局性,因此其 this 通常指向 window 在通过 call() 或者 apply() 改变函数执行环境的情况下,this 就指向其他对象
  11. 保存 THIS 对象,让闭包能访问 v a r n a m e

    = " g l o b a l " ; v a r o b j = { n a m e : " l o c a l " , g e t N a m e F u n c : f u n c t i o n ( ) { v a r t h a t = t h i s ; r e t u r n f u n c t i o n ( ) { r e t u r n t h a t . n a m e ; } } } c o n s o l e . l o g ( o b j . g e t N a m e F u n c ( ) ( ) ) ; / / " l o c a l " 在函数执行时,会自动获取两个特殊变量:this 和 arguments。 函数按照作用域链的顺序,首先在其活动对象搜索到了这两个变量,就停止往”上”搜索外部函数 先把 this 赋值给 that,闭包可以访问 that,that 也引用着 obj
  12. 私有作用域 Javascript 作用域不是块级作用域,而是函数作用域 下面可以实现私有作用域 v a r s o m

    e F u n c = f u n c t i o n ( ) { } ; s o m e F u n c < = = = = = > f u n c t i o n ( ) { } s o m e F u n c ( ) < = = = = = > f u n c t i o n ( ) { } ( ) / / 必须加上括号表示是一个函数表达式,而不是以关键字 f u n c t i o n 开头的函数声明 s o m e F u n c ( ) < = = = = = > ( f u n c t i o n ( ) { } ) ( ) ; 实例 ( f u n c t i o n ( ) { v a r n o w = n e w D a t e ( ) ; i f ( n o w . g e t M o n t h ( ) = = 0 & & n o w . g e t D a t e ( ) = = 1 ) { c o n s o l e . l o g ( " H a p p y n e w y e a r ! " ) ; } } ) ( ) ;
  13. 私有变量和特权方法 在函数内部创建一个闭包,通过闭包的作用域链就能访问这些变量 能够访问私有变量和私有函数的公共方法称为特权方法 f u n c t i o

    n M y O b j e c t ( ) { / / 私有变量和私有函数 v a r p r i v a t e V a r i a b l e = 1 0 ; f u n c t i o n p r i v a t e F u n c t i o n ( ) { r e t u r n f a l s e ; } / / 特权方法 t h i s . p u b l i c M e t h o d = f u n c t i o n ( ) { p r i v a t e V a r i a b l e + + ; r e t u r n p r i v a t e F u n c t i o n ( ) ; } }
  14. 特权方法 - 实例 利用私有和特有成员,可以隐藏不应该被直接修改的 name,唯一途径就是用 getName() 和 setName() 私有变量 name

    在 Car 的每个实例中都不相同 f u n c t i o n C a r ( n a m e ) { / / 特权方法,外部只能通过它访问私有变量 n a m e t h i s . g e t N a m e = f u n c t i o n ( ) { r e t u r n n a m e ; } t h i s . s e t N a m e = f u n c t i o n ( v a l u e ) { n a m e = v a l u e ; } } v a r m y C a r = n e w C a r ( " B u m b l e b e e " ) ; m y C a r . g e t N a m e ( ) ; / / " B u m b l e b e e " m y C a r . s e t N a m e ( " I r o n h i d e " ) ; m y C a r . g e t N a m e ( ) ; / / " I r o n h i d e "
  15. 静态私有变量 为了避免针对每个实例都创建同样一组新方法,可以使用静态私有变量来实现特权方法 ( f u n c t i o

    n ( ) { / / 私有变量和私有函数 v a r p r i v a t e V a r i a b l e = 1 0 ; f u n c t i o n p r i v a t e F u n c t i o n ( ) { r e t u r n f a l s e ; } / / 构造函数 M y O b j e c t = f u n c t i o n ( ) { } ; / / 公有/ 特权方法 M y O b j e c t . p r o t o t y p e . p u b l i c M e t h o d = f u n c t i o n ( ) { p r i v a t e V a r i a b l e + + ; r e t u r n p r i v a t e F u n c t i o n ( ) ; } } ) ( ) ; 公有方法是在原型(prototype)基础上定义的,体现了原型模式 MyObject 是全局变量,能在私有作用域之外被访问到 这个模式与在构造函数中定义特权方法的区别是私有变量和函数是由实例共享的。
  16. 静态私有变量 - 实例 ( f u n c t i

    o n ( ) { v a r n a m e = " " ; C a r = f u n c t i o n ( v a l u e ) { n a m e = v a l u e ; } ; C a r . p r o t o t y p e . g e t N a m e = f u n c t i o n ( ) { r e t u r n n a m e ; } ; C a r . p r o t o t y p e . s e t N a m e = f u n c t i o n ( v a l u e ) { n a m e = v a l u e ; } ; } ) ( ) ; v a r m y C a r = n e w C a r ( " B u m b l e b e e " ) ; m y C a r . g e t N a m e ( ) ; / / " B u m b l e b e e " m y C a r . s e t N a m e ( " I r o n h i d e " ) ; m y C a r . g e t N a m e ( ) ; / / " I r o n h i d e " v a r T o y = n e w C a r ( " O p t i m u s " ) ; t o y . g e t N a m e ( ) ; / / " O p t i m u s " m y C a r . g e t N a m e ( ) ; / / " O p t i m u s " toy 和 myCar 共享变量 name
  17. 模块模式 为单例(singleton)创建私有变量和特权方法 v a r c o p = f

    u n c t i o n ( ) { / / 私有变量和函数 v a r a p p l i c a t i o n s = [ ] ; / / v a r a p p l i c a t i o n s = n e w A r r a y ; / / 初始化 a p p l i c a t i o n s . p u s h ( " b a s e " ) ; / / 公共 r e t u r n { g e t A p p l i c a t i o n s : f u n c t i o n ( ) { r e t u r n a p p l i c a t i o n s ; } , r e g i s t e r A p p l i c a t i o n : f u n c t i o n ( a p p ) { a p p l i c a t i o n s . p u s h ( a p p ) ; } } ; } ( ) ; c o p . g e t a p p l i c a t i o n s ( ) ; / / [ " b a s e " ] c o p . r e g i s t e r A p p l i c a t i o n ( " F C " ) ; c o p . g e t a p p l i c a t i o n s ( ) ; / / [ " b a s e " , " F C " ] 在 Web 应用中,经常需要使用一个单例来管理应用程序级的信息 例子中创建了一个用于管理应用的 cop 对象 applications 数组是私有变量,同时公开了 getApplications() 和 registerApplication() 两个方法用于查看 和注册新组件
  18. 增强的模块模式 适用于单例必须是某种类型的实例 f u n c t i o n

    P l a t f o r m ( ) { P l a t f o r m . p r o t o t y p e . c o m p a n y = " C h i n a C a c h e " ; } v a r c o p = f u n c t i o n ( ) { / / 私有变量和函数 v a r a p p l i c a t i o n s = [ ] ; / / v a r a p p l i c a t i o n s = n e w A r r a y ; / / 初始化 a p p l i c a t i o n s . p u s h ( " b a s e " ) ; / / 创建 c o p 的一个局部副本 v a r _ c o p = n e w P l a t f o r m ( ) ; / / 公共 _ c o p . g e t A p p l i c a t i o n s = f u n c t i o n ( ) { r e t u r n a p p l i c a t i o n s ; } ; _ c o p . r e g i s t e r A p p l i c a t i o n = f u n c t i o n ( a p p ) { a p p l i c a t i o n s . p u s h ( a p p ) ; } ; / / 返回这个副本 r e t u r n _ c o p ; } ( ) ; c o p . c o m p a n y ( ) ; / / " C h i n a C a c h e " c o p . g e t a p p l i c a t i o n s ( ) ; / / [ " b a s e " ] 在 Web 应用中,经常需要使用一个单例来管理应用程序级的信息 例子中创建了一个用于管理应用的 cop 对象 applications 数组是私有变量,同时公开了 getApplications() 和 registerApplication() 两个方法用于查看 和注册新组件