原型与原型链

3,331 阅读5分钟

一、全局对象 window

ECMAScript 规定全局对象叫做 global,但是浏览器把 window 作为全局对象(浏览器先存在的),window 就是一个哈希表,有很多属性。

window 的属性就是全局变量。这些全局变量分为两种:

  1. 一种是 ECMAScript 规定的:

    • global.parseInt
    • global.parseFloat
    • global.Number
    • global.String
    • global.Boolean
    • global.Object
  2. 一种是浏览器自己加的属性:

    • window.alert
    • window.prompt
    • window.comfirm
    • window.console.log
    • window.console.dir
    • window.document
    • window.document.createElement
    • window.document.getElementById

二、全局函数

  1. Number
    • Number(1)传给它一个东西变成数字
    • var n = new Number(1)创建一个 Number 对象
    • 1new Number(1) 的区别:内存不同

n1 在控制台打出的结果就是 1,n2 实际上是一个 hash,可以看到有好几个方法函数。

这个地方就有个疑问了,n1 其实也可以使用 toString() 方法,那干嘛还需要 new Number() 呢?

这里就要说到历史了,Brendan Eich在发明 js 的时候被公司要求JS要像Java,所以他模仿javavar n = new Number(1);申明 Number。 这样 n 是一个对象,有自己的属性,但太麻烦,于是他又写了一种 var n =1; 但是有个缺点,这样的话就没有 toString() 方法,基本类型是没有属性的。又想用简单的类型,又想用对象的那些 toString() 方法,于是他想了一个妙计,做一个临时的转换。首先声明一个临时的对象 temp = new Number(n),也就是说 temp 是 n 的一个复杂类型的封装,然后 toString() 实际上是 temp.toString(),然后把 temp.toString() 的值作为 n.toString() 的值。再把 temp 干掉,这个 temp 就好像没有存在过一样。但是它临时把 toString() 给弄过来了。

var n=1;
n.toString(); //n用toString时,本质是下面这两行,实质n是没有属性的

var temp = new Number(1); //声明了一个临时变量temp,并将n作为Number对象赋给它
temp.toString(); //用temp来执行toString,执行完后temp就被抹杀了

n.xxx = 2; //可以的
n.xxx //undefined,因为xxx是存在temp的,上一句的temp执行完后就被抹杀,所以xxx也被抹杀了,再去调的时候又是一个新的临时对象

  1. String
    • 声明的形式一种是 var s = 'sldkjslkdjskldj'
    • 另一种是 var s2 = new String(s)
    • 它们的区别是第一种是基本类型的 string,直接存在 Stack(栈内存),第二种是把它变成了对象之后的 hash
var s = 'abc'; // 原理和 Number 一样

var s2 = new String(s);
s2[0]   // 'a',因为 String() 对象有 hash 的属性
s[0]    // 'a',s 也能调用这个属性,但 s 实质没有属性,这个原理和上面 Number 一样

s.charCodeAt(0)    // 97,a 的十进制 unicode 码
s.charCodeAt(0).toString(16)    //'61',a 的十六进制 unicode 码

' abc '.trim()    // 'abc',去掉字符串两边的空格

var s1 = 'Hello'
s.concat(s1)    // abcHello,连接两个字符串
s1.slice(0,2)    // 'He',slice 是切片,切下 0 到 2 之前的内容,包前不包后
s1.replace('e','o')    // 'Hollo',替换(注意 S1 还是原来的 S1,replace 是得到一个新的字符串 'Hollo' )
s1.length   // 5
  1. Boolean
    • 声明的形式一种是 var b = true
    • 另一种是 var b2 = new Boolean(true) 创建一个 Boolean 对象
    • 不加 new 是用来做转换用的,new 是用来生成对象的
var b = false
var b1 = new Boolean(false)
if(b){console.log('true')}
if(b1){console.log('true')}     // true, 只会输出 b1,因为所有对象都是 true
  1. Object
    • var o1 = {}
    • var o2 = new Object()
    • o1 和 o2 没区别,但并不是相等的
o1 === o2   //false,因为比较的是内存地址,而地址不同

三、公用属性(原型)

所有对象都有 toStringvalueOf 属性,那么我们是否有必要给每个对象一个 toStringvalueOf 呢?

明显不需要,JS 的做法是把 toStringvalueOf 放在一个对象里(暂且叫做公用属性组成的对象), 然后让每一个对象的 __proto__ 存储这个「公用属性组成的对象」的地址。

怎么知道 o1 可以 toString() 呢?当在写 o1.toString() 的时候,首先看 o1 是不是对象,如果不是对象就先包装成一个对象,做一个临时的转换。如果是对象的话,就去看有没有 toString() 这个 key。当发现没有,然后就进入公用属性看看有没有,那有于是就调用这个 toString()

同时 Number 对象还有它特有公用属性,所以它比 Object 要多一个公用属性库,并且会先访问 Number 的原型(公用属性库)。

同理,StringBoolean 也有自己的公用属性,都会先指向自己的公用属性,再指向 Object 的公用属性,Object__proto__ 指向 null,所以说 Object 的公用属性是所有对象的公用属性。

什么是原型链呢?

如果我们看上面其中的一条线,S1 它有自己的属性,然后它作为 String 的公用属性是在这条线的上一个节点。如果还要再找呢,就是 String 作为对象,因为 String 也是对象(我们可以把一个字符串转换成对象),如果把它看成一个对象的公用属性再往上找。所以这一条线就像个链子一样,链子上面有 3 个节点,这就叫做原型链。

ObjectStringNumberBoolean 的公用属性在哪里?这些东西都不是凭空产生的,是浏览器一开始就把这些给准备好了。这些公用属性都是一个 hash,若没被引用的话会被当作垃圾回收。那谁在引用它呢,这个东西叫做 prototype, 平常对象的原型是 Object.prototype 在引用。

Number.prototype.__proto__ === Object.prototype     //true,是把Object.prototype的地址赋给Number的__proto__属性了
var n1 = new Number(1);
n1.__proto__ === Number.prototype       //true,是把Number.prototype的地址赋给n1的__proto__属性了
n1.__proto__.__proto__ === Object.prototype     //true

__proto__和prototype的区别

打开浏览器就相当于生成了一个 window,里面存了 NumberStringBooleanObject这几个函数,对象里的 prototype 属性里存了它们原型的地址,在你写:

var s = new String('hello');     //就会把 String.prototype 里的地址放入 s 的 __proto__ 属性里

这样用 s 就能调用 String 的原型了,String.prototypeString 的公用属性的引用,s.__proto__String 的公用属性的引用。

总结:

var 对象 = new 函数()   // 函数可以是 Number/String/Boolean/Object
对象.__proto__ === 函数.prototype   //true

__proto__ 是对象的属性,prototype是函数对象的属性

然后可以推出:

函数.prototype.__proto__ === obj.prototype
函数.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
Function.prototype.__proto__ === obj.prototype