目录:

1.一点关于内存的知识2.原型对象

如果文章里哪里是我理解错误或者表达不严谨的地方,大家可以指出来,共同进步~

(这篇笔记我估计废话很多,重复的废话尤其多。。。)

一点关于内存的知识

当我们声明一个变数并赋值后,值究竟是如何存储在内存里的呢!

下面举两个例子:(1)值为数值的时候(2)值为对象

(1)数值

var num = 1;

上面声明了一个变数,变数名是num,赋值为数字1,在数据类型7大类型里属于数值(number)。

下面画一个图简要说明它在内存中存储数据的方式。

我们把变数num值修改一下:

num = 2;

此时它在内存中储存的内容是这样的

再声明一个变数num2,它的值等于num

var num2 = num;

因为此时num的值为2,所以数字2就被复制给了变数num2,现在变数num和变数num2的值都是一样的。

这个时候有两种情况

① 仅仅是把数字2拷贝给了变数num2,拷贝完后num和num2不再有任何关系,也就是不管变数num还是num2,就算下次再把值改成其他数字,也不会影响到另一个变数

② 只要其中一个变数(num或者num2)修改自己的值(比如把2改成数字3,或者字元串『gdfg』之类的),另一个变数的值会自动变成和它的修改以后的新值一样。

上面这两个情况先不管,这次我们换声明一个对象。

var eg = {
name: a,
age: 1
};

多声明几个对象之后,看起来差不多像这样

每创建一个对象,都会在堆中重新创建一个空间存放数据,栈里保存的是指向栈的具体地址,通过这个地址,我们可以找到这个对象的数据。

现在删掉对象a和b,创建一个对象c,并把对象eg的值赋给c

var c = eg;

对象eg拷贝给对象c的值不是存放在堆中的这部分内容

不是这个

,而是把保存在栈中的地址,也就是指向堆里存储数据的那个地址拷贝给了c,现在c在栈里存放的地址和eg存放的地址一样,他们指向的是存放在堆里的同一个对象。

直接把对象eg赋值给对象c的结果就是,任何一方如果改变堆里的信息,另一方由于栈里的地址指向的是堆的同一个地址,所以另一个对象的值也会跟著改变。

我们改下对象c里的age属性看一下效果。

c.age = 11;

因为都是引用的同一个地址,所以只要堆里的信息更改后,引用这个地址的所有对象的属性的值都会发生变化。

像之前的两个变数(值为数字1的变数num和变数num2),就没有这种特点。

现在把num2的值改为20,看num的值会不会像上面那样受到影响。

结果是另一个变数没有受到任何影响,是因为num被赋值给num2时,仅仅是把数字1拷贝给了num2,变数num2和变数num本身并没有关系,所以再次修改值以后,也不会影响到另一个变数。

总结:上面的两种情况,一种是对象直接赋值给另一个对象时,因为拷贝给另一个对象的值是栈里保存的地址,这个地址指向堆里对应的对象数据,由于两个对象保存的地址相同,任意一方修改堆里的数据时,另一方的值也会跟著改变,这种双方会受到影响的地址拷贝称为【浅拷贝】;另一种拷贝的是保存在栈里的值,比如数字1,数字2等等,只是把数字多复制了一份给它,所以两个变数之间没有那么紧密的关系,就算其中一方再次修改自己的值,也不会影响到另一个变数,这种互不影响的拷贝称为【深拷贝】。

原型对象

比如你有这样一个奇葩的工作,你的老板家里有很多只猫,每当一只猫从你面前路过,

你就需要用Cat这个构造函数声明一个对象,并给这个函数传入一个参数。

这个是Cat构造函数的代码

这时候你发现来了一只红色猫咪,你立马用Cat构造函数声明了一个对象,并传入参数 red

不到一会又来了几只猫,你按照老板的指示继续声明对象

每当你声明一个对象,对象中的属性和方法就会在内存的堆中创建一份数据,如下图

通过使用严格相等运算可以看出,每个对象的数据在堆中的地址是不同的,因为他们各自都会单独复制一份保存在堆中

(对象和对象使用严格相等符号的时候,对比的不是内容,而是地址是否一样,地址一样代表引用了同一地址的数据。这里false代表了他们的地址是不同的,虽然sayHello函数的内容都是一样的,但还是各自在堆中不同的地方生成的数据。)

这是很浪费空间的,为了节约空间,我们可以重新建一个Cat构造函数。

(JavaScript 继承机制的设计思想是,原型对象的所有属性和方法,都能被实例对象共享。每个构造函数都有prototype属性,构造函数的prototype属性指向的就是原型对象。当构造函数生成实例对象的时候,实例对象会自动继承原型对象里的属性和方法。构造函数的prototype属性保存的是原型对象的地址,地址指向堆中的原型对象。不像深拷贝每声明一个对象,实例对象上就会生成一份sayHello方法。这个属于浅拷贝,里面的函数都是大家共有的,可以达到节约空间,共享属性的目的。)

构造函数创建完毕后,我们重新声明两个对象,再用严格相等运算符=== 看下这两个对象的sayHello指向的地址是否是同一个

结果为true。这说明,所有用Cat构造函数创建的实例对象,他们sayHello的地址所指向的对象都是同一个(指向同一个内存地址),这属于浅拷贝,表示大家都是引用的堆中同一个对象,对象里的属性就是大家共有的属性,而不是用深拷贝复制对象作为自己私有的属性。根据之前的例子我们知道,如果把大家都在引用的拥有共同属性的对象的内容修改了,那么所有实例对象的sayHello对象都会受到影响。

所以我们可以把那些不需要做修改的属性和方法都写在构造函数的prototype里,当用这个构造函数声明一个实例对象的时候,这个实例对象的_proto_属性和构造函数的prototype属性指向的是同一个原型对象。

如果是需要修改的一些属性和方法,由于浅拷贝的特点,为了避免影响到其他实例对象,可以把这些属性和方法直接写在构造函数里,当用这个构造函数声明一个实例对象时,该实例对象会用深拷贝的方式拷贝到自己的对象中,而不是大家都引用同一个地址的原型对象,这样在修改的时候,就能避免其他实例对象受到影响了。

(上面提到一个实例对象的_proto_属性,如果知道实例对象中这个属性的请跳过下面这部分内容,直接跳到这部分内容的分割线后面~)

实例对象中的_proto_属性:

这里我用Number构造函数创建一个实例对象

刚刚我创建实例对象的时候什么属性 / 方法都没有加,输出a; 发现有个折叠起来的Number对象,打开这个Number对象,里面有个_proto_属性。

再打开,出现了很多可以调用的函数,这些函数看著倒像是Number构造函数的prototype属性里自己定义的函数。

我们看一下构造函数Number.prototype指向的那个原型对象里共有的属性有哪些

看著和实例对象_proto属性里的内容一样啊!为了严谨,我们用严格运算符证明一下。

用严格运算符之前,首先用typeof这个函数检测一下这两个属性是否是对象。

(如果双方都是对象,用严格运算符进行比较的时候,只有当【实例对象proto_属性的地址】和【Number构造函数的prototype属性的地址】指向的是【同一个原型对象】时结果才为true。)

可以看出这两个都是对象

两个对象相等,说明他们引用的是同一个地址的原型对象

故:

实例对象._proto_ === 其构造函数.prototype


记住下面这4句话:

1.一切函数都是Function构造函数的实例 !

2.一切对象都是Object构造函数的实例 !

3.实例对象的__proto__属性和其构造函数的prototype属性指向的是同一个原型对象(存的是同一个内存地址)

4. __proto__说的是实例对象和原型对象之间的关系,prototype说的是构造函数和原型对象之间的关系!


根据第1句话可以得出,Object这个构造函数(构造函数也是函数)是Function的实例对象;加上第三句话可以得出,Object.__proto__ === Function.prototype;

Function本身是构造函数,但Function.prototype是一个对象,按照第2句可以得出,Function.prototype就是Object这个构造函数的实例对象;再根据第三点得出,Function.prototype.__proto__ === Object.prototype;

Function是构造函数,一切函数都是Function这个构造函数的实例,构造函数Function也是函数,所以它是构造函数Function的实例对象,由于实例.proto指向其构造函数的prototype,因此,Function._proto_ === Function.prototype。

请看下面这张图

①是Number构造函数,②是①的实例对象a,由于

所以①和②都指向了③这个原型对象。又因为原型对象里也能引用其他对象,加上一切对象都是Object的实例对象,所以Object是Number.prototype这个实例对象的构造函数,根据上面第三句,得到Number.prototype._proto_ === Object.prototype;

注意:Object.prototype._proto_就是尽头了,为null;

关于原型对象里共用属性的调用:

比如我声明了一个对象num,传入的参数是数字1,对象num是构造函数Number的实例对象,除了传入的参数1,我没有给num这个对象加入其他属性和方法。

不过由于实例对象num会继承构造函数Number的prototype指向的原型对象,所以还是可以用这个原型对象中的属性,比如toExponential函数。

调用对象某个函数的时候,js引擎会先看这个实例对象本身的属性里有没有这个函数,如果没有,再去原型对象里面找,如果没有,就去原型对象里的原型里去找,也就是一层一层的进入实例对象的_proto属性里找,找到为止,或者直到Object.prototype.__proto_为null的这个环节结束。这样,就形成了一个原型链。

最终可以了解到Function 和 Object 的关系: (图来自:[学习笔记] 小角度看JS原型链)

参考:

[学习笔记] 小角度看JS原型链?

segmentfault.com

对象的继承?

wangdoc.com

javascript王国的一次旅行,一个没有类的世界怎么玩转面向对象??

mp.weixin.qq.com


推荐阅读:
查看原文 >>
相关文章