创建对象
工厂模式
示例:
function createPerson(name, age, job) {var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {console.log(this.name);
};
return o;
}
var person1 = createPerson('张三', 20, 'actor');
console.log(person1);
person1.sayName();
var person2 = createPerson('李四', 21, 'teacher');
person2.sayName();
return 语句:
- 当 createPerson() 函数中没有“return”语句时,person1 指向
createPerson {}
, 调用时会报错Uncaught TypeError: person1.sayName is not a function
作用:解决了创立多个类似对象的问题
毛病:无奈确定对象的类型
构建函数模式
示例:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {console.log(this.name);
};
}
var person1 = new Person('张三', 20, 'actor');
person1.sayName();
与工厂函数的不同:
- 没有显式地创建对象
- 间接将属性和办法赋给了
this
对象- 没有 return 语句
new 操作符,调用构造函数的 4 个步骤:
- 创立一个新对象;
- 将构造函数的作用域赋给新对象(因而 this 就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象增加属性)
- 返回新对象
function New(f) {return function () {
var o = {__proto__: f.prototype,};
f.apply(o, arguments);
return o;
};
}
var person1 = New(Person)('张三', 20, 'actor');
console.log('per', person1);
person1.sayName();
将构造函数当作函数
var person1 = new Person('张三', 20, 'actor');
person1.sayName();
// 作为一般函数调用:Person('李四', 21, 'teacher'); // 增加到 window
window.sayName();
// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, '小白', 21, 'doctor');
o.sayName();
应用构造函数的次要问题
- 每个办法都要在每个实例上从新创立一遍,而如果把
sayName
函数转移到构造函数内部,在构造函数外部,将sayName
属性设置成等于全局的sayName
函数。这样能解决两个函数做同一件事件的问题,然而新问题是:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点徒有虚名。 - 而更让人无奈承受的是:如果对象须要定义很多办法,那么就要定义很多个全局函数,于是咱们这个自定义的援用类型就丝毫没有封装性可言了。
- 这些问题可通过应用
原型模式
来解决。
原型模式
操作符与办法
- instanceof: 确定原型和实例的关系
person1 instanceof Person
- isPrototypeOf(): 确定原型和实例的关系
Person.prototype.isPrototypeOf(person1)
- Object.getPrototypeOf(): 返回
[[Prototype]]
的值Object.getPrototypeOf(person1) == Person.prototype
- hasOwnProperty(): 检测一个属性是在于实例中,还是存在于原型中。
-
in
in
操作符会在通过对象可能拜访给定属性时返回 true, 无论该属性存在于实例中还是原型中- 同时应用 hasOwnProperty()办法和
in
操作符,能够确定该属性到底是存在于对象中,还是存在于原型中
function hasPrototypeProperty(object, name){return !object.hasOwnProperty(name) && (name in object); }
- 在应用 for-in 循环时,返回的是所有可能通过对象拜访的、可枚举的属性(实例属性 + 原型属性)
- Object.keys(): 取得对象上所有可枚举的实例属性
-
Object.getOwnPropertyNames(): 取得所有实例属性,无论是否可枚举。
原型应用的注意事项
function Person(){}
Person.prototype = {
name: 'Ha',
age: 29,
sayName: function(){}
}
/*
这种形式重写了默认的 prototype 对象,constructor 属性不再指向 Person
*/
// 重设构造函数
Object.defineProperty(Person.prototype, "constructor", {
eumerable:false,
value: Person
})
组合模式
- 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,后果,每个实例都会有本人的一份实例属性的正本,但同时又共享着对办法的援用,最大限度地节俭了内存。另外,这种模式还反对向构造函数传递参数。
- 示例
function Person(name) {
this.name = name;
this.friends = ["Shelby", "Court"]
}
Person.prototype = {
constructor: Person,
sayName: function() {console.log(this.name)
}
}
var person1 = new Person('Ha');
var person2 = new Person('Xi');
person1.friends.push("Van")
console.log(person1.friends) //"Shelby,Court,Van"
console.log(person2.friends) //"Shelby,Court"
console.log(person1.friends === person2.friends) //false
console.log(person1.sayName === person2.sayName) // true
动静原型模式
- 动静原型模式,把所有信息都关闭在了构造函数中,而通过在构造函数中初始化原型(仅在必要的状况下),又放弃了同时应用构造函数和原型的长处。换句话说,能够通过查看某个应该存在的办法是否无效,来决定是否须要初始化原型。
- 示例
function Person(name) {
this.name = name;
if (typeof this.sayName != "function") {Person.prototype.sayName = function() {console.log(this.name)
}
}
}
var person1 = new Person("Ha")
person1.sayName() // Ha
寄生构造函数模式
- 这种模式的根本思维是创立一个函数,该函数的作用仅仅是封装创建对象的代码,而后再返回新创建的对象
- 示例
function Person(name) {var o = new Object()
o.name = name;
o.sayName = function() {console.log(this.name)
}
return o
}
var person1 = new Person('Ha')
person1.sayName() // 'Ha'
-
特点:
- 除了应用 new 操作符并把应用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是截然不同的。构造函数在不返回值的状况下,默认会返回新对象实例,而通过在构造函数的开端增加一个 return 语句,能够重写调用构造函数时返回的值。
- 这个模式能够在非凡的状况下用来为对象创立构造函数。如创立一个具备额定办法的非凡数组,因为不能间接批改 Array 构造函数,因而能够应用这个模式
- 对于寄生构造函数模式,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;不能依赖 instanceof 操作符来确定对象类型
稳当构造函数模式
- 稳当对象最适宜在一些平安的环境中(这些环境中会禁止应用 this 和 new),或者在避免数据被其余应用程序。稳当构造函数遵循与寄生构造函数相似的模式,但有两点不同: 一是新创建对象的实例不援用 this,二是不应用 new 操作符调用构造函数
- 示例
function Person(name) {var o = new Object()
o.name = name;
o.sayName = function() {console.log(name)
}
return o
}
var person1 = Person('Ha')
person1.sayName() // 'Ha'
继承
- 许多 OO 语言都反对两种继承形式: 接口继承和实现继承。接口继承只继承办法签名,而实现继承则继承理论的办法。
-
ECMAScript 中只反对实现继承,而且其实现继承次要是依附原型链来实现的。其根本思维是利用原型让一个援用类型继承另一个援用类型的属性和办法。
原型链
- 原型链的构建是通过将一个类型的实例赋给另一个构造函数的原型实现的。
- 示例
function SuperType(){this.colors = ["red","blue","green"]
}
function SubType(){}
SubType.prototype = new SuperType()
var instance1 = new SubType()
instance1.colors.push("black");
console.log(instance1.colors) // "red,blue,green,black"
var instance2 = new SubType()
console.log(instance2.colors) // "red,blue,green,black"
-
原型链的问题:
最次要的问题来自蕴含援用类型值的原型,在通过原型来实现继承时,原型实际上会变成另一个类型的实例,于是,原先的实例属性也就牵强附会地变成了当初的原型属性。借用构造函数 (也叫伪造对象或者经典继承)
- 解决原型中蕴含援用类型值所带来问题, 借用构造函数的根本思维: 在子类型构造函数的外部调用超类型构造函数。函数只是在特定环境中执行代码的对象,因为通过应用 apply()和 call()办法也能够在 (未来) 新创建的对象上执行构造函数
- 示例
function SuperType(){this.colors = ["red","blue","green"]
}
function SubType(){SuperType.call(this)
}
var instance1 = new SubType()
instance1.colors.push("black")
console.log(instance1.colors) // "red,blue,green,black"
var instance2 = new SubType()
console.log(instance2.colors)
-
特点
- 传递参数:
绝对于原型链而言,借用构造函数有一个很大的劣势,即能够在子类型构造函数中向超类型构造函数传递参数。 - 借用构造函数的问题
如果仅仅是借用构造函数,那么也将无奈防止构造函数模式存在的问题——办法都在构造函数中定义,因为函数复用就无从谈起了,而且在超类型的原型中定义的办法,对子类型而言也是不可见的,后果所有类型都只能应用构造函数模式。思考到这些问题,借用构造函数的技术也是很少独自应用的。
- 传递参数:
组合继承 (也叫伪经典继承)
- 指的是将原型链和借用构造函数的技术组合到一块,从而施展二者之长的一种继承模式,其背地的思路是应用原型链实现对原型属性和办法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又可能保障每个实例都有它本人的属性
- 示例
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function() {console.log(this.name)
}
function SubType(name, age) {SuperType.call(this, name) // 第二次调用 SuperType()
this.age = age
}
SubType.prototype = new SuperType() // 第一次调用 SuperType()
SubType.prototype.sayAge = function() {console.log(this.age)
}
var instance1 = new SubType("Ha", 29)
instance1.colors.push("black")
console.log(instance1.colors) //"red,blue,green,black"
instance1.sayName() //"Ha"
instance1.sayAge() //29
var instance2 = new SubType("Xi", 27)
console.log(instance2.colors) //"red,blue,green"
console.log(instance2.sayName) //"Xi"
console.log(instance2.sayAge)
-
毛病
- 组合继承最大的问题就是无论什么状况下,都会调用两次超类型构造函数: 一次是在创立子类型原型的时候; 另一次是在子类型构造函数外部。
- 子类型最终会蕴含超类型对象的全副实例属性,但咱们不得不在调用子类型构造函数时重写这些属性
原型式继承
- ECMAScript 5 通过 Object.create()办法规范化了原型式继承。这个办法接管两个参数: 一个用作新对象原型的对象和 (可选的) 一个为新对象定义额定属性的对象。在传入一个参数的状况下,Object.create()与 object()办法的行为雷同
- 函数外部,先创立了一个临时性的构造函数,而后将传入的对象作为 这个构造函数的原型,最初返回了这个长期类型的一个新实例。从实质上讲,object()对传入其中的对象进行了一次浅复制
- 示例
function object(o){function F(){}
F.prototype = o;
return new F();}
寄生式继承
- 寄生式继承的思路与寄生构造函数和工厂模式相似,即创立一个仅用于封装继承过程的函数,该函数在外部以某种形式来加强对象,最初返回对象
- 示例
function createAnother(original){var clone = object(original);
clone.sayHi = function(){console.log('hi');
};
return clone;
}
寄生组合式继承
- 解决组合继承两次调用超类型构造函数的问题。
- 寄生组合式继承,通过借用构造函数来继承属性,通过原型链接的混成模式来继承办法。其背地的基本思路是: 不用为了指定子类型的原型而调用超类型的构造函数,咱们所须要的无非就是超类型原型的一个正本而已。实质上,就是应用寄生式继承来继承超类型的原型,而后再将后果指定给子类型的原型。
- 示例
function inheritPrototype(subType, superType){var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
/*
在函数外部,第一步是创立超类型原型的一个正本,第二步是为创立的正本增加 constructor 属性,从而补救因重写原型而失去的默认的 constructor 属性。最初一步,将新创建的对象 (即正本) 赋值给子类型的原型。*/
function SuperType(name){
this.name = name;
thsis.colors = ["red","blue","green"]
}
SuperType.prototype.sayName = function(){console.log(this.name)
}
function SubType(name, age){SuperType.call(this,name)
this.age = age
}
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function(){console.log(this.age)
}