可能你听说过原型和原型链,但是MDN的解释对于初学者来讲又很难理解。下面我介绍一下自己的理解,可能有失偏颇,敬请谅解。
目录
全局对象Window
ECMAScript规定全局对象叫做global,而在浏览器中称全局对象为window(浏览器先存在)
ECMAScript规定有以下常用属性:
parseInt
parseFloat
Number()
String()
Boolean()
Object()
浏览器自己添加以下常用属性:
alert //弹框
promit //用户填写
confirm //弹框确认
console.log() //开发者打印东西
document //文档,与DOM有关
history //浏览器下命令,与BOM有关
window对象中的方法可以省略window,例如window.alert()方法可以写为alert()方法,比较简便。
浏览器下添加的属性,在每个浏览器下效果可能会不一样,因为他们没有统一的标准。
简单类型与对象的区别
//声明简单的数据类型
var n1 = 1;
//声明一个对象,可以用n2.toString()将其转换成字符串
var n2 = new Number(1);
他们两者的存储方式不同。n1是简单声明的数据类型,直接存放在Stack内存中。n2是声明了一个对象,Stack内存中只存放它所指向的Heap内存地址,Heap内存中存放内容。
- 声明对象除了存放内容,还存放了一些公共属性,如:toString()、valueOf()等。这些在我们接下来的文章中有提到。
Number对象
Number的常用属性 | 含义 |
---|---|
Number.valueOf() | 获取对象的值 |
Number.toString() | 转换为字符串 |
Number.toFixed() | 转换为小数 |
Number.toExponential() | 转换为科学计数法 |
String对象
String的常用属性 | 含义 |
---|---|
String.charAt() | 获取数组中某一位置字符 |
String.charCodeAt() | 获取数组中某一位置字符的Unicode编码 |
String.trim() | 去空格 |
String1.concat(String2) | 连接字符串 |
String.slice(start,end) | 切片 |
String.replace(‘e’,’o’) | 替换对应的字符 |
String.indexOf() | 查询字符是否在字符串中 |
String.split() | 分隔 |
String.substr(start[, length]) | 截取某一部分字符串 |
Boolean对象
var f1 = false
var f2 = new Boolean(false)
if(f1) { console.log('1') }
if(f2) { console.log('2') }
f1 === f2 //false
注意:f1并不等于f2,因为f2是一个对象。一切对象都是的布尔值都是true。只有6个falsy值:
0 NaN '' null undefined false
Object对象
var obj1 = {};
var obj2 = {};
obj1 === obj2; // false
obj1不等于obj2的原因:
因为它们存放的地址不同,所以两个对象是不相等的。
原型(公共属性)
什么是原型?
所有对象都有 toString
和 valueOf
属性,那么我们是否需要给每个对象单独添加这些属性呢?答案是否定的,因为这么做将会占用大量的内存。
JS解决的方法:
设计一个隐藏的属性:__proto__
,来存放公用属性,它的值就是公用属性的值,它指向存放公共属性的地址。当你用一个类型调用公用属性(如:toString)时,如果不是对象就包装成对象,做一个临时的转换。如果是对象的话,就看有没有想要调用的,对应的属性。如果有,就调用。如果没有,就去proto属性去查看有没有,有就去相应地址去调用对应属性。
proto 与 prototype 区别
我们创建一个对象时,创建的对象的__proto__
属性会指向对应浏览器所创建的类型对象
。这时我们就可以调用浏览器所创建的类型对象
的共有属性。
__proto__
是对象共有属性的引用。目的是为了引用浏览器所创建的类型对象
的共有属性。prototype
是浏览器所创建的,本身就存在。和__proto__
用法上没有区别,只是形式上有区别。prototype
是浏览器亲爹生的,__proto__
是用户创建的对象产生的。prototype
存在的意义是为了防止对象的共有属性没有被调用而被垃圾回收,导致浏览器的崩溃。
对象调用原型的流程
另外还有一些单独类别才有的公共属性,如number类型有toFixed属性,而string类型没有该属性。那么怎么把每个类型的公共属性区分开呢?
将每个类别共有的放在一起,独有的属性分别赋予类别?答案是将其类型先指向number共有属性(它独有的属性),number共有属性再添加proto属性,地址再指向公共属性。拿number类型的tofixed()和valueOf()属性为例:
- 他先从对象的属性里找,找不到。
- 再去number共有属性里的proto属性里找,这时候找到tofixed()属性,返回。
- 找不到valueOf()属性,再去Object公共属性里找。这样总共3个步骤。
总结:
- 每个类型共有属性(String共有属性,Number共有属性,Boolean共有属性)都有属于自己独特的属性。
- 变量的属性先调用自己类型的共有属性,如果找不到,通过
__proto__
属性指向的地址,调用object共有属性。 - 普通对象直接调用object共有属性。
- object共有属性里没有
__proto__
属性。
原型链
概念:
每个实例对象object
都有一个私有属性(称之为__proto__
)指向它的原型对象prototype
。该原型对象也有一个自己的原型对象__proto__
,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
简单来说,原型链就是对象一直指向它的原型对象,直到原型对象为null。就像一条链子,每个共有属性都是链子上的节点。
也可以这么理解,变量的__proto__
私有属性指向的就是它的原型对象prototype
如: number.__proto__.__proto__ === Object。prototype
变量在调用属性的时候,会通过原型链一层层找原型对象里的对应属性,如果找不到则undefined。
其实原型链就用到了数据结构里的树。私有属性__proto__
指向对应树的节点。
在没有写任何代码的情况下,浏览器初始化原型对象。
String.prototype是 String 的公用属性。 s.__proto__
是String的公用属性的引用。
prototype为了防止垃圾回收,__proto__
为了引用属性。
一些烧脑的等式
通过var 对象 = new 函数;推出其他烧脑的等式
var n = new Number(1)
//var 对象 = new 函数
//对象的__proto__指向某对象的共用属性,构造某对象的函数的prototype也指向某对象的共用属性
//__proto__ 是对象的属性,prototype是函数的属性
对象.__proto__ === 函数.prototype
//函数的prototype是对象,这个对象对应的就是最简单的函数Object
函数.prototype.__proto__ === Object.prototype
//由于函数本身即是函数(最优先被视为函数),也是对象,而函数的构造函数是Function
函数.__proto__ === Function.prototype
//Function即是对象,也是函数,但他优先是个函数
Function.__proto__ === Function.prototype
//Function.prototype也是对象,是普通的对象,所以其对应的函数是Object
Funciton.prototype.__proto__=== Object.prototype
通过推导,我们可以知道Function即是函数,也是对象。Function.prototype
与Funciton.__proto__
互相引用。
所以得出结论,Function是Object的构造函数。
Object.__proto__ === Function.prototype
,
参考资料
https://www.jianshu.com/p/dffeae0f1316
by 《你真的完全理解原型与原型链?》—— 区家乐