简单地理解,继承就是一个对象能够访问另一个对象中的属性和方法。在JavaScript 中,咱们经过原型和原型链的方式来实现了继承特性。javascript
观察上图,由于 B 继承了 A,那么 B 能够直接使用 A 中的 color 属性,就像这个属性是 B 自带的同样。java
JavaScript中任意对象都有一个内置属性 [[Prototype]] ,可是ES5以前没有访问这个 [[Prototype]] 属性的标准方式,因此大多数浏览器会在每一个对象上暴露__proto__
属性,经过这个属性能够访问对象的原型。浏览器
看下图:函数
C.__proto__ = B
咱们把 __proto__
属性称之为 C 对象的原型 (prototype),__proto__
指向了内存中的 B 对象,咱们就把 __proto__
指向的 B 对象称为 C 对象的原型对象,那么 C 对象就能够直接访问其原型对象 B 的方法或者属性。性能
构成原型链,用于实现基于原型的继承。举个例子,当咱们访问 C 这个对象中的 color 属性时,若是在 C 中找不到,那么就会沿着__proto__
依次查找。this
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 浏览器的兼容性,可是不推荐使用。spa
- 这是隐藏属性,并非标准定义的;
- 使用该属性会形成严重的性能问题。
为了更好的支持,推荐使用
Object.getPrototypeOf()
来获取 [[Prototype]] 。prototype
那应该怎么去正确地设置对象的原型对象呢?设计
答案是使用构造函数来建立对象。指针
看下面这段代码:
function DogFactory(type,color){ this.type = type this.color = color } let dog = new DogFactory('Dog','Black')
要建立 DogFactory
的实例,应使用 new
操做符。以这种方式调用构造函数会执行以下操做。
咱们主要看第2步,这里 dog 对象内部的 [[Prototype]] 被赋值为了DogFactory.prototype
。
dog.__proto__ = DogFactory.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 的属性,指回与之关联的构造函数。
仍是看上面的例子:
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
__proto__
) 属性引用其原型对象;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 返回 true。
console.log(dog1 instanceof Object); // true console.log(dog1 instanceof DogFactory); // true console.log(dog1 instanceof Dog); // true
原型链中的每一个原型均可以调用这个方法,只要原型链中包含这个原型,这个方法就返回 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高级程序设计第四版]