浅谈JS原型
栏目: JavaScript · 发布时间: 5年前
内容简介:JS原型学习笔记,如有错误,还请留言~~在谈window之前,试想一个简单的问题,打开浏览器在控制台输入这里需要注意的是,浏览器中的全局对象是window,但是在其他的环境下,例如node.js就不一样了。浏览器下,在控制台输入
JS原型学习笔记,如有错误,还请留言~~
- 全局对象window
- 什么是原型
- JS中一切皆为对象?
全局对象window
在谈window之前,试想一个简单的问题,打开浏览器在控制台输入 console.log("Hello");
并按下回车键,我们理所应当看到了控制台给我们返回的结果 Hello
,那么, console.log()
这个方法是怎么来的呢?其实浏览器中已经为我们内置了一个全局对象叫做window,window作为全局对象,代表脚本正在运行的浏览器窗口。 console.log()
是一种简写的格式,更加精确的写法为: window.console.log()
。window下则封装了多种 多样的属性,这些属性中,有些是浏览器自带的属性,有些则是ECMAScript标准规定的属性。ECMAScript规定的全局对象为global,但是在浏览器下则是window。
这里需要注意的是,浏览器中的全局对象是window,但是在其他的环境下,例如node.js就不一样了。浏览器下,在控制台输入 window
可以查看全局对象的所有属性。而在node.js环境中,则需要使用 global
,且浏览器中规定的属性在node.js环境下自然是无效的。
当我们打开浏览器,系统自然会为浏览器分配内存,浏览器将内存分给各个页面,页面中的HTML,CSS,JS,插件等会分配到一部分内存空间。对于JS来说,一开始window对象就已经存在于堆内存中了。
什么是原型
先上图:
这是堆内存中window全局对象的部分内存模型图,红色框部分为函数,黑色框部分为prototype对象,先不管函数中的prototype属性以及对象中的__proto__属性的含义。我们先声明一个对象,并调用toString()
方法,看一看内存中到底会发生什么。
var obj = new Object(); 复制代码
我们看到我们在声明了一个空对象之后,内部自带了一个__proto__属性,且使用命令: obj.__proto__ === Object.prototype
结果会返回 true
。
那么也就是说:
obj对象的__proto__属性指向了Object.prototype这样一个对象。我们来看一看Object.prototype里面有什么。
在Object.prototype中的toString属性指向一个函数,恰恰是我们想要使用的toString()这样一个方法。现在我们在大概可以明白:在我们声明一个对象obj时,这明明是一个空对象,但是这个对象却可以调用toSting(),valueOf()等方法,是因为在我们声明对象时,浏览器为我们这个对象添加了一个属性 __proto__
,这个属性指向了一个 Object.prototype
这样一个对象,Object.prototype对象中有着Object所共有的一些属性。其实这个prototype就是原型,比起原型,我更喜欢叫它共有属性对象。那么 __proto__
是什么呢? __proto__
是浏览器自动赋予 对象 的一个属性,这个属性指向着一个函数的原型,当然最后的root必然是Object.prototype。
例如:
var n = new Number(15); n.toString(16);// "f" 复制代码
上述代码的含义是,将数值类型的n转化为16进制后,再将其转化为字符串,15在16进制中对应的值为f,转化为字符串之后的结果为"f"。很显然,Object原型中的toString()方法是没有办法将一个值按照进制转化,再变为字符串的,也就是说,Number类型的toString(),不同于Object原型的toString()方法。倘若是Java,我们会想到方法的重写,但是JS则不一样,我们继续从内存模型的角度来分析到底发生了什么:
首先 var n = new Number(15);
n为一个对象,如果不明白为什么n的值会在堆内存中,可以参考我的文章JS内存模型。我们可以看到,对象n的 __proto__
指向了Number.prototype,在Number.prototype中有什么呢?
我们可以看到,Number.prototype也有一个toString的属性,指向了toString()这样一个函数。并且
Number.prototype.toString === Object.prototype.toString
返回的结果为false,也就是说,Number.prototype中的toString是一个“重写”的属性,当我们声明了对象n,并调用toString()方法时,首先,浏览器会在n这个对象中寻找toString这个属性,如果没有它就会在n这个对象的 __proto__
属性所指向的原型中寻找, n.__proto__
指向了 Number.prototype
。 因为Numebr.prototype即Number原型也是一个对象, Number.prototype.__proto__
则指向了root即 Object.prototype
。如果在Number原型中没有toString这个属性,那么顺其自然地就会在Object原型中寻找这个属性。当然本例中,在Number的原型中 Number.prototype
找到了toString这个属性,自然会调用它所指向的Number独有的toString()方法。刚刚描述的过程中,这种指向的关系好似 链表 ,而在JS中,我们可以形象地称作为“ 原型链 ”。
接下来,我们再思考一个问题: 函数是对象吗 ?虽然 typeof
一个函数返回的结果为“function”,但是我们一再强调JavaScript里的数据类型只有七种,无论是数组还是函数,它们的本质都是对象。既然明确了函数是一个对象,那么在上文中,我曾经暗示prototype是一个函数所拥有的属性,__proto__是一个对象所拥有的属性,那么函数既然是对象,那么换言之,一个函数中自然拥有两种属性了。我们再将内存模型图完善一些:
Funcion.__proto__ === Funciton.prototype
。 这可以形象地理解为:自己造自己,自己赋予自己了属性。其实理解不了上图也无关紧要,我们只需要记住原型里面的一个规则即可:
内置函数.__proto__ === Function.prototype; 复制代码
一切皆为对象?
JS中一切皆为对象?很显然这句话是错的,用最简单的代码就可以证明:
var n = 15; typeof n;// "number" 复制代码
JS中,有七种数据类型,在上面的代码中,我们声明了 var n = 15;
,在使用 typeof n
时,我们也可以看到返回的类型是 number
。但是,我们却可以这样做:
n.toString(16);// "f" 复制代码
和上面介绍的 var n = new Number(15)
声明n的方式不同。n的typeof 返回的是一个“number”,如果使用 var n = new Number(15)
声明n,那么使用 typeof n
返回的结果就会是“object”。在声明一个对象时,浏览器会为这个对象自动添加 __proto__
这样一个属性指向原型,好调用相应的方法,既然我们声明了 var n = 15;
且可以调用toString()这样一个方法,那不还是说明n实际上是一个对象吗?实际上,并不是这样的,n自然是一个number类型的数字,只不过这里面另有蹊跷。
var n = 15; n.toString(16); 复制代码
当我们调用toString()方法时,在堆内存中会生成一个临时对象,我们暂时叫它temp。也就是说这个过程是这样的:
var n = 15; // 调用toString()方法时,会在堆内存中产生一个临时变量temp // var temp = new Number(n); // 将temp.toString(16)的结果记录下来 // 临时变量temp随即被"抹杀掉" 复制代码
一个数值型,字符串型等普通类型的变量可以调用原型中的方法,并不能说明它们类型的本质是对象,因为“创建临时变量机制”,让许多人对此有了误解,实际上数值型即是数值型,字符串型是字符串型,JS当中一切皆对象很显然是一个谬论。再看一个例子:
var a = 1; // 问:执行此条语句 a.xxx = 2 是否会执行成功? 复制代码
其实只要理解了"临时对象"这个概念,就不难回答这个问题,在声明 var a = 1;
后,我们已经确定了a的类型是Number,在执行语句 a.xxx = 2;
时,在堆内存中会生成一个临时对象,对于这个临时对象来说,执行 a.xxx = 2;
就相当于添加了一个属性xxx其值为2,所以这条语句自然能执行成功。当然这个临时对象会立刻被“抹杀”,当我们再次在控制台输入a.xxx查看这个值时,返回的结果自然是undefined。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。