关于javascript:万物皆空之-JavaScript-原型

53次阅读

共计 7122 个字符,预计需要花费 18 分钟才能阅读完成。

ES6 带来了太多的语法糖,其中箭头函数覆盖了 this 的神妙,而 class 也覆盖了本文要长篇议论的 原型。

最近,我重写了这篇文章,通过本文,你将能够学到:

1. 如何用 ES5 模仿类;2. 了解 prototype 和 __proto__;3. 了解原型链和原型继承;4. 更深刻地理解 JavaScript 这门语言。

引入:一般对象与函数对象

在 JavaScript 中,始终有这么一种说法,万物皆对象。事实上,在 JavaScript 中,对象也是有区别的,咱们能够将其划分为 一般对象 和 函数对象。Object 和 Function 便是 JavaScript 自带的两个典型的 函数对象。而函数对象就是一个纯函数,所谓的 函数对象,其实就是应用 JavaScript 在 模仿类。

那么,到底什么是一般对象,什么又是函数对象呢?请看下方的例子:

首先,咱们别离创立了三个 Function 和 Object 的实例:

function fn1() {}
const fn2 = function() {}
const fn3 = new Function('language', 'console.log(language)')

const ob1 = {}
const ob2 = new Object()
const ob3 = new fn1()

打印以下后果,能够失去:

console.log(typeof Object); // function
console.log(typeof Function); // function
console.log(typeof ob1); // object
console.log(typeof ob2); // object
console.log(typeof ob3); // object
console.log(typeof fn1); // function
console.log(typeof fn2); // function
console.log(typeof fn3); // function

在上述的例子中,ob1、ob2、ob3 为一般对象(均为 Object 的实例),而 fn1、fn2、fn3 均是 Function 的实例,称之为 函数对象。

如何辨别呢?其实记住这句话就行了:

所有 Function 的实例都是函数对象,而其余的都是一般对象。

说到这里,仔细的同学会发表一个疑难,一开始,咱们曾经提到,Object 和 Function 均是 函数对象,而这里咱们又说:所有 Function 的实例都是函数对象,难道 Function 也是 Function 的实例?

先保留这个疑难。接下来,对这一节的内容做个总结:

从图中能够看出,对象自身的实现还是要依附构造函数。那原型链到底是用来干嘛的呢?

家喻户晓,作为一门面向对象(Object Oriented)的语言,必然具备以下特色:

  1. 对象唯一性
  2. 抽象性
  3. 继承性
  4. 多态性

而原型链最大的目标, 就是为了实现继承。

进阶:prototype 和 proto

原型链到底是如何实现继承的呢?首先,咱们要引入介绍两兄弟:prototype 和 __proto__,这是在 JavaScript 中无处不在的两个变量(如果你常常调试的话),然而,这两个变量并不是在所有的对象上都存在,先看一张表:

对象类型 prototype __proto__
一般对象 ×
函数对象

首先,咱们先给出以下论断:

只有 函数对象 具备 prototype 这个属性;prototype __proto__ 都是 JavaScript 在定义一个函数或对象时主动创立的 预约义属性。

接下来,咱们验证上述的两个论断:

function fn() {}
console.log(typeof fn.__proto__); // function
console.log(typeof fn.prototype); // object

const ob = {}
console.log(typeof ob.__proto__); // function
console.log(typeof ob.prototype); // undefined,哇!果然一般对象没有 prototype

既然是语言层面的预置属性,那么两者到底有何区别呢?咱们仍然从论断登程,给出以下两个论断:

prototype 被实例的 __proto__ 所指向(被动)__proto__ 指向构造函数的 prototype(被动)

哇,也就是说以下代码成立:

console.log(fn.__proto__ === Function.prototype); // true
console.log(ob.__proto__ === Object.prototype); // true

看起来很酷,论断霎时被证实,感觉是不是很爽,那么问题来了:既然 fn 是一个函数对象,那么 fn.prototype.__proto__ 到底等于什么?

这是我尝试去解决这个问题的过程:

首先用 typeof 失去 fn.prototype 的类型:”object”;哇,既然是 “object”,那 fn.prototype 岂不是 Object 的实例?根据上述的论断,疾速地写出验证代码:

console.log(fn.prototype.__proto__ === Object.prototype) // true

接下来,如果要你疾速地写出,在创立一个函数时,JavaScript 对该函数原型的初始化代码,你是不是也能疾速地写出:

