可能你听说过原型和原型链,但是MDN的解释对于初学者来讲又很难理解。下面我介绍一下自己的理解,可能有失偏颇,敬请谅解。

目录

  1. 全局对象Window
  2. 简单类型与对象的区别
    1. Number对象
    2. String对象
    3. Boolean对象
    4. Object对象
  3. 原型(公共属性)
    1. proto 与 prototype 区别
    2. 对象调用原型的流程
  4. 原型链
  5. 一些烧脑的等式
  6. 参考资料

全局对象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的原因:

因为它们存放的地址不同,所以两个对象是不相等的。

原型(公共属性)

什么是原型?

所有对象都有 toStringvalueOf 属性,那么我们是否需要给每个对象单独添加这些属性呢?答案是否定的,因为这么做将会占用大量的内存。

JS解决的方法:

设计一个隐藏的属性:__proto__ ,来存放公用属性,它的值就是公用属性的值,它指向存放公共属性的地址。当你用一个类型调用公用属性(如:toString)时,如果不是对象就包装成对象,做一个临时的转换。如果是对象的话,就看有没有想要调用的,对应的属性。如果有,就调用。如果没有,就去proto属性去查看有没有,有就去相应地址去调用对应属性。

proto 与 prototype 区别

我们创建一个对象时,创建的对象的__proto__属性会指向对应浏览器所创建的类型对象。这时我们就可以调用浏览器所创建的类型对象的共有属性。

  • __proto__是对象共有属性的引用。目的是为了引用浏览器所创建的类型对象的共有属性。
  • prototype是浏览器所创建的,本身就存在。和__proto__用法上没有区别,只是形式上有区别。prototype是浏览器亲爹生的,__proto__是用户创建的对象产生的。prototype存在的意义是为了防止对象的共有属性没有被调用而被垃圾回收,导致浏览器的崩溃。

对象调用原型的流程

另外还有一些单独类别才有的公共属性,如number类型有toFixed属性,而string类型没有该属性。那么怎么把每个类型的公共属性区分开呢?

将每个类别共有的放在一起,独有的属性分别赋予类别?答案是将其类型先指向number共有属性(它独有的属性),number共有属性再添加proto属性,地址再指向公共属性。拿number类型的tofixed()和valueOf()属性为例:

  1. 他先从对象的属性里找,找不到。
  2. 再去number共有属性里的proto属性里找,这时候找到tofixed()属性,返回。
  3. 找不到valueOf()属性,再去Object公共属性里找。这样总共3个步骤。

对象调用原型的流程

总结:

  1. 每个类型共有属性(String共有属性,Number共有属性,Boolean共有属性)都有属于自己独特的属性。
  2. 变量的属性先调用自己类型的共有属性,如果找不到,通过__proto__属性指向的地址,调用object共有属性。
  3. 普通对象直接调用object共有属性。
  4. 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.prototypeFunciton.__proto__互相引用。

所以得出结论,Function是Object的构造函数。

Object.__proto__ === Function.prototype

参考资料

https://www.jianshu.com/p/dffeae0f1316

by 《你真的完全理解原型与原型链?》—— 区家乐