[javascript总结]原型和原型链

继承

简单地理解,继承就是一个对象能够访问另一个对象中的属性和方法。在JavaScript 中,咱们经过原型和原型链的方式来实现了继承特性。javascript

image-20210518172746716

观察上图,由于 B 继承了 A,那么 B 能够直接使用 A 中的 color 属性,就像这个属性是 B 自带的同样。java

\_proto\_

JavaScript中任意对象都有一个内置属性 [[Prototype]] ,可是ES5以前没有访问这个 [[Prototype]] 属性的标准方式,因此大多数浏览器会在每一个对象上暴露__proto__属性,经过这个属性能够访问对象的原型浏览器

看下图:函数

image-20210519090101754

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

image-20210519101816391

__proto__ 属性在 ES6 时才被标准化,以确保 Web 浏览器的兼容性,可是不推荐使用spa

  1. 这是隐藏属性,并非标准定义的;
  2. 使用该属性会形成严重的性能问题。

为了更好的支持,推荐使用 Object.getPrototypeOf()来获取 [[Prototype]] 。prototype

那应该怎么去正确地设置对象的原型对象呢?设计

答案是使用构造函数来建立对象。指针

构造函数建立对象

看下面这段代码:

function DogFactory(type,color){
this.type = type
this.color = color
}
let dog = new DogFactory('Dog','Black')

要建立 DogFactory 的实例,应使用 new 操做符。以这种方式调用构造函数会执行以下操做。

  1. 在内存中建立一个新对象。
  2. 这个新对象内部的 [[Prototype]] 特性被赋值为构造函数的 prototype 属性。
  3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
  4. 执行构造函数内部的代码(给新对象添加属性)。
  5. 若是构造函数返回非空对象,则返回该对象;不然,返回刚建立的新对象。

咱们主要看第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 属性,这就是咱们实现继承的正确方式。

image-20210519111757179

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

image-20210519140643914

  1. 构造函数 DogFactory 有一个 prototype(__proto__) 属性引用其原型对象;
  2. 这个原型对象也有一个 constructor 属性,引用这个构造函数 DogFactory ;
  3. 构造函数 DogFactory 有一个 prototype 属性引用其原型对象;
  4. ......
  5. 换句话说,二者循环引用。

原型链

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的区别和关系?

相关文章
相关标签/搜索