独家剖析Javascript原型继续

传统面向对象的继续和多态

我们晓得C++/Java/C#等面向对象言语,都原生地支撑类的继续。继续的中心作用约略是建立一个派生类,并使其复用基础类(即父类)的字段和/或要领。而且派生类能够重写基础类的要领。如许基础类和派生类雷同署名的要领在被挪用时,就会有差别的行为表现,即为多态的实质。换句话说,多态是透过继续重写完成的。

举例:完成差别的动物啼声差别。
过程式编程(Java代码):

void animalSpeak(String animal) {
    if(animal == "Dog") {
        System.out.println("汪汪");
    } else if(animal == "Cat") {
        System.out.println("喵喵");
    } else {
      System.out.println("动物发声");
    }
}

//挪用代码
animalSpeak("Dog"); //汪汪
animalSpeak("Cat"); //喵喵
animalSpeak("");    //动物发声

这里一个问题是,假如增添一种动物,就要在speak要领增添if分支,此要领逐步变得痴肥难保护。偶然因增添一种动物,会偶然误伤其他动物的逻辑,也何尝可知。面向对象式的动态应运而生。

面向对象完成(java代码)

class Animal {
    void speak() {
       System.out.println("动物发声:");
    }
}

class Dog extends Animal {
    void speak() {
       super.speak();
       System.out.println("汪汪");
    }
}

class Cat extends Animal {
    void speak() {
       super.speak();
       System.out.println("喵喵");
    }
}

void animalSpeak(Animal animal) {
    animal.speak();
}

//挪用代码
animalSpeak(new Cat());     //动物发声: 
                            //喵喵
animalSpeak(new Dog());     //动物发声: 
                            //汪汪

当要增添一种动物时,只需增添一个class继续 Animal,不会影响其他已有的动物speak逻辑。可看出,面向对象多态编程的一个中心头脑是便于扩大保护

结语:面向对象编程以继续发生派生类和重写派生类的要领,完成多态编程。中心头脑是便于扩大和保护代码,也防止if-else

JavaScript继续完成

Java继续是class的继续,而JavaScript的继续平常是经由过程原型(prototype)完成。prototype的实质是一个Object实例,它是在统一范例的多个实例之间同享的,它内里包括的是须要同享的要领(也能够有字段)。
JavaScript版原型继续的完成:

function Animal() {
}
Animal.prototype.speak = function () {
    console.log('动物发声:');
}

function Dog(name) {
    this.name = name;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function () {
    //经由过程原型链找‘基础类’原型里的同名要领
    this.__proto__.__proto__.speak.call(this);
    console.log('汪汪, 我是', this.name);
}

function Cat(name) {
    this.name = name;
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Cat.prototype.speak = function () {
    //经由过程原型链找‘基础类’原型里的同名要领
    this.__proto__.__proto__.speak.call(this);
    console.log('喵喵, 我是', this.name);
}

//挪用代码
function animalSpeak(animal) {
    animal.speak();
}

animalSpeak(new Dog('大黄'))
console.log()
animalSpeak(new Cat('小喵'))

//动物发声:
//汪汪, 我是 大黄

//动物发声:
//喵喵, 我是 小喵

JavaScript原型理会

传统面向对象言语的class继续是为代码(要领和字段)复用,而JavaScript的prototype是在同范例实例之间同享的对象,它包括同享的要领(也可有字段)。所以java的class继续和javascript的原型继续,可谓异曲同工。为了轻易明白js原型,提出两个观点:原型的design-time和run-time

Design-time 原型

可明白为我们(程序员)怎样设想js范例的自上而下的继续关联。以上例看出,design-time是经由过程prototype赋值完成。

// Dog范例继续自Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Run-time 原型和原型链

Run-time可明白为,原型设想好以后,要建立实例了, 而且还能向上查找其继续自哪一个原型

// 建立Dog范例的一个实例
var dog = new Dog('大黄');

// ***打起精神*** 这里到了症结的处所:怎样查找dog建立自哪一个范例:
// 不言而喻, dog是由Dog制造出来的
dog.__proto__ // Dog { constructor: [Function: Dog], speak: [Function] }

// 再往上查找原型链:
// 看出来是继续了Animal的原型
dog.__proto__.__proto__  //Animal { speak: [Function] }

// 再往上查找原型链:
// 看出来是继续了Object的原型
dog.__proto__.__proto__.__proto__ // {}

// 再往上查找原型链:
// 到了原型链的顶端。
dog.__proto__.__proto__.__proto__.__proto__ // null

所以在挪用实例的要领,它会在原型链上自下而上,直到找到该要领。假如到了原型链顶端还没有找到,就抛错了。

结语: design-time原型是经由过程prototype赋值,设想好自上而下的继续关联; run-time时经由过程实例的__proto__,自下而上在原型链中查找须要的要领。

再论prototype 与__proto__

如前文述prototype可算作design-time的观点,以此prototype制造出一个实例。

var dog = new Dog('大黄');
//实质是:
//new 是方便组织要领
var dog = Object.create(Dog.prototype);
dog.name = '大黄'

__proto__是属于实例的,可反查实例出自哪一个prototype。 所以dog.__proto__明显即是Dog.prototype.

  dog.__proto__ == Dog.prototype //true

而Dog.prototype创自于 Animal.prototype:

Dog.prototype = Object.create(Animal.prototype);

所以Dog.prototype的__proto__即为Animal.prototype

 Dog.prototype.__proto__ == Animal.prototype //true
 dog.__proto__.__proto__ == Animal.prototype //true

如许就完成了run-time原型链自下而上的查找

结束语

原型继续是JS陈词滥调的话题,也是很主要但不容易深切明白的手艺点。本文里提出了design-time原型设想和run-time原型链查找,愿望有助于此手艺点的明白。

跋文

上文是对自定义函数范例实例的原型剖析,漏掉了对JS内置范例的原型剖析。人人晓得JS内置范例是有:

undefined
null
bool
number
string
object
    Function
    Date
    Error
 symbol (ES6)

JS基础(primitive)范例原型剖析

基础范例有undefined, null, bool, number, string和symbol. 基础范例是以字面量赋值函数式赋值的,而非经由过程new或Object.create(…)出来。
基础范例是没有design-time的prototype。但undefined/null以外的基础演习,照样有run-time的__proto__

// 经常使用基础范例的字面量赋值:
var b = true;
var n = 1;
var s = 'str';

// 经常使用基础范例的run-time __proto__
// 须要申明的是,基础范例自身是没有任何要领和字段的。
// 比方undefined/null, 不能挪用其任何要领和字段。
// 这里挪用b.__proto__时,会暂时天生一个基础范例包装类的实例,
// 即天生var tmp = new Boolean(b)。这是个object实例,返回__proto__
b.__proto__ // [Boolean: false]
n.__proto__ // [Number: 0]
s.__proto__ // [String: '']
// 以上 *.__proto__ 打印出的是,字面量值出自哪一个函数, 所以亦能够函数体式格局赋值, 跟字面量完全等价。

// 基础范例的函数式赋值:
// *注重*的是,这里仅是挪用函数,没有new。若用了new, 就组织出一个对象实例,而再非基础范例了
b = Boolean(true)
n = Number(1)
s = String('')
sym = Symbol('IBM') // ES6 新增的symbol没有字面量赋值体式格局

// 特别的case是undefined和null, 只要字面量赋值(没函数体式格局赋值)
// null 和 undefined是没有__proto__的。
// 也能够明白为,null处于任何原型链的最顶端,这是由于null是object范例(typeof null == 'object')。undefined不是object范例。
var nn = null;
var un = undefined;

JS基础范例的对象实例的原型剖析

假如经由过程new 组织基础范例的对象实例,那末就是对象而非原生态基础范例了。

var b = new Boolean(true)
var n = new Number(1)
var s = new String('')

它们就遵照自定义函数范例对象的原型轨则了。以上述Number n为例:

// Number的 design-time的prototype:
Number.prototype //[Number: 0]
typeof Number.prototype //'object'
Number.prototype instanceof Object // true. 原型自身就是一对象实例

// n的run-time __proto__原型链
n.__proto__ //[Number: 0],n是由Number函数组织发生的
// 可看出,n 继续了object范例
n.__proto__.__proto__ // {}
// n的原型链顶端也是null
n.__proto__.__proto__.__proto__ // null

JS内置object范例的原型剖析

JS内置的Date, Error, Function其自身就是function,就是说 typeof Date, typeof Error, typeof Function 都是 ‘function’. 所以读者可用本文剖析自定义函数范例原型的要领,自行剖析这三者的design-time的prototype, 以及run-time的__proto__原型链。

须要特别指出的是,我们险些不会用new Function的体式格局去建立Function的实例,而是透过function症结字去定义函数。

// 平常是用function这个症结字,去定义函数。这和经由过程new Function组织实质是一样的
function foo() { }

// 经由过程run-time的 __proto__,实在可看出,foo就是Function这个范例的一个实例
foo.__proto__ //[Function]
foo instanceof Foo // true

// 所以foo也就继续了Function的design-time prototype
// 而明白这一点很主要。
add.__proto__ == Function.prototype // true

// 函数实例自身也是object范例
foo.__proto__.__proto__ //{}
foo instanceof Object // true

{}和Object.create(null)的区分

以下定义是等价的

var obj = {} // 字面量
var obj = new Object() // Object函数组织
var obj = Object.create(Object.prototype) // Object原型组织

obj run-time的__proto__即Object.prototype, 故obj继续了Object.prototype的同享要领,比方toString(), valueOf()

obj.__proto__ == Object.prototype //true
obj.toString()                   //'[object Object]'
var obj2 = Object.create(null)
obj2.__proto__  // undefined
obj2.toString() //TypeError: obj2.toString is not a function

能够看出,obj2无run-time的__proto__,没有继续Object.prototype,故而就不能挪用.toString()要领了

    原文作者:杰克船长
    原文地址: https://segmentfault.com/a/1190000010197254
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