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原型 #2

Open
daydaylee1227 opened this issue Jun 27, 2020 · 0 comments
Open

谈一谈JavaScript原型 #2

daydaylee1227 opened this issue Jun 27, 2020 · 0 comments
Assignees
Labels

Comments

@daydaylee1227
Copy link
Owner

前言

写在前面

首先我们需要明确两点:

1️⃣__proto__constructor对象独有的

2️⃣prototype属性是函数独有的

原型

prototype

  • 在最新ES规范里,prototype 被定义为:给其它对象提供共享属性的对象。
  • 也就是说,prototype 自己也是对象,只是被用以承担某个职能罢了。

因此,prototype 描述的是两个对象之间的某种关系(其中一个,为另一个提供属性访问权限)。

constructor与prototype联系

  • 每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)
  • 原型对象中有一个属性constructor,它指向函数对象
  • 给原型对象添加属性(一般是方法)
    • 作用:函数的所有实例对象自动拥有原型中的属性(方法)

下面通过一个例子来说明:

		function Demo() {}
        console.log(Demo.prototype.constructor === Demo)  // true
        console.dir(Demo.prototype)

![](C:\Users\DayDay\Pictures\Camera Roll\demo.png)

可以看到就是Demo函数对象的prototype原型是右边这个对象,那么Demo.prototype原型上有个constructor属性,这个属性正好指向Demo函数本身。

所有你可以理解成:

A的显示原型是B,则有:
A.prototype === B 
B.constructor === A

我觉得这样子唯一的好处在于你可以找到我,我也可以找到你。好滑稽

__proto__和prototype关系

再次强调 :

1️⃣__proto__constructor对象独有的。2️⃣prototype属性是函数独有的

关于更多__proto__更深入的介绍,可以参看工业聚大佬的《深入理解 JavaScript 原型》一文。

显示原型和隐式原型

  • 每个函数fun都独有一个prototype, 及显式原型(属性)
  • 每个实例对象都有一个__proto__, 及隐式原型(属性)**
  • 对象的隐式原型的值 === 其构造函数的显示原型的值

怎么理解呢?我们通过内存结构图来看看吧

		function Demo() {}
        Demo.prototype.say = () => {       //给原型添加say方法
            console.log("hello world")
        }
        
        console.log(Demo.prototype.say)
        let fn = new Demo();
        
        fn.say();   // 怎么找到say方法的呢?
        console.log(fn.__proto__ === Demo.prototype)  // true

原型1

我们从图中可以看到,Demo函数的原型跟它构造函数(Demo)创建的实例fn.__proto__指向同一个对象。

那么fn是怎么找到say方法的呢?

更加具体的说就是通过隐式原型__proro__找到的,分析如下:

  • js引擎执行到fn.say()整行代码时,解析器去栈中查找fn变量
  • 发现fn变量是引用类型,就去堆内存中查找地址为0x234的实体,查到后,发现并没有say属性,接着就去找__proro__属性对应的原型
  • 接着找到内存地址为0x345对应的实体,发现该实体中有say属性,同样的操作去找地址为0x789的实体,最后执行该函数。

那么是不是可以更加准确的说明:实例是通过隐式原型__proto__查找需要调用的属性的,那么我们通过接下来的代码去验证一下。

代码:

function Demo() {}
        Demo.prototype.say = () => {       //给原型添加say方法
            console.log("hello world")
        }
        Demo.prototype.name = 'old name'
        let fn = new Demo();
        
        fn.say();   // 怎么找到say方法的呢?
        console.log(fn.name)
        console.log("为修改前",fn.__proto__ === Demo.prototype)  // true
        console.log("-------接下来修改fn的__proto__")
        fn.__proto__ = {
            say: () => {
                console.log("hello 隐式原型")
            },
            name : 'new name'
        }
        console.log("修改实例中的隐式原型",fn.__proto__ === Demo.prototype)  // true
        console.log(fn.name)
        fn.say()
        console.log("重新创建一个Demo构造函数实例")
        let demo1 = new Demo();
        console.log(Demo.prototype === demo1.__proto__)
        demo1.say()

证明隐式原型

