Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript深入之从原型到原型链 #9

Open
CharlesGC opened this issue Aug 17, 2021 · 0 comments
Open

JavaScript深入之从原型到原型链 #9

CharlesGC opened this issue Aug 17, 2021 · 0 comments

Comments

@CharlesGC
Copy link
Owner

构造函数创建对象

function Person () { }
let person1 = new Person()
person1.name = 'Charles'
person1.age = 27
person1.job = 'Software Engineer'

person1.sayName = function() {
    console.log(this.name)
}

person1.sayName() // Charles

在这个例子中,Person就是一个构造函数,我们使用 new 创建了一个实例对象person1,这是是用构造函数创建对象,很简单吧。

prototype

我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象就是我们通过调用构造函数创建的那个对象实例的原型对象。

这个原型对象就管理着由特定类型的创建的所有实例共享的属性和方法。也就是说我们通过某个构造函数创建对象实例,它都会从它指向的原型那里继承属性和方法。

由上个例子:

我们在浏览器打印一下,可以看到,Person.prototype是一个对象,这个原型对象上有一个constructor属性指向构造函数Person,如图所示,这里是一个循环引用。

__poroto__和[[prototype]]

我们现在知道了构造函数上有一个prototype属性指向了原型对象,原型对象上的constructor属性指向构造函数,那另一个属性__proto__属性呢?它是代表什么意思?有什么作用呢?

我们来看下面这个例子:

我们可以看到,用Man构造函数实例化的child里面也有一个__proto__参数,而且它同样指向一个对象,我们发现它和Man函数的原型对象一模一样,__proto__是每个实例上都有的属性,但prototype是构造函数上的属性,这两个不一样,但 p.__proto__和 Parent.prototype 却指向同一个对象。

我们从上面知道,构造函数和原型对象之间通过prototype和constructor进行连接,所以构造函数,实例和原型对象之间的关系如下图:

现在我们知道可以通过__proto__访问到对象的内部 [[Prototype]]

[[Prototype]] 是对象的一个内部属性,外部代码无法直接访问。

遵循 ECMAScript 标准,someObject.[[Prototype]] 符号用于指向 someObject 的原型。

注意点

__proto__属性在 ES6 时才被标准化,以确保 Web 浏览器的兼容性,但是不推荐使用,除了标准化的原因之外还有性能问题。为了更好的支持,推荐使用 Object.getPrototypeOf()。

通过改变一个对象的[[Prototype]]属性来改变和继承属性会对性能造成非常严重的影响,并且性能消耗的时间也不是简单的花费在obj.proto= ... 语句上, 它还会影响到所有继承自该[[Prototype]]的对象,如果你关心性能,你就不应该修改一个对象的 [[Prototype]]。

如果要读取或修改对象的 [[Prototype]] 属性,建议使用如下方案,但是此时设置对象的 [[Prototype]] 依旧是一个缓慢的操作,如果性能是一个问题,就要避免这种操作。

// 获取
Object.getPrototypeOf()
Reflect.getPrototypeOf()

// 修改
Object.setPrototypeOf()
Reflect.setPrototypeOf()

如果确要新建一个对象并继承另一个对象的[[prototype]],可以使用Object.create()。

var man =  {
    age: 50
};
var child = Object.create(man);

这时child上有一个指针__proto__ 指向man对象。

原型链

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。

来看下面这个例子:

function Parent(age) {this.age = 1}
function Son () {}
Son.prototype = new Parent()
var grandSon = new Son()
console.log(grandSon.age) // 1

为什么grandSon上并没有定义age属性,却能打印出 1 ,因为原型链的搜索机制是,当以读取模式访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。就拿上面的例子来说,调用
grandSon.age 会经历三个搜索步骤:1)搜索实例;2)搜索 Son.prototype ;3)搜索 Parent.prototype ,最后一步才会找到该属性。在找不到属性或方法的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。

继续上面的例子验证我们的想法:

function Parent(age) {this.age = 1}
function Son () {}
Son.prototype = new Parent()
var grandSon = new Son()
console.log(grandSon.age) // 1
console.log(grandSon.__proto__ === Son.prototype) // true
console.log(grandSon.__proto__.__proto__ === Parent.prototype) // true
console.log(grandSon.__proto__.__proto__.__proto__ === Object.prototype) // true
console.log(grandSon.__proto__.__proto__.__proto__.__proto__ === null) // true

参考文章:

下集预告

原型链和继承

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant