js高级程序设计:面向对象、函数表达式
面向对象
理解对象
- 对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数”,相当于说对象是一组没有特定顺序的值,可以对象想象成散列表
- 每个对象都是基于一个引用类型创建的,这个引用类型可以是第 5 章讨论的原生类型,也可以是开发人员定义的类型。
后面的内容在首页不显示,请点击下方的展开全文或者
属性类型:数据属性和访问器属性
- 数据属性:包含一个数据值的位置。在这个位置可以读取和写入值,
- 属性有4个特性
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,直接在对象上定义的属性的这个特性默认值为 true。把 configurable 设置为 false,表示不能从对象中删除属性。一旦把属性定义为不可配置的,就不能再把它变回可配置
- [[Enumerable]]:表示能否通过 for-in 循环返回属性,直接在对象上定义的属性的这个特性默认值为 true。
- [[Writable]]:表示能否修改属性的值,直接在对象上定义的属性的这个特性默认值为 true。
- [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值为 undefined
- Object.defineProperty()方法可以修改属性默认的特性,接收三个参数:属性所在的对象、属性的名字和一个描述符对象。描述符对象的属性必须是:configurable、enumerable、writable 和 value。在调用 Object.defineProperty()方法时,如果不指定,configurable、enumerable 和writable 特性的默认值都是 false。注意这个方法ie8+才能实现
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
- 访问器属性
- 不包含数据值;包含一对儿 getter 和 setter 函数
- 4个特性
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为true。
- [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为 true。
- [[Get]]:在读取属性时调用的函数。默认值为 undefined。
- [[Set]]:在写入属性时调用的函数。默认值为 undefined。
- 访问器属性不能直接定义,必须使用 Object.defineProperty()来定义,注意这个方法ie9+才能实现
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); //2
- 使用访问器属性的常见方式,即设置一个属性的值会导致其他属性发生变化,如上例
定义多个属性
- Object.defineProperties()方法可以通过描述符一次定义多个属性。接收两个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应.ie9+支持该方法
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
读取属性的特性
- Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象,如果是访问器属性,这个对象的属性有 configurable、enumerable、get 和 set;如果是数据属性,这个对象的属性有 configurable、enumerable、writable 和 value。
创建对象
工厂模式
- 封装以特定接口创建对象
- 在函数内new一个对象,设置该对象的属性,返回该对象
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
- 存在问题:没有解决对象识别的问题(即怎样知道一个对象的类型)
构造函数模式
- 创建自定义的构造函数,从而定义自定义对象类型的属性和方法
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
- 与工厂模式区别
- 没有显式地创建对象
- 直接将属性和方法赋给了 this 对象
- 没有 return 语句
- 构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头
- 创建 Person 的新实例,必须使用 new 操作符
- 可以用instanceof检测对象类型
- 使用 new 操作符调用构造函数经历的步骤
- 创建一个新对象;
- 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象
- 创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型
- 任何函数,通过 new 操作符来调用,那它就可以作为构造函数,如果不通过 new 操作符来调用,就是普通函数
- 不使用new操作符调用构造函数,:属性和方法都被添加给window对象,因为在全局作用域中调用一个函数时,this 对象总是指向 Global 对象
- 构造函数模式的缺点:每个方法都要在每个实例上重新创建一遍,针对这个问题可以将构造函数内的方法转移到构造函数外部以实现全局共享,但是这样带来一个新问题:如果对象需要定义很多方法,那么就要定义很多个全局函数,自定义的引用类型就丝毫没有封装性了
原型模式
- 每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向函数的原型对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
- prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中
/*构造函数变成了空函数。也可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属性和方法*/
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
- 理解原型对象
- 创建一个新函数,就会为该函数创建一个 prototype属性,这个属性指向函数的原型对象.在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针.例如Person.prototype. constructor 指向 Person
- 连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2C6WOgLL-1569921253271)(JavaScript高级程序设计_files/1.jpg)]
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
/*
Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person。
原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性。Person 的每个实例——
person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype;换句话说,它们
与构造函数没有直接的关系
*/
- 在所有实现中都无法访问到实例中的[[Prototype]],但可以通过 isPrototypeOf()方法来确定对象之间是否存在这种关系
alert(Person.prototype.isPrototypeOf(person1)); //true
- ES 5 增加 Object.getPrototypeOf()方法,在所有支持的实现中,这个方法返回[[Prototype]]的值,即地取得一个对象的原型
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
- 对象的属性搜索流程:先搜索对象实例,再搜索指针指向的原型对象
- 当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。注意,使用 delete 操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性
- hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中,它只在给定属性存在于对象实例中时才会返回 true,存在原型中返回false
- 原型与in操作符
- 两种方式使用 in 操作符
- 单独使用时,in 操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中
- 使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。
alert("name" in person1); //true
- 默认不可枚举的所有属性和方法,包括:hasOwnProperty()、propertyIsEnumerable()、toLocaleString()、toString()和 valueOf()。
- ES5 的 Object.keys()方法可以取得对象上所有可枚举的实例属性,。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组
- 使用 Object.getOwnPropertyNames()方法可以得到所有实例属性,无论它是否可枚举
- 更简单的原型语法
- 用一个包含所有属性和方法的对象字面量来重写整个原型对象
/* 注意,下例中constructor 属性不再指向 Person,因为下例中的prototype对象是默认的object对象的,用这个方法创建的对象无法通过constructor确定对象的类型,/
function Person(){
}
Person.prototype = {
/* constructor : Person, 如果需要constructor 属性指向 Person也可以自己设置,不设置通过constructor确定对象的类型,注意默认constructor属性是不可枚举的,这样设置后的[[Enumerable]]特性被设置为 true,需要将[[Enumerable]]特性被设置为 false,则通过defineProperty()**/
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
- 原型的动态性
用字面量重写原型后,重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;实例引用的仍然是最初的原型。如果把实例放到重写原型后再创建,则联系的是重写后的原型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HRvjFIXf-1569921253274)(JavaScript高级程序设计_files/实例与原型关系.jpg)] - 原生对象的原型
js原生的引用类型都是采用原型模式创建的,通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法.注意!不推荐修改原生对象的原型
String.prototype.startsWith = function (text) {
return this.indexOf(text) == 0;
};
var msg = "Hello world!";
alert(msg.startsWith("Hello")); //true
- 原型对象的问题
- 省略了为构造函数传递初始化参数这一环节,导致所有实例在默认情况下都将取得相同的属性值
- 原型中所有属性是被很多实例共享的,但是,实例一般都是要有属于自己的全部属性的
组合使用构造函数模式和原型模式,定义引用类型的一种默认模式
- 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性.
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
动态原型模式
动态原型模式把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
function Person(name, age, job){
//属性
this.name = name;
this.age = age;
this.job = job;
//方法
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();
- 使用动态原型模式时,不能使用对象字面量重写原型,因为在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系
寄生构造函数模式
- 基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
- 除了使用 new 操作符来创建实例并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
- 返回的对象与构造函数或者与构造函数的原型属性之间没有关系,所以无法用instanceof 操作符来确定对象类型
- 不推荐使用这种方式
稳妥构造函数模式
- 稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象,适合在一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其他应用程序(如 Mashup程序)改动时使用
- 稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用 this;二是不使用 new 操作符调用构造函数
function Person(name, age, job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
};
//返回对象
return o;
}
var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
- 返回的对象与构造函数或者与构造函数的原型属性之间没有关系,所以无法用instanceof 操作符来确定对象类型
继承
js只支持依靠原型链实现继承
原型链
- 基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法
- 具体做法是构造函数1的原型对象A将包含一个指向另一个原型B的指针,相应地,原型B中也包含着一个指向另一个构造函数2的指针
- 示例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaG5PgdF-1569921253276)(JavaScript高级程序设计_files/原型链.jpg)]
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了 SuperType ,通过创建 SuperType 的实例,并将该实例赋给SubType.prototype 实现的
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
- 默认的原型,所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype
- 判断原型与实例的关系,
- 使用 instanceof 操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回 true
alert(instance instanceof Object); //true
- 使用 isPrototypeOf()方法。只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,该方法也会返回 true
alert(Object.prototype.isPrototypeOf(instance)); //true
- 谨慎的定义方法,给原型添加方法的代码一定要放在替换原型(实现继承)的语句之后,此外,在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链
- 原型链的问题:
- 包含引用类型值的原型属性会被所有实例共享
- 在创建子类型的实例时,不能向超类型的构造函数中传递参数
借用构造函数(做伪造对象或经典继承)
基本思想是在子类型构造函数的内部调用超类型构造函数
通过使用 apply()和 call()方法在(将来)新创建的对象上执行构造函数
- 传递参数
function SuperType(){
this.name = name;
}
function SubType(){
//继承了 SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
//实例属性
this.age = 29; //在调用超类型构造函数后,再添加应该在子类型中定义的属性
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
- 借用构造函数的问题:无法避免构造函数模式存在的问题——方法都在构造函数中定义,复用性不强
组合继承(伪经典继承),组合原型链和借用构造函数,最常用
思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性
- 示例
//让两个不同的 SubType 实例既分别拥有自己属性——包括 colors 属性,又可以使用相同的方法
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//继承属性,借用构造函数
SuperType.call(this, name); // 第二次调用 SuperType()
this.age = age;
}
//继承方法。原型链
SubType.prototype = new SuperType(); //第一次调用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
- 问题
无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部
原型式继承:
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
- 原型式继承要求必须有一个对象可以作为另一个对象的基础。如果有这么一个对象的话,可以把它传递给 object()函数,然后再根据具体需求对得到的对象加以修改即可
function object(o){
function F(){} //创建一个临时性的构造函数
F.prototype = o; //将传入的对象作为这个构造函数的原型
return new F(); //返回临时类型的新实例
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
- ES5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
- 在传入一个参数的情况下,Object.create()与 object()方法的行为相同
//var anotherPerson = object(person);
var anotherPerson = Object.create(person);
- Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
- 此方法IE9+以上才实现
- 类似使用原型模式,包含引用类型值的属性始终都会共享相应的值
寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
- 示例
function createAnother(original){
var clone = object(original); /*通过调用函数创建一个新对象,不一定非要用object(),任何能够返回新对象的函数都适用于此模式 */
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
- 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率
寄生组合式继承
- 寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
- 思路:使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
- 示例
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
- 优点:它只调用了一次 SuperType 构造函数,并且因此避免了在 SubType. prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和 isPrototypeOf()
函数表达式
- 定义函数的两种方式
- 函数声明
function functionName(arg0, arg1, arg2) {
//函数体
}
函数声明提升,执行代码之前会先读取函数声明。所以可以把函数声明放在调用它的语句后面。
- 函数表达式
var functionName = function(arg0, arg1, arg2){
//函数体
};
- 这种方式创建的是匿名函数,因为 function 关键字后面没有标识符。
- 函数表达式创建的函数不存在函数提升,调用必须放在后面
递归:函数通过名字调用自身
- 直接用函数名实现递归存在隐患
//一个经典的阶乘函数,arguments.callee 是一个指向正在执行的函数的指针
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * factorial(num-1); //改为return num * arguments.callee(num-1);就不会报错了
}
}
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //出错!
- 在严格模式下,访问这个arguments.callee属性会导致错误,所以可以通过命名函数表达式来实现效果
var factorial = (function f(num){
if (num <= 1){
return 1;
} else {
return num * f(num-1);
}
});//将函数放f()用括号括起来是为了让解析器清楚这是一个表达式
闭包
- 闭包是指有权访问另一个函数作用域中的变量的函数
- 创建闭包的常见方式,就是在一个函数内部创建另一个函数
- 外部函数在执行完毕后,其活动对象也不会被销毁,因为它返回的匿名函数的作用域链仍然在引用这个活动对象。直到匿名函数被销毁后,外部函数的活动对象才会被销毁
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FzLdIUVM-1569921253279)(JavaScript高级程序设计_files/闭包.jpg)]
- 过度使用闭包可能会导致内存占用过多
闭包与变量
- 闭包只能取得包含函数中任何变量的最后一个值
//第一个函数中,在每个函数内部 i 的值都是 10
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
//每个函数就会返回各自不同的索引值
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
this对象
- 在全局函数中,this 等于 window,而当函数被作为某个对象的方法调用时,this 等于那个对象,
- 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window,如果通过 call()或 apply()改变函数执行环境的情况下,this 就会指向其他对象
- 每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)
- 把外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
- 特殊情况下,this的值可能发生改变
var name = "The Window";
var object = {
name : "My Object",
getName: function(){
return this.name;
}
};
object.getName(); //"My Object"
(object.getName)(); //"My Object"
(object.getName = object.getName)(); //"The Window",在非严格模式下
/*最后一行相当于 var temp = object.getName = object.getName; temp(); 这样调用this当然是 window */
内存泄漏
- 如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁
- 必要把引用变量设置为 null,解除对象的引用,顺利地减少其引用数,确保正常回收其占用的内存。
模仿块级作用域
- 匿名函数可以用来模仿块级作用域
/*将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式,如果没有括号就会出错。而紧随其后的另一对圆括号会立即调用这个函数*/
(function(){
//这里是块级作用域
})();
- 注意:JavaScript 将 function 关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要给它加上一对圆括号
- 无论在什么地方,只要临时需要一些变量,就可以使用私有作用域
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){ //这个你们匿名函数是一个闭包,所以可以可以访问外部作用域的count变量
alert(i);
}
})(); //此时已经将循环插入私有作用域内,外部不能访问
alert(i); //导致一个错误!
}
- 用处:在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数
- 优点:可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链
私有变量
- 任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数
- 有权访问私有变量和私有函数的公有方法称为特权方法
- 在构造函数中定义特权方法,缺点是必须使用构造函数模式来达到这个目的
function MyObject(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法,作为闭包有权访问在构造函数中定义的所有变量和函数
this.publicMethod = function (){
privateVariable++;
return privateFunction();
};
}
静态私有变量
- 基本模式:创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法,公有方法是在原型上定义的
- 注意:在定义构造函数时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数
- 没有在声明 MyObject 时使用 var 关键字,使他成为一个全局变量
- 与在构造函数中定义特权方法的主要区别,就在于私有变量和函数是由实例共享的
(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//构造函数
MyObject = function(){
};
//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
模块模式
- 模块模式是为单例创建私有变量和特权方法
- 单例(singleton),指的就是只有一个实例的对象,一般js按对象字面量来创建单例对象
var singleton = {
name : value,
method : function () {
//这里是方法的代码
}
};
- 模块模式语法
- 使用了一个返回对象的匿名函数
- 将一个对象字面量作为函数的值返回,里面只包含可以公开的属性和方法
- 对象字面量定义的是单例的公共接口
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权/公有方法和属性
return {
publicProperty: true,
publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();
- 使用场景:必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,如Web 应用程序中,经常需要使用一个单例来管理应用程序级的信息
增强的模块模式:返回对象之前加入对其增强的代码
- 增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况
- 示例
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//创建对象
var object = new CustomType();
//添加特权/公有属性和方法
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
};
//返回这个对象
return object;
}();