JS学习笔记--原型和原型链

218 阅读4分钟

一、全局对象 window

ECMAScript 规定,全局对象叫做 global,但是在浏览器中,全局对象叫做 window,window 的属性就是全局变量。

全局变量分为两种,一种是ECMAScript标准规定,如:

  • global.parseInt
  • global.parseFloat
  • global.Number
  • global.String
  • global.Boolean
  • global.Object
  • ...

还有一种是浏览器私有的:

  • window.alert
  • window.prompt
  • window.confirm
  • window.console.log
  • window.console.dir
  • window.document
  • window.document.createElement
  • window.history
  • ...

MDN 中详细列出了所有 API 的资料,本文主要介绍一下 ECMAScript 标准中的全局变量,以及一个重要的概念——原型

二、全局函数

JS 中常见的全局函数有Number、String、Boolean、Object等等(实际是window.Number,使用全局属性时,可以省略前面的window或global),我们以Number为例,介绍一下其用法。

首先,Number函数可以将其他类型的数据转为number类型:

Number('1')  //1
Number('abc') //NaN
Number(true) //1

Number函数还可以声明一个Number对象实例:

var n1 = new Number(1)

此时n1为一个Number对象,具有一些属性和方法,如:

n1.toString()  //'1'
n1.valueOf()  //1

这里有一个需要注意的问题,在JS中,即使只是声明了一个number类型的量,也可以直接使用Number对象的方法。但是,number又不是对象,为什么会有属性或者方法呢?

var n2 = 1
n2.toString()  //'1'

这其实是JS的一种“妙计”,在对 number 类型的 n2 使用 toString 方法时,JS引擎会生成一个临时的 Number 对象,我们假设其为 temp,即:

var temp = new Number(n2)

之后在调用n2.toString()时,实际上是在调用temp.toString(),并将temp.toString()的返回值作为n2.toString()的返回值。

需要注意的是,在上述过程执行完毕以后,temp 会被“抹杀”掉,就像temp从来没存在过一样

var n = 1
n.xxx = 2
n.xxx  //undefined

以上代码中,在运行n.xxx = 2时,实际是在运行temp.xxx = 2,随后temp被消除,此时n.xxx就会返回undefined

三、原型

前面说到,Number、String、Boolean等全局函数,会创建一个相应的对象实例,每个对象都有相应的属性和方法,然而这些方法中,总会有一些是所有对象都有的。如果每创建一个对象,都要把这些共有的方法创建一遍,势必会造成内存浪费。

为解决这个问题,JS中每个对象都有一个__proto__属性,__proto__也是一个对象,指向该类对象的共有属性。

例如,我们先声明一个Number对象实例

var n = new Number(1) 

n有一个__proto__属性,其中包含关于数值的一些方法,如toFixedtoExponential等,此外n.__proto__中还有一个__proto__属性,其中包含所有object类型的共有属性,如valuaOftoString等。(n.__proto__.__proto__下其实还有一个__proto__,只不过它的值为null,没有意义。)

n.__proto__  //Number类对象的属性
n.__proto__.__proto__  //Object类对象的属性
n.__proto__.__proto__.__proto__ //null

可以看到,上面的__proto__是一层嵌套一层,呈一种链式结构,称为原型链。

当我们调用n的某个方法时,如n.xxx,JS引擎会先查找n中是否有xxx这个属性,如果没有,就在n.__proto__继续查找,如果还没有,就继续在n.__proto__.__proto__中查找,直到原型链上所有的__proto__均被找完,如果依然没找到,就会返回undefined。

对于String、Boolean等其他类型的对象,情况类似,这里不再赘述。

这样以来,就解决了内存浪费的问题。但是,JS中没有被引用的对象,会被当做垃圾回收掉,所以还需要有一个变量去引用它,才能保证随时可以调用这些共有属性,这就引入了原型(prototype)的概念。

浏览器在启动后就会创建全局对象window,window对象里有Number、String、Boolean、Object等函数(对象),称为原型对象,原型对象中还有个属性叫做prototypeprototype指向的就是那些共有属性,如此,就保证了共有属性的存在性。

具体来说,prototype和前述的__proto__有如下关系。

var n = new Number(1)
var s = new String('1')
var b = new Boolean(true)
var o = new Object({})
n.__proto__ === Number.prototype  //true
s.__proto__ === String.prototype  //true
b.__proto__ === Boolean.prototype  //true
n.__proto__.__proto__ === Object.prototype  //true
s.__proto__.__proto__ === Object.prototype  //true
b.__proto__.__proto__ === Object.prototype  //true

以上结论可以简要概括为:

var 对象 = new 函数
对象.__proto__ === 对象的构造函数.prototype

需要注意的是,function 类型的对象同样也是由 Function 构造的,即:

var f = new Function()
f.__proto__ === Function.prototype  //true

由于所有函数都是由 Function 构造出来的, 所以Number、String、Boolean、Object也不例外,亦由 Function 构造,他们是 Function 的实例

Number.__proto__ === Function.prototype  //true
String.__proto__ === Function.prototype  //true
Boolean.__proto__ === Function.prototype  //true
Object.__proto__ === Function.prototype  //true

总而言之,NumberStringBooleanObjectFunction,这些都是原型对象,它们的属性prototype就是原型,都是浏览器启动时就创建好的。与其相应的对象实例nsb等,都是由这些原型函数所构造,其__proto__指向相应原型对象的prototype