首先的说明的是:

通过查阅相关的文档,ES6之前不能直接操作隐式原型,也不推荐你这么做。

通过修改fn的隐式原型,让它指向一个新的对象。那么fn.proto 不等于Demo.prototype. 这个例子也能证明一点,实例对象调用属性时,实例对象不具有该属性时,是通过隐式原型去找的该属性的,找不到的话,在它的隐式原型对象隐式原型对象上找。

这也就是我们常说的,在原型上添加属性或者方法,实例可以共享,原因就在于我们并不推荐去修改实例的__proto__属性,这样子也就是会有一下结果:

function Demo() {
		// 内部语句 this.prototype = {}
}
let fn = new Demo(); // 内部语句: fn.`__proto__` = Demo.prototype

// 实例化一个对象隐式原型会默认赋值: fn.__proto__ = Demo.prototype
// 定义函数时: 显式原型也会默认添加: Demo.prototype = new Object()

这里我们需要知道的是,__proto__是对象所独有的,并且__proto__一个对象指向另一个对象,也就是他的原型对象。我们也可以理解为父类对象。它的作用就是当你在访问一个对象属性的时候,如果该对象内部不存在这个属性,那么就回去它的__proto__属性所指向的对象(父类对象)上查找,如果父类对象依旧不存在这个属性,那么就回去其父类的__proto__属性所指向的父类的父类上去查找。以此类推,知道找到 null。而这个查找的过程,也就构成了我们常说的原型链

总结

  • 那什么是原型呢?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
  • 函数的prototype属性:在定义函数时自动添加prototype,默认是一个空Object对象
  • 对象的__proto__属性:创建一个对象实例时,默认值是构造函数的prototype属性值,也就是上面所说的
  • 实例的构造函数属性(constructor)指向构造函数
  • 一般而言,可以直接操作显式原型,不能直接操作隐式原型(ES6)
  • 更多规范,移步MDN

补充

ObjectFunction的鸡和蛋的问题

**最后总结: ** 先有Object.prototype(原型链顶端),Function.prototype继承Object.prototype而产生,最后,Function和Object和其它构造函数继承Function.prototype而产生。

MDN的推荐

使用__proto__是有争议的,也不鼓励使用它。因为它从来没有被包括在ECMAScript语言规范中,但是现代浏览器都实现了它。__proto__属性已在ECMAScript 6语言规范中标准化,用于确保Web浏览器的兼容性,因此它未来将被支持。它已被不推荐使用, 现在更推荐使用Object.getPrototypeOf/Reflect.getPrototypeOfObject.setPrototypeOf/Reflect.setPrototypeOf(尽管如此,设置对象的[[Prototype]]是一个缓慢的操作,如果性能是一个问题,应该避免)。

proto 属性也可以在对象文字定义中使用对象[[Prototype]]来创建,作为Object.create() 的一个替代。

prototype chain 原型链

a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain.

如上,在 ECMAScript 2019 规范里,只通过短短的一句话,就介绍完了 prototype chain

原型链的概念,仅仅是在原型这个概念基础上所作的直接推论。

既然 prototype 只是恰好作为另一个对象的隐式引用的普通对象。那么,它也是对象,也符合一个对象的基本特征。

每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找...... 这个操作被委托在整个原型链上,这个就是我们说的原型链了。

结论:

  • __proto__ 是原型链查询中实际用到的,它总是指向 prototype
  • prototype 是函数所独有的**,**在定义构造函数时自动创建,它总是被 proto 所指。
  • 所有对象都有__proto__属性,函数这个特殊对象除了具有__proto__属性,还有特有的原型属性prototype。prototype对象默认有两个属性,constructor属性和__proto__属性。prototype属性可以给函数和对象添加可共享(继承)的方法、属性,而__proto__是查找某函数或对象的原型链方式。constructor,这个属性包含了一个指针,指回原构造函数。

参考

深入理解 JavaScript 原型

一文吃透所有JS原型相关知识点

JavaScript深入之从原型到原型链

从__proto__和prototype来深入理解JS对象和原型链

js原型链

@daydaylee1227 daydaylee1227 self-assigned this Jun 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant