立即调用的函数表达式
1 2 3 4 5 6 7 8 9 10
| (function() { })();
(function () { for (var i = 0; i < count; i++) { console.log(i); } })(); console.log(i);
|
私有变量
- 严格来讲,JavaScript 没有私有成员的概念,所有对象属性都公有的。
- 任何定义在函数或块中的变量,都可以认为是私有的,因为在这个函数或块的外部无法访问其中的变量。
- 私有变量包括函数参数、局部变量,以及函数内部定义的其他函数。
- 特权方法( privileged method )是能够访问函数私有变量(及私有函数)的公有方法。
- 在对象上有两种方式创建特权方法。第一种是在构造函数中实现。
1 2 3 4 5 6 7 8 9 10 11 12
| function MyObject() { let privateVariable = 10; function privateFunction() { return false; } this.publicMethod = function() { privateVariable++; return privateFunction(); }; }
|
静态私有变量
- 特权方法也可以通过使用私有作用域定义私有变量和函数来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| (function() { let privateVariable = 10; function privateFunction() { return false; } MyObject = function() {}; MyObject.prototype.publicMethod = function() { privateVariable++; return privateFunction(); }; })();
|
在这个模式中,匿名函数表达式创建了一个包含构造函数及其方法的私有作用域。首先定义的是有变量和私有函数,然后又定义了构造函数和公有方法。公有方法定义在构造函数的原型上,与典型的原型模式一样。注意,这个模式定义的构造函数没有使用函数声明,使用的是函数表达式。函数声明会:创建内部函数,在这里并不是必需的。基于同样的原因(但操作相反),这里声明Myobject并没有使用任何关键字。因为不使用关键字声明的变量会创建在全局作用域中,所以Myobject变成了全局变量,用任何关键字。因为不使用关键字声明的变量会创建在全局作用域中,所以Myobject变成了全局变量。
- 这个模式与前一个模式的主要区别就是:私有变量和私有函数是由实例共享的。
- 因为特权方法定义在原型上,所以同样是由实例共享的。
- 特权方法作为一个闭包,始终引用着包含它的作用域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| (function() { let name = ''; Person = function(value) { name = value; }; Person.prototype.getName = function() { return name; }; Person.prototype.setName = function(value) { name = value; }; })(); let person1 = new Person('Nicholas'); console.log(person1.getName()); person1.setName('Matt'); console.log(person1.getName()); let person2 = new Person('Michael'); console.log(person1.getName()); console.log(person2.getName());
|
- 注意使用闭包和私有变量会 导致作用城链变长,作用域链越长,则查找变量所需的时间也越多。
模块模式
- 模块模式是在单例对象基础上加以扩展,使其通过作用域链来关联私有变量和特权方法。
- 模块模式的样板代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let singleton = function() { let privateVariable = 10; function privateFunction() { return false; } return { publicProperty: true, publicMethod() { privateVariable++; return privateFunction(); } }; }();
|
;模块模式使用了匿名函数返回一个对象。在匿名函数内部,首先定义私有变量和私有函数。之后,创建一个要通过匿名函数返回的对象字面量。这个对象字面量中只包含可以公开访问的属性和方法。因为这个对象定义在匿名函数内部,所以它的所有公有方法都可以访问同一个作用域的私有变量和私有函数。本质上,对象字面量定义了单例对象的公共接口。
如果单例对象需要进行某种初始化,并且需要访问私有变量时,那就可以采用这个模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let application = function() { let components = new Array(); components.push(new BaseComponent()); return { getComponentCount() { return components.length; }, registerComponent(component) { if (typeof component == 'object') { components.push(component); } } }; }();
|
模块增强模式
- 另一个利用模块模式的做法是在返回对象之前先对其进行增强。
- 这适合单例对象需要是某个特定类型的实例,但又必须给它添加额外属性或方法的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let singleton = function() { let privateVariable = 10; function privateFunction() { return false; } let object = new CustomType(); object.publicProperty = true; object.publicMethod = function() { privateVariable++; return privateFunction(); }; return object; }();
|
总结
- 函数表达式与函数声明是不一样的。函数声明要求写出函数名称,而函数表达式并不需要。没有名称的函数表达式也被称为匿名函数。
- ES6新增了类似于函数表达式的箭头函数语法,但两者也有一 些重要区别。
- JavaScript中函数定义与调用时的参数极其灵活。arguments对象,以及ES6新增的扩展操作符,可以实现函数定义和调用的完全动态化。
- 函数内部也暴露了很多对象和引用,涵盖了函数被谁调用、使用什么调用,以及调用时传入了什么参数等信息。
- JavaScript引擎可以优化符合尾调用条件的函数,以节省栈空间。
- 闭包的作用域链中包含自己的一个变量对象,然后是包含函数的变量对象,直到全局上下文的变量对象。
- 通常,函数作用域及其中的所有变量在函数执行完毕后都会被销毁。
- 闭包在被函数返回之后,其作用域会一直保存在内存中,直到闭包被销毁。
- 函数可以在创建之后立即调用,执行其中代码之后却不留下对函数的引用。
- 立即调用的函数表达式如果不在包含作用域中将返回值赋给一个变量,则其包含的所有变量都会被销毁。
- 虽然JavaScript没有私有对象属性的概念,但可以使用闭包实现公共方法,访问位于包含作用域中定义的变量。
- 可以访问私有变量的公共方法叫作特权方法。
- 特权方法可以使用构造函数或原型模式通过自定义类型中实现,也可以使用模块模式或模块增强模式在单例对象上实现。