JS中的原型和原型链

1. 公用属性(就是原型,prototype)

问题1:每个对象都有toString()、valueof()等方法,但他们本身是没有构造这些方法的,但为什么能使用呢?
答:JS在每个对象里都存了一个隐藏的原型属性:__ proto __(浏览器中可以解读为这个),这个属性指向了存放公用属性的地址(Object.prototype,原型对象),当用toString()时,就是调用的公用属性。

o1.toString === o2.toString   //true

下面我们看一张图:
在这里插入图片描述
当我们创建一个对象O1时,在O1字面量就代表一个存储在栈(stack)中的堆(heap)地址,这个地址指向一个我们所创建的对象。对于这个对象来说, __ proto __ 属性又存储的是Object公有属性(原型)的地址。所以通过O1 . __ proto __,我们可以访问到Object.prototype(原型对象),而在该原型对象中,也有一个 __ proto __属性,该属性指向null。


追问1:为什么Object.prototype. __ proto __ === null呢?
答:我们就拿《圣经》比喻,在耶和华神创造天地以前就只有神,也就相当于此处的null,然后第一天神创造天地,于是就创造了Object.prototype,之后继续创造了其他光、空气等世间万物,就相当于通过Object构造出了无数的其他对象。或许,JavaScript就是一门具有哲学与宗教色彩的语言吧。


追问2:对于 var n = new Number(1),为什么会多出一个原型对象?
答:Object.prototype太大了,是所有对象的公有属性,没法包含某些特定对象的一些方法。就以 Number来说,每个number都有自己的toFixed等方法,我们如果要使用toFixed方法,总不能没创建一个number对象就定义一个toFixed方法吧。于是number对象的 __ proto __ 会先指向Number.prototype的原型对象,如果这里面没有需要的方法,再向上搜索到达Object.prototype原型对象。
上图展示了,String、Number、Boolean、Object对象的搜索过程,而这个搜索过程就实例与原型的链条(原型链).


追问3:那__ proto 和prototype有什么区别呢?
答:
proto __是对象的属性,它是指向公用属性(原型)的地址的;而prototype是函数的属性,就拿O1来说,O1 . __ proto __ === Object.prototype。那这么说Object不就是函数吗?当然,它是的。因为var o1 = new Object()就表明了自己是个构造对象的函数,这就好比var fuck = 1,请问console.log(fuck)是什么?这会输出字面上的意思fuck吗?不会的,同理,Object在字面上意思是对象,但其实它是个构造对象的函数。


追问4:JS有垃圾回收机制,凡是没有被引用的对象就是垃圾,就会被回收,那比如String的公用属性在没写代码之前存在吗?
答:这是存在的,因为在没写代码var s1 = new String()之前,Srting.prototype是String的公用属性的引用,这是浏览器做的工作,为了防止被当作垃圾回收。而我们写代码时就有s1. __ proto __是String的公有属性的引用,这是写了代码才有的,是为了自身使用而产生的引用。

接下来给出一个重要公式:

var 对象 = new 函数()=> “对象.__ proto __ === 函数.prototype"

var number = new Number()
number.__ proto __  === Number.prototype
Number.__ proto __  === Function.prototype  //因为Number是Function的实例

var object = new Object()
object.__ proto __  === Object.prototype
Object.__ proto __  === Function.prototype  //因为Object构造对象函数是Function函数的实例

var function = new Function()
function.__ proto __  === Function.prototype
Function.__ proto __  === Function.prototype  //因为Function是Function的实例,不可思议吧

2. Funtion对象的特别之处

2.1 原型对象的constructor属性

无论什么时候,只要创建了一个函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是指向prototype所在函数的地址。如下图所示:
在这里插入图片描述
由这个图我们也可以看出__ proto __是存在于实例person1和原型对象person.prototype之间的,而不是不是不是实例和构造函数Person之间的。

2.2 typeof xxx.prototype

console.log(typeof Function.prototype)           // function
console.log(typeof Object.prototype)   // object
console.log(typeof Number.prototype)   // object
console.log(typeof Boolean.prototype)  // object
console.log(typeof String.prototype)   // object
console.log(typeof Array.prototype)    // object
console.log(typeof RegExp.prototype)   // object
console.log(typeof Error.prototype)    // object
console.log(typeof Date.prototype)     // object

惊不惊喜,意不意外?除了Function.prototype,其他xxx.prototype都是对象。而且Function.prototype还是个空函数。官方ECMAScript5说:
The Function prototype object is itself a Function object (its [[Class]] is “Function”) that, when invoked, accepts any arguments and returns undefined.
好吧,打扰了。
给出与Function有关的两个结论:

Function.prototype.__proto__ === Object.prototype   //ture,函数是特殊的对象,所以空函数也是构造对象函数的实例
Function..__proto__ === Function.prototype          //ture

3. 继承

许多OO语言都支持两种继承方式:
接口继承:继承方法签名
实现继承:继承实际的方法
由于JS中函数没有签名,在ECMAScript中无法实现接口继承,所以只支持实现继承,主要依靠原型链来实现。

猜你喜欢

转载自blog.csdn.net/zhouyl02/article/details/82745741
今日推荐