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从原型到原型链 #14

Open
wolfdu opened this issue Dec 18, 2017 · 0 comments
Open

JavaScript从原型到原型链 #14

wolfdu opened this issue Dec 18, 2017 · 0 comments

Comments

@wolfdu
Copy link
Owner

wolfdu commented Dec 18, 2017

https://wolfdu.fun/post?postId=5a22c111c7ad1346411b7264

最近正在拜读JavaScript深入系列文章,初读发现文章简洁明了,知识循序渐进,虽然有些知识点在文章中介绍的不够完全但是在评论区中的讨论却是十分火热,引人思考,所以觉得有必要学习整理梳理其中的观点与知识。

借助文章梳理一下原型&原型链。

原型(prototype)

先看栗子:

//构造函数
function Person() {

}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin

那么prototype属性到底指向的是什么呢?

这个属性是指向的是一个对象,这个对象称为原型对象->调用该构造函数(Person)而创建的实例的原型,换句话说就是栗子中person1person2的原型。

那么原型是什么?

每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

关于这里的“继承”,真正的继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。(这个观点可以在阅读完全文后有更深的理解)

构造函数同实例原型关系图:
prototype

那么实例同实例原型的关系从哪里体现呢(也是就是person与Person.prototype的关系)?
我们来看下面这个属性👇

_proto_

以下是MDN解释:

The _proto_ property of Object.prototype is an accessor property (a getter function and a setter function) that exposes the internal [[Prototype]] (either an object or null) of the object through which it is accessed.

__proto__属性是一个访问器属性(一个getter函数和一个setter函数), 暴露了通过它访问的对象的内部Prototype

__proto__是每一个JavaScript对象(除了 null )都具有的一个属性,这个属性会指向该对象的原型。

可以用一段代码验证下:

function Person() {

}
var person = new Person();
console.log(person.__proto__ === Person.prototype);  //  true

//使用__proto__获取原型对象是不被推荐的可以使用Object.getPrototypeOf(obj)
console.log(Object.getPrototypeOf(person) === Person.prototype)//true

那么我们可以完善上面的关系图:

这里我知道了构造函数可以通过prototype指向原型对象,实例可以通过__proto__指向原型对象。
那么原型对象是否有属性指向构造函数或实例呢?

可以思考,一个构造函数可以生成多个实例,也就是说很多个实例可以指向同一个原型对象,所以还没有原型对象指向实例的属性。

但是原型对象指向构造函数的属性是有的👇

constructor

以下是MDN解释:

Returns a reference to the Object constructor function that created the instance object.

返回创建实例对象的 Object 构造函数的引用。

constructor,每个原型都有一个 constructor 属性指向关联的构造函数。

function Person() {

}
console.log(Person === Person.prototype.constructor); // true

再次更新关系图:

至此,搞清楚了构造函数,原型对象和实例之间的关系。
👇我们来理一理实例和原型对象之间的关系。

实例与原型

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

举个例子:

function Person() {

}

Person.prototype.name = 'Kevin';

var person = new Person();

person.name = 'Daisy';
console.log(person.name) // Daisy

delete person.name;
console.log(person.name) // Kevin

可以看到,当删除person.name属性的时候,再去读取name属性在person中找不到name属性,就会从person的原型person.__proto__也就是Person.prototype中查找,例子中的原型对象中是有name属性,所以这里得到的结果是Kevin

但是如果没有找到呢?
如果没有找到是不是会继续向上查找原型的原型中的属性,那么原型的原型又是什么呢?

用代码说话:

function Person() {

}

var person = new Person();

person.name = 'Daisy';
console.log(person.name) // Daisy

delete person.name;
console.log(person.name) // undefined

如果没找到最后输出undefined

那么原型的原型是什么?👇

原型的原型

摘自MDN

Nearly all objects in JavaScript are instances of Object; a typical object inherits properties (including methods) from Object.prototype, although these properties may be shadowed (a.k.a. overridden).

几乎所有的 JavaScript 对象都是 Object 的实例;一个典型的对象“继承”了Object.prototype的属性(包括方法),尽管这些属性可能被遮蔽(也被称为覆盖)。

我们知道,原型也是一个对象,那么我们可以知道原型对象是通过Object构造函数生成的,那么我们继续更新一下关系图:

既然都说了原型也是对象那么原型的原型的原型是什么呢?😸
说人话就是Object.prototype的原型是什么呢?
可以通过代码探一探究竟:

console.log(Object.prototype.__proto__ === null) // true

Object.prototype的原型竟然是null!!!
阮一峰--undefined与null的区别中我们可以得知null的含义。

null表示"没有对象",即该处不应该有值。

可以理解为,Object.prototype没有原型对象。
最后更新关系图:

图中的蓝色链就是我们所说的原型链。

总结

  • 构造函数的prototype属性指向的是由该函数创建(new)的实例的原型
  • 实例的__proto__属性指向的是该实例的原型对象
  • 原型对象的constructor属性指向关联的构造函数
  • 在当前实例中查找属性如果没有找到,会顺着原型链一直向上查找(类似作用域的查找),直到查找到顶端为止
  • Object.prototype没有原型对象(为null表示木有)。

以为到这里就完了吗???☝N☝O☝!!!
下面才是正文...😹

鸡生蛋,蛋生鸡?

先执行如下代码:

console.log(Function.__proto__ === Function.prototype) // true

用原型链关系图来解释:Function是Function的一个实例(感觉怪怪的。。)

在JavaScript中,Function构造函数本身也算是Function类型的实例吗?Function构造函数的prototype属性和__proto__属性都指向同一个原型,是否可以说Function对象是由Function构造函数创建的一个实例?

其实呢,不仅仅Function.__proto__ === Function.prototypeObject.__proto__ === Function.prototype也为true,Array.__proto__ === Function.prototype也为true。

仔细想想其实Function,Object,Array都是一个函数与自定义的构造函数是一样的,那么我们认为Function,Object,Array都是Function的实例;

其实构造函数和普通函数并没有什么区别,只有使用new关键字调用时函数才起到了构造函数的作用,我们只是习惯性将首字母大写以示区别。JS 的 new 到底是干什么的?

所以Function在作为构造函数时Function.prototype指向的是一个函数原型对象,作为函数实例时Function.__proto__指向的也是一个函数原型对象,而这里的函数原型对象,我更倾向理解为JavaScript内置的函数对象,所以并不会出现Function对象由Function构造函数创建的情况。

以下是我觉得靠谱点的解释

1.Function作为一个内置对象,是运行前就已经存在的东西,所以根本就不会根据自己生成自己,所以就没有什么鸡生蛋蛋生鸡,就是鸡生蛋。至于为什么Function.__proto__ === Function.prototype,我认为有两种可能:一是为了保持与其他函数一致,二是就是表明一种关系而已。
简单的说,我认为:就是先有的Function,然后实现上把原型指向了Function.prototype,但是我们不能倒过来推测因为Function.__proto__ === Function.prototype,所以Function调用了自己生成了自己。

阅读文章:
JavaScript深入之从原型到原型链
阮一峰--undefined与null的区别
JS 的 new 到底是干什么的?
在JavaScript中,Function构造函数本身也算是Function类型的实例吗?

若文中有知识整理错误或遗漏的地方请务必指出,非常感谢。如果对你有一丢丢帮助或引起你的思考,可以点赞鼓励一下作者=^_^=

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

No branches or pull requests

1 participant