片段 1:
let Foo = function Foo(){};
Foo.prototype= {x:1};
let FooSon = new Foo();
console.log(FooSon.x); //输出:1
Foo.prototype.x =2;
console.log(FooSon.x); //输出:2
片段 2:
let Con = function Con(){}
Con.prototype.x =2;
let ConSon = new Con();
console.log(ConSon.x); //输出:2
Con.prototype= {x:1};
console.log(ConSon.x); //输出:2
let QonSon = new Qon(); console.log(QonSon.x); //输出:1 =>引用了最新原型
问题是看不懂为什么先 prototyp={},再 prototype.x 方式时候后面的实例能够继承到最新的原型,反过来就不可以?这里面牵涉到了什么高深知识点,万能的 v 友久久我。
1
rpxwa 2020-07-06 22:40:08 +08:00 via iPhone 1
可以看出 prototype 本质上是一个指针。
我给了你一张纸条,纸条上写着我家的地址,这个地址就叫指针。 当我把我家玻璃砸碎,你通过这个地址来到我家,你就能看到玻璃已被砸碎。 然后我把你手里的纸条撕碎,给了你一张新纸条,纸条上写着我刚买的毛坯房地址。 现在我把我原来家的玻璃修好。 你顺着纸条找过来,目光所及没有修好的玻璃,只有空空如也的房子。 |
2
JerryY 2020-07-06 22:45:45 +08:00
建议先看看红宝书再来思考这个问题。这里给一点参考,prototype={}已经改写了原型对象了,这时候再 prototype.x 是在这个改写后的对象上的修改,这里不是你说的`继承到最新的原型`;反过来的话,就更好理解了,因为你先 prototype.x 赋值,再改掉这个 prototype 的整个对象,这时候原型链已经“丢失”了。
|
3
ChanKc 2020-07-06 22:51:04 +08:00
想了一下
原型链其实继承的是对象 Foo.prototype= {x:1}; 这里是表示 new Foo 创建的对象要继承于匿名的{x:1}对象 Foo.prototype= {x:2}; 这时候就继承于另外一个新创建的匿名对象{x:2}了,不过因为原来的{x:1}还能通过你之前 new 的 Foo 的原型来获取,所以不会被回收 |
5
Kaciras 2020-07-06 23:00:41 +08:00
不知道你能不能理解这个最基本的引用问题:
let A = { x: 1 }; let B = A; A.x = 2; 请问现在 B.x = ? let A = { x: 2 }; let B = A; A = { x: 1 }; 请问现在 B.x = ? 你的例子就是上面的复杂版,没有高深的知识点,只是“原型链”三个字把你吓着了。 我是这么理解原型链的,就两条: 1.实例化时,自动给实例添加一个__proto__的属性,其值为类(或函数)的 prototype 属性 2.从对象查找属性时,如果找不到则自动往__proto__上递归查找 let ConSon = new Con(); // ConSon.__proto__ = Con.prototype Con.prototype= {x:1}; // Con.prototype 重新赋值,但是 ConSon.__proto__ 仍指向旧的值 |
6
ChanKc 2020-07-06 23:02:00 +08:00
另外本质上,new 可以大致等同于以下操作
let foo = Object.create(Foo.prototype); Foo.call(foo) 这里就只是用了一些 Foo 的原型链指向的一个对象,然后再拿 Foo 作为普通函数调用一下。可以说构造完的对象和 Foo 这个函数没什么关系了 |
7
rzWack OP @JerryY 嗯嗯,这个我理解了。但是这里还有个奇怪的问题,在先 prototype.x 赋值,再通过 prototype={..} 覆盖掉当前实例的原型后,再使用 prototype.x 赋值。此时输出的值仍然还是第一次 prototype.x 的值。这里是不符合前面 “ 再 prototype.x 是在这个改写后的对象上的修改 ”的逻辑的呀
|
8
rzWack OP @Kaciras 第一个是引用,值跟着变;第二个不太懂,用浏览器输出了一下 B.x=2,猜想 A={..}的时候重写地址发生了改变,B 还是指向原来 A 的地址,所以还是 2
|
9
ChanKc 2020-07-06 23:19:40 +08:00
@rzWack 索性这样,单纯只考虑原型链的话
我把 foo = new Foo()替换成 foo = {} Object.setPrototypeOf(foo, Foo.prototype) Foo.call(foo) 接下来我把 Foo.prototype 换个写法 let obj = {x:1} Foo.prototype = obj foo = {} Object.setPrototypeOf(foo, Foo.prototype) Foo.call(foo) 然后我等价代换一下 let obj = {x:1} Foo.prototype = obj foo = {} Object.setPrototypeOf(foo, obj) Foo.call(foo) 问此时 foo 这个对象和 Foo.prototype 还有什么关系 |
11
hurrytospring 2020-07-06 23:21:28 +08:00
理解一下把 new 的过程实现一遍清楚了
|
12
Jirajine 2020-07-06 23:23:36 +08:00 via Android
看这个,图文并茂深入浅出
https://javascript.info/function-prototype |
13
Kaciras 2020-07-06 23:29:42 +08:00
@rzWack 你的猜想是对的,把 A 换成 Con.prototype,B 换成 ConSon.__proto__ 就是你的问题了
|
14
shuangya 2020-07-06 23:43:58 +08:00
|
15
rzWack OP @ChanKc 这里只看懂到
Object.setPrototypeOf(foo, obj) => foo.prototype=obj 。Foo.call(foo)这一步不懂,大佬能给解释一下不😂 |
17
cs419 2020-07-07 07:05:19 +08:00 1
let Foo = function Foo(){};
Foo.prototype= {x:1}; let s01 = new Foo(); s01.__proto__.y=2; console.log(s01.__proto__ == Foo.prototype); let s02 = new Foo(); console.log(s02.y); Foo.prototype = {z:3}; console.log(s01.__proto__ == s02.__proto__); console.log(s01.__proto__ == Foo.prototype); |
18
Mutoo 2020-07-07 07:53:58 +08:00
分享一下我的笔记:JS 原型拓扑
https://blog.mutoo.im/2015/01/topology-of-javascript-prototype/ 画重点: 当 let foo = new Foo() 的时候,伪代码相当于 let foo = {__proto: Foo.prototype }; 之后修改 Foo.prototype 指向另一个 {} 时,不影响已经创建的 foo 的 __proto 指向原来的 prototype |
19
ChanKc 2020-07-07 08:22:43 +08:00 via Android
@rzWack Foo.call(foo)就是“让 Foo 函数中的 this 指向 foo,并在这个情况下调用 Foo 函数”
|
20
ChanKc 2020-07-07 08:54:02 +08:00 via Android
@rzWack setPrototypeOf 等于楼里其他人说的调用__proto__的 setter 。我不用__proto__是因为在最新的 ecmascript 标准里不推荐用
|
21
darknoll 2020-07-07 09:00:27 +08:00
应该是原型对象指向了新的对象,此时 QonSon.__proto__ !== Con.prototype
|
22
fxxwor99LVHTing 2020-07-07 09:33:46 +08:00
你的代码我没看懂,建议发代码前最好整理下格式,
推荐看下《 JavaScript 高级程序设计》第 6 章 的 第 3 节。 js 的原型链继承其实并没有什么魔法。 0 js 里面的每一个函数都有一个叫做 prototype 的属性,即原型属性,这个属性指向的对象即为这个函数的原型对象,原型对象就是普普通通的对象而已 1 js 里面的一个对象可以通过 new 操作符 作用于一个‘构造函数’得到,‘构造函数’与普通的函数并没有区别,只是概念上 /功能上的区分 2 由‘构造函数’创建得到的对象,其内部会自动有一个指向其‘构造函数’原型对象的指针,也就是说‘构造函数’和其创建的对象都会有一个属性指向‘原型对象’(原型对象仅仅只是一个普通的对象,它由‘构造函数’的 prototype 属性来指定或进行修改),也就是说 js 里面的每一个对象内部都有一个指针指向一个原型对象,不论这个对象是由 自定义的‘构造函数’ 或是 对象字面量 或是 new Object() 等等方式创建的,每一个对象都会关联一个原型对象。 注意:‘构造函数’ 和 由其创建的对象 是分别指向‘原型对象’的,如果先使用‘构造函数’创建了一个对象 a,再去修改该‘构造函数’的 prototype 属性,将其指向一个新的对象,则这个修改对于对象 a 是不可见的,因为该操作只是切断了‘构造函数’和最初的那个原型对象的链接,而 a 对象还是引用着最初的那个原型对象,a 对象并没有被影响,如果直接修改最初的那个原型对象,例如给它添加一个属性,那个这个操作对于 a 对象是可见的,因为是同一个对象。 3 既然每一个对象内部都有一个指针指向一个原型对象(该原型对象由其‘构造函数’的 prototype 属性决定或进行修改), 而且原型对象也是一个普普通通的对象,那么完全可以再将‘原型对象’设置为由某个‘构造函数’创建的对象,而这个对象内部同样有一个指针指向另一个原型对象,以此类推,便形成了一个‘原型链’,注意:这里有一个递归定义,即 原型对象可以使用类似当前对象的创建方式来进行创建,即 当前对象会有一个指针指向原型对象,而原型对象只是一个简单的对象(可以类别当前对象)它也会有一个指针指向另一个原型对象,而另一个原型对象也只是一个简单的对象,它也会有一个指针指向另另一个原型对象,,,以此类推,便形成了原型链继承。 |
23
JerryY 2020-07-07 09:45:00 +08:00
@rzWack 你指的是第二个栗子吧,画个图就知道了。你创建对象后,那个对象的__proto__指向的是最原始的,接着你修改原型对象,是不会对之前的造成影响的。你之后创建的对象才会受到影响。
|
24
justfindu 2020-07-07 09:53:28 +08:00
这你就需要理解一下实例和原类的区别.
|
25
PineappleBeers 2020-07-07 10:14:11 +08:00
这里面有几个知识点,我就一一列出来,也正好给自己复习一下。
1 、引用类型与基本类型。 2 、js 取值。 3 、原型链的继承。 先了解一下引用类型和基本类型最大的区别: 1 、操作基本类型是直接操作内存空间里面的值。 2 、操作引用类型一般是操作内存地址,将内存地址赋值给别的变量。 function Fn() {}; Fn.prototype = {x: 1}; let fn1 = new Fn(), fn2 = new Fn(); fn1; //Fn(){} fn1.x; //1 fn2.x; //1 这里 new 有四步操作 ①let fn1 = undefined; Object.create({}); //新建一个匿名对象,没有赋值 ②{}.__proto__ = Fn.prototype; {}.__proto__.constructor = Fn; ③{}.call(Fn); ④fn1 = {}; //将匿名对象赋值 然后是取值问题,fn1.x 取值会先从 fn1 这个变量里面去查找有无 x 这个值,没有的话就从它的原型链__proto__上面去查找。很显然,fn1 这个变量自身上没有值,而它的__proto__是等于 Fn.prototype 的,所以它最终找到 x = 1 ; fn1.x = 2; fn1.x; //2; fn2.x //1; fn1; //Fn() {x: 2} fn2; //Fn() {} 当 fn1.x = 2 时,相当于给 fn1 这个变量自身加了个{x:2}的对象,而上面看到 fn1 与 fn2 是两个匿名对象,除了__proto__指向同一个 Fn.prototype 外,他们自己的内存空间是不干扰的。所以 fn1.x 不能影响 fn2.x,他们这个 x 取值的位置都不同,一个是自身,一个是原型链。 Fn.prototype.x = 3; fn1.x; //2 fn2.x; //3 fn1.__proto__.x; //3 这里也就清楚了,如果 Fn 的 prototype 直接修改 x,也就相当于在{x: 1}(原)这块内存空间中直接修改 x 的值。而 fn1.__proto__与 fn2.__proto__指向的还是{x: 1}(原)这块内存空间,所以从 fn1.__proto__和 fn2.__proto__取 x 都变成了 3 。 Fn.prototype = {x: 4}; fn1.__proto__.x; //3 fn2.x; //3 上面提到过,引用类型赋值,变量指向的是内存地址,由于 fn1.__proto__与 fn2.__proto__在声明的时候已经指向了原来{x: 1}(后 x 改为了 3 )这块内存空间,所以 Fn.prototype 指向了新的内存空间也就与 fn1.__proto__和 fn2.__proto__无关了。 |
26
soulmt 2020-07-07 10:30:05 +08:00
其实你还是没理解 原型 和原型链, 跟其他人一样,建议你先看一遍红宝书,理解原型,原型链,继承,还有引用再回头想想
首先先说片段 2 你在第一次 new 的时候 ConSon.__proto__ === Con.prototype 所以在你 Con.prototype= {x:1};之前 修改 Con.prototype 和修改 ConSon.__proto__效果是一样的, 但是你 Con.prototype= {x:1};之后 ConSon.__proto__ 所指的 prototype 其实没变 这其实就相当于 let a = {a:1} let b = a; a = {c:2}; 这段代码的结果就是 a 是{c: 2} b 是{a:1} 与上述同理 你改了 prototype 之后 只能说 Con.prototype 确实变了,但是 ConSon.__proto__其实没变, 所以不会有变化,只有你重新 new 才会用新的 prototype 去初始化 不知道你好不好理解, 这是个好问题,之前确实没有这么思考过。 |
27
ChanKc 2020-07-07 10:31:55 +08:00
@Mutoo 你的文章排版和图片很不错,我尤其喜欢代码块的字体
但是 instanceof 的部分不太对,可能是标准有发生的变化 foo instanceof Foo 可以替换成 Foo[Symbol.hasInstance](foo) 其中[Symbol.hasInstance]是 Function.prototype 上的方法 所以除了考虑“追溯”原型链,还要看 instanceof 右值是不是一个函数 |
29
liberty1900 2020-07-07 11:59:25 +08:00
@Jirajine 老哥稳,这篇文章才读了没几段,就豁然开朗. 这个问题一句话就能解释, "F.prototype only used at new F time", 也就是 prototype 在 new 的时候已经确定,之后对 F.prototype 的改动不会对已经 new 过的对象产生影响,只会对未来 new 的对象生效
|
30
liberty1900 2020-07-07 12:03:21 +08:00
@liberty1900 我这里说的改动不是类似 F.prototype.x = 1 这种引用改动,而是 F.prototype = {x: 1}这种 reassign
|
31
chenliangngng 2020-07-07 12:46:12 +08:00 via Android
JavaScript 中有一种奇怪的行为一直在被无耻地滥用,那就是模仿类------《 You don't know JavaScript 》
|
32
rzWack OP 看了诸位大佬的解释和推荐的链接,我觉得我懂了。现在说一下我的理解:
对于片段 1,先 prototype={..} 覆盖了原有(默认)原型,再 prototype.x=.. 时,是基于 prototype={..} 的地址引用改动,无论怎么修改 FooSon.__proto__ 指向的 Foo.prototype 都是 Foo.prototype={..}这一步的地址,所以 FooSon 能够动态获取到原型链上的值。用官方一点的说法是: “继承原型属性的实例总是能够获取最新值” 。 对于片段 2,先 prototype.x =.. 是基于原有(默认)原型上的引用改动,再 prototype={..}覆盖原型,此时地址发生了变化。对于 CooSon.__proto__ 恒指向的是 初始化时原型(的地址),而不是覆盖后的地址导致后续无论怎么修改 Coo.prototype, CooSon.x 值恒定不变。用官方一点的说法是: “用新对象替换 prototype 属性不会更新以前的实例” 。 而在后面实例化的 QonSon 实例化时,QonSon.__proto__ 指向的是在 QonSon 实例化之前的 Coo.prototype 的最新值,所以 QonSon.x =1 。引用 29 楼大佬 @liberty1900 的话,即实例化对象的 __proto__ 属性的值(个人更倾向于内存空间的地址)在 new 的时候已近确定。后实例化的对象自然继承的就是原型链上最后一次修改的值。 如果理解有误,希望大家指正一下。 |
33
xoyimi 2020-07-07 16:22:40 +08:00
跟着复习了一下,有了新的进步,耐思
|