// 理论代码
function fn1() {}

// JavaScript 主动执行
fn1.protptype = {
    constructor: fn1,
    __proto__: Object.prototype
}

fn1.__proto__ = Function.prototype

到这里,你是否有一丝豁然开朗的感觉?此外,因为一般对象就是通过 函数对象 实例化(new)失去的,而一个实例不可能再次进行实例化,也就不会让另一个对象的 __proto__ 指向它的prototype,因而本节一开始提到的 一般对象没有 prototype 属性 的这个论断仿佛十分好了解了。从上述的剖析,咱们还能够看出,fn1.protptype 就是一个一般对象,它也不存在 protptype 属性。

再回顾一下上一节,咱们还遗留一个疑难:

难道 Function 也是 Function 的实例?

是时候去掉应该让它成立了。那么此刻,just show me your code!

console.log(Function.__proto__ === Function.prototype) // true

重点:原型链

上一节咱们详解了 prototype __proto__,实际上,这两兄弟次要就是为了结构原型链而存在的。

先上一段代码:

const Person = function(name, age) {
    this.name = name
    this.age = age
} /* 1 */

Person.prototype.getName = function() {return this.name} /* 2 */

Person.prototype.getAge = function() {return this.age} /* 3 */

const ulivz = new Person('ulivz', 24); /* 4 */

console.log(ulivz) /* 5 */
console.log(ulivz.getName(), ulivz.getAge()) /* 6 */

解释一下执行细节:

  • 执行 1,创立了一个构造函数 Person,要留神,后面曾经提到,此时 Person.prototype 曾经被主动创立,它蕴含 constructor 和 __proto__这两个属性;
  • 执行 2,给对象 Person.prototype 减少了一个办法 getName();
  • 执行 3,给对象 Person.prototype 减少了一个办法 getAge();
  • 执行 4, 由构造函数 Person 创立了一个实例 ulivz,值得注意的是,一个构造函数在实例化时,肯定会主动执行该构造函数。在浏览器失去 5 的输入,即 ulivz 应该是:
{
     name: 'ulivz',
     age: 24
     __proto__: Object // 实际上就是 `Person.prototype`
}

联合上一节的教训,以下等式成立:

console.log(ulivz.__proto__ == Person.prototype)  // true
  • 执行 6 的时候,因为在 ulivz 中找不到 getName() 和 getAge() 这两个办法,就会持续朝着原型链向上查找,也就是通过 proto 向上查找,于是,很快在 ulviz.__proto__ 中,即 Person.prototype 中找到了这两个办法,于是进行查找并执行失去后果。

    这便是 JavaScript 的原型继承。精确的说,JavaScript 的原型继承是通过 proto 并借助 prototype 来实现的。

于是,咱们能够作如下总结:

  • 函数对象的 proto 指向 Function.prototype;(温习)
  • instance.__proto__ 指向函数对象的 prototype;(温习)
  • 一般对象的 proto 指向 Object.prototype;(温习)
  • 一般对象没有 prototype 属性;(温习)
  • 在拜访一个对象的某个属性 / 办法时,若在以后对象上找不到,则会尝试拜访 ob.__proto__, 也就是拜访该对象的构造函数的原型 obCtr.prototype,若仍找不到,会持续查找 obCtr.prototype.__proto__,像这样顺次查找上来。若在某一刻,找到了该属性,则会立即返回值并进行对原型链的搜寻,若找不到,则返回 undefined。

为了测验你对上述的了解,请剖析下述两个问题:

1. 以下代码的输入后果是?
console.log(ulivz.__proto__ === Function.prototype)

答案:false

2. `Person.__proto__` 和 `Person.prototype.__proto__ ` 别离指向何处?

剖析:

后面曾经提到,在 JavaScript 中万物皆对象。Person 很显著是 Function 的实例,因而,Person.__proto__ 指向 Function.prototype:

console.log(Person.__proto__ === Function.prototype)  // true

因为 Person.prototype 是一个一般对象,因而 Person.prototype.__proto__ 指向 Object.prototype

console.log(Person.prototype.__proto__ === Object.prototype)  // true

为了验证 Person.__proto__ 所在的原型链中没有 Object,以及 Person.prototype.__proto__ 所在的原型链中没有 Function, 联合以下语句验证:

console.log(Person.__proto__ === Object.prototype) // false
console.log(Person.prototype.__proto__ == Function.prototype) // false

终极:原型链图

上一节,咱们实际上还遗留了一个疑难:

原型链如果一个搜寻上来,如果找不到,那何时进行呢?也就是说,原型链的止境是哪里?

咱们能够疾速地利用以下代码验证:

function Person() {}
const ulivz = new Person()
console.log(ulivz.name)

很显然,上述输入 undefined。上面简述查找过程:

ulivz                // 是一个对象,能够持续 
ulivz['name']           // 不存在,持续查找 
ulivz.__proto__            // 是一个对象,能够持续
ulivz.__proto__['name']        // 不存在,持续查找
ulivz.__proto__.__proto__          // 是一个对象,能够持续
ulivz.__proto__.__proto__['name']     // 不存在, 持续查找
ulivz.__proto__.__proto__.__proto__       // null !!!! 进行查找,返回 undefined

哇,原来路的止境是一场空。

最初,再回过头来看看上一节的那演示代码:

const Person = function(name, age) {
    this.name = name
    this.age = age
} /* 1 */

Person.prototype.getName = function() {return this.name} /* 2 */

Person.prototype.getAge = function() {return this.age} /* 3 */

const ulivz = new Person('ulivz', 24); /* 4 */

console.log(ulivz) /* 5 */
console.log(ulivz.getName(), ulivz.getAge()) /* 6 */

咱们来画一个原型链图,或者说,将其整个原型链图画进去?请看下图:

PS:重写的时候,把 chl(我的中文名缩写)改成了 ulivz(Github 名),所以这张图中的 chl 实际上就是 ulivz,画这张图的时候, 我还在用 windows…

画完这张图,基本上所有之前的疑难都能够解答了。

与其说万物皆对象, 万物皆空仿佛更形象。
调料:constructor

后面曾经有所提及,但只有原型对象才具备 constructor 这个属性,constructor 用来指向援用它的函数对象。

Person.prototype.constructor === Person //true
console.log(Person.prototype.constructor.prototype.constructor === Person) //true

这是一种循环援用。当然你也能够在上一节的原型链图中画下来,这里就不赘述了。

增加链接形容补充:JavaScript 中的 6 大内置(函数)对象的原型继承

通过前文的阐述,联合相应的代码验证,整顿出以下原型链图:

由此可见,咱们更加强化了这两个观点:

任何内置函数对象(类)自身的 proto 都指向 Function 的原型对象;除了 Oject 的原型对象的 proto 指向 null,其余所有内置函数对象的原型对象的 proto 都指向 object。

为了缩小读者敲代码的工夫,特给出验证代码,心愿可能促成你的了解。

    Array:

    console.log(arr.__proto__)
    console.log(arr.__proto__ == Array.prototype)   // true 
    console.log(Array.prototype.__proto__== Object.prototype)  // true 
    console.log(Object.prototype.__proto__== null)  // true

    RegExp:

    var reg = new RegExp;
    console.log(reg.__proto__)
    console.log(reg.__proto__ == RegExp.prototype)  // true 
    console.log(RegExp.prototype.__proto__== Object.prototype)  // true

    Date:

    var date = new Date;
    console.log(date.__proto__)
    console.log(date.__proto__ == Date.prototype)  // true 
    console.log(Date.prototype.__proto__== Object.prototype)  // true

    Boolean:

    var boo = new Boolean;
    console.log(boo.__proto__)
    console.log(boo.__proto__ == Boolean.prototype) // true 
    console.log(Boolean.prototype.__proto__== Object.prototype) // true

    Number:

    var num = new Number;
    console.log(num.__proto__)
    console.log(num.__proto__ == Number.prototype)  // true 
    console.log(Number.prototype.__proto__== Object.prototype)  // true

    String:

    var str = new String;
    console.log(str.__proto__)
    console.log(str.__proto__ == String.prototype)  // true 
    console.log(String.prototype.__proto__== Object.prototype)  // true

总结

若 A 通过 new 创立了 B, 则 B.__proto__ = A.prototype;__proto__是原型链查找的终点;执行 B.a,若在 B 中找不到 a,则会在 B.__proto__中,也就是 A.prototype 中查找,若 A.prototype 中依然没有,则会持续向上查找,最终,肯定会找到 Object.prototype, 假使还找不到,因为 Object.prototype.__proto__指向 null,因而会返回 undefined;为什么万物皆空,还是那句话,原型链的顶端,肯定有 Object.prototype.__proto__ ——> null。

原文作者

正文完
 0