深入理解js中的原型链(一)

327 阅读4分钟

文章来自于公众号coderwhy

一.理解原型

1.1.什么是原型呢

你需要先知道一个事实:
我们创建的每个函数都有一个prototype(原型)属性
这个属性是一个指针,指向一个对象
而这个对象的作用是存放这个类型创建的所有实例(new出来的就叫实例)
实例也是对象,每个对象都有 __protp__属性 指向的这个对象, 就是我们的所谓的原型对象.
原型对象的作用:
使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
我们来看看原型对象的使用:

// 创建对象的构造函数
function Person() {}

// 通过原型对象来设置一些属性和值
Person.prototype.name = "Coderwhy"
Person.prototype.age = 18
Person.prototype.height = 1.88
Person.prototype.sayHello = function () {
    alert(this.name)
}

// 创建两个对象, 并且调用方法
var person1 = new Person()
var person2 = new Person()

person1.sayHello() // Coderwhy
person2.sayHello() // Coderwhy

代码解析:
在上面的代码中, 我们没有给实例对象单独设置属性和方法, 而是直接设置给了原型对象.
而原型对象的作用是可以让所有的对象来共享这些属性和方法.
因此, 我们调用sayHello()方法时, 它们打印的结果是一样的, 它们是共享的.

1.2. 深入原型对象

原型对象的创建:
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。

原型上的constructor属性:
默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。
用我们上面的例子来说, Person.prototype.constructor指向Person。
也就是原型对象自身来说, 只有一个constructor属性, 而其他属性可以由我们添加或者从Object中继承.
新的实例创建时, 原型对象在哪里呢?
当调用构造函数创建一个新实例后,该实例的内部将包含一个内部属性,该属性的指针, 指向构造函数的原型对象。
这个属性是_proto_
简单说, 每个实例中, 其实也会有一个属性, 该属性是指向原型对象的.

// 原型对象中有一个属性: constructor属性
// 属性指向Person函数
console.log(Person.prototype.constructor); // Person函数

// 对象实例也有一个属性指向原型
console.log(person1.__proto__); // 原型对象
console.log(Person.prototype); // 原型对象
console.log(person1.__proto__ === Person.prototype); // true

我们通过一个图来解释上面的概念:

image.png

解析:
上面的图解析了Person构造函数、Person的原型属性以及Person现有的两个实例之间的关系
Person.prototype指向原型对象, 而Person.prototype.constructor又指回了Person.
原型对象中除了包含constructor属性之外,还包括后来添加的其他属性。
Person的每个实例——personl和person2都包含一个内部属性_proto_,该属性也指向原型对象;
你可以理解为什么person1修改了name后, person2也会修改吗?
通过上面的图, 自己再来理解一下吧.

person1.sayHello() // Coderwhy
person2.sayHello() // Coderwhy

person1.__proto__.name = "Kobe"

person1.sayHello() // Kobe
person2.sayHello() // Kobe

但是要注意下面的情况:
当我们给person1.name进行赋值时, 其实在给person1实例添加一个name属性.
这个时候再次访问时, 就不会访问原型中的name属性了.

// 创建两个对象, 并且调用方法
var person1 = new Person()
var person2 = new Person()

person1.sayHello() // Coderwhy
person2.sayHello() // Coderwhy

// 给person1实例添加属性
person1.name = "Kobe"
person1.sayHello() // Kobe, 来自实例
person2.sayHello() // Coderwhy, 来自原型

通过hasOwnProperty判断属性属于实例还是原型.

// 判断属性属于谁
alert(person1.hasOwnProperty("name")) // true
alert(person2.hasOwnProperty("name")) // false

1.3.修改原型属性

考虑下面的代码执行是否会有问题:


// 定义Person构造函数
function Person() {}

// 创建Person的对象
var person = new Person()

// 给Person的原型添加方法
Person.prototype.sayHello = function () {
    alert("Hello JavaScript")
}

// 调用方法
person.sayHello()

代码解析:
我们发现代码的执行没有任何问题.
因为在创建person的时候, person的__proto__也是指向的Person.prototype.
所以, 当动态的修改了Person.prototype中的sayHello属性时, person中也可以获取到该属性
图解上面的过程:

image.png

我们再来看下面的代码会不会有问题:

// 定义Person构造函数
function Person() {}

// 创建Person的对象
var person = new Person()

// 给Person的原型添加方法
Person.prototype = {
    constructor: Person,
    sayHello: function () {
        alert("Hello JavaScript")
    }
}
// 调用方法
person.sayHello()

代码解析:
代码是不能正常运行的. 因为Person的prototype指向了一个新的对象.
而最初我们创建的person依然指向原来的原型对象, 原来的原型对象没有sayHello()函数.
当然, 如果再次之后, 再创建的Person对象, 是可以调用sayHello()的, 但是再次之前创建的, 没有该方法.
图解上面的过程:

image.png