共计 5130 个字符,预计需要花费 13 分钟才能阅读完成。
继承
简略地了解,继承就是一个对象能够拜访另外一个对象中的属性和办法 。在 JavaScript 中,咱们通过 原型和原型链的形式 来实现了继承个性。
察看上图,因为 B 继承了 A,那么 B 能够间接应用 A 中的 color 属性,就像这个属性是 B 自带的一样。
\_proto\_
JavaScript 中任意对象都有一个内置属性 [[Prototype]],然而 ES5 之前没有拜访这个 [[Prototype]] 属性的规范形式,所以大多数浏览器会在每个 对象 上裸露 __proto__
属性,通过这个属性能够拜访 对象的原型。
看下图:
C.__proto__ = B
咱们把 __proto__
属性称之为 C 对象的原型 (prototype),__proto__
指向了内存中的 B 对象,咱们就把 __proto__
指向的 B 对象称为 C 对象的 原型对象,那么 C 对象就能够间接拜访其原型对象 B 的办法或者属性。
作用
形成 原型链 ,用于实现基于原型的继承。举个例子,当咱们拜访 C 这个对象中的 color 属性时,如果在 C 中找不到,那么就会沿着__proto__
顺次查找。
let A = {color: 'orange'}
let B = {name: 'pipi'}
let C = {type: 'dog'}
C.__proto__ = B
B.__proto__ = A
console.log(C.color) // orange
__proto__
属性在ES6
时才被标准化,以确保 Web 浏览器的兼容性,然而 不举荐应用。
- 这是暗藏属性,并不是规范定义的;
- 应用该属性会造成重大的性能问题。
为了更好的反对,举荐应用
Object.getPrototypeOf()
来获取 [[Prototype]]。
那应该怎么去正确地设置对象的原型对象呢?
答案是应用构造函数来创建对象。
构造函数创建对象
看上面这段代码:
function DogFactory(type,color){
this.type = type
this.color = color
}
let dog = new DogFactory('Dog','Black')
要创立 DogFactory
的实例,应应用 new
操作符。以这种形式调用构造函数会执行如下操作。
- 在内存中创立一个新对象。
- 这个新对象外部的 [[Prototype]] 个性被赋值为构造函数的 prototype 属性。
- 构造函数外部的 this 被赋值为这个新对象(即 this 指向新对象)。
- 执行构造函数外部的代码(给新对象增加属性)。
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创立的新对象。
咱们次要看第 2 步,这里 dog 对象外部的 [[Prototype]] 被赋值为了DogFactory.prototype
。
dog.__proto__ = DogFactory.prototype
那么 prototype
是什么呢?
Prototype
每个函数对象中都有一个公开的 prototype 属性,当你将这个函数作为构造函数来创立一个新的对象(实例)时,新创建对象(实例)的原型对象就指向了该函数的 prototype 属性。
当然了,如果你只是失常调用该函数,那么 prototype 属性将不起作用。
看上面这段代码:
function DogFactory(type,color){
this.type = type
this.color = color
//Mammalia
}
DogFactory.prototype.constant_temperature = 1
let dog1 = new DogFactory('Dog','Black')
let dog2 = new DogFactory('Dog','Black')
let dog3 = new DogFactory('Dog','Black')
// 实例通过__proto__链接到原型对象,它实际上指向暗藏个性[[Prototype]],构造函数通过 prototype 属性链接到原型对象。// 也就是说实例与构造函数没有间接分割,与原型对象有间接分割。console.log(dog1.__proto__ === DogFactory.prototype) // true
所以此时:
dog1.__proto__ = DogFactory. prototype
dog2.__proto__ = DogFactory. prototype
dog3.__proto__ = DogFactory. prototype
这样咱们三个 dog 对象的原型对象(DogFactory)都指向了 prototype,而 prototype 又蕴含了constant_temperature
属性,这就是咱们实现继承的正确形式。
constructor
默认状况下,所有原型对象主动取得一个名为 constructor 的属性,指回与之关联的构造函数。
还是看下面的例子:
function DogFactory(type,color){
this.type = type
this.color = color
//Mammalia
}
DogFactory. prototype.constant_temperature = 1
let dog1 = new DogFactory('Dog','Black')
console.log(DogFactory.prototype.constructor === DogFactory) // true
- 构造函数 DogFactory 有一个 prototype(
__proto__
)属性援用其原型对象; - 这个原型对象也有一个 constructor 属性,援用这个构造函数 DogFactory;
- 构造函数 DogFactory 有一个 prototype 属性援用其原型对象;
- ……
- 换句话说,两者循环援用。
原型链
ECMA-262 把原型链定义为 ECMAScript 的次要继承形式。其根本思维就是通过原型继承多个援用类型的属性和办法。
下面咱们晓得了原型链的实现次要是靠__proto__
,当然,理论应用中还是要用 prototype。
function DogFactory() {
this.type = 'toypoodle'
this.color = 'black'
}
DogFactory.prototype.getInfo = function () {return `Type is: ${this.type},color is ${this.color}.`
}
function Dog() {this.name = 'doudou'}
// 继承 DogFactory
Dog.prototype = new DogFactory();
Dog.prototype.getDogInfo = function () {return this.name}
let dog1 = new Dog()
console.log(dog1.getInfo()) // Type is: toypoodle,color is black.
上面补充几个知识点。
默认原型
默认状况下,所有援用类型都继承自 Object,这也是通过原型链实现的。任何函数的默认原型都是一个 Object 的实例,这意味着这个实例有一个外部指针指向
Object.prototype。这也是为什么自定义类型可能继承包含 toString()、valueOf() 在内的所有默认办法的起因。
还是用下面的代码举例:
function DogFactory(type, color) {
this.type = type
this.color = color
//Mammalia
}
console.log(DogFactory.prototype.__proto__ === Object.prototype); // true
console.log(DogFactory.prototype.__proto__.constructor === Object); // true
console.log(DogFactory.prototype.__proto__.__proto__ === null); // true
也就是说,失常的原型链都会终止于 Object 的原型对象,Object 原型的原型是 null。
instanceof
如果一个实例的原型链中呈现过相应的构造函数,则 instanceof 返回 true。
console.log(dog1 instanceof Object); // true
console.log(dog1 instanceof DogFactory); // true
console.log(dog1 instanceof Dog); // true
isPrototypeOf()
原型链中的每个原型都能够调用这个办法,只有原型链中蕴含这个原型,这个办法就返回 true。
console.log(Object.prototype.isPrototypeOf(dog1)); // true
console.log(DogFactory.prototype.isPrototypeOf(dog1)); // true
console.log(Dog.prototype.isPrototypeOf(dog1)); // true
对于办法
子类笼罩父类办法
function DogFactory() {
this.type = 'toypoodle'
this.color = 'black'
}
DogFactory.prototype.getInfo = function () {return `Type is: ${this.type},color is ${this.color}.`
}
function Dog() {this.name = 'doudou'}
// 继承 DogFactory
Dog.prototype = new DogFactory();
Dog.prototype.getDogInfo = function () {return this.name}
// 子类重写新办法
Dog.prototype.getInfo = function(){return `Color is ${this.color}.`
}
let dog1 = new Dog()
console.log(dog1.getInfo()) // Color is black.
Dog 实例上调用 getInfo() 时调用的是这个办法。而 DogFactory 的实例依然会调用最后的办法。
重点在于上述两个办法都是在把原型赋值为 DogFactory 的实例之后定义的。
对象字面量形式创立原型
以对象字面量形式创立原型办法会毁坏之前的原型链,因为这相当于重写了原型链。
function DogFactory() {
this.type = 'toypoodle'
this.color = 'black'
}
DogFactory.prototype.getInfo = function () {return `Type is: ${this.type},color is ${this.color}.`
}
function Dog() {this.name = 'doudou'}
// 继承 DogFactory
Dog.prototype = new DogFactory();
Dog.prototype.getDogInfo = function () {return this.name}
// 通过对象字面量增加新办法,这会导致上一行有效
Dog.prototype = {getDoggInfo() {return this.color}
}
let dog1 = new Dog()
console.log(dog1.getInfo()) // Uncaught TypeError: dog1.getInfo is not a function
原型链的问题
原型链的第一个问题是,原型中蕴含的援用值会在所有实例间共享。
看上面的代码:
function DogFactory() {this.colors = ["red", "blue", "green"]
}
function Dog() {}
// 继承 DogFactory
Dog.prototype = new DogFactory();
let dog1 = new Dog()
dog1.colors.push('black')
console.log(dog1.colors) // ["red", "blue", "green", "black"]
let dog2 = new Dog()
console.log(dog2.colors) // ["red", "blue", "green", "black"]
通过 dog1 改变 colors 属性也会反映到 dog2 上,而这往往不是咱们想要的。这也是为什么属性通常会在构造函数中定义而不会定义在原型上的起因。
原型链的第二个问题是,子类型在实例化时不能给父类型的构造函数传参。
参考
[javascript 高级程序设计第四版]
图解 Google V8
js 中__proto__和 prototype 的区别和关系?