继续与原型

我们在讨(mian)论(shi)JavaScript这门言语时,老是绕不过的一个话题就是“继续与原型链”。那末“继续与原型链”究竟是什么呢?

我很喜欢的一个谈天情势是:我不能说XX是什么,我只能说XX像什么。也就是说我不直接跟你说定义,由于一般而言,“定义”所形貌的观点很艰涩,比方关于“闭包”的定义——闭包是函数和声明该函数的词法环境的组合。

所以,我们先来看一下,JavaScript里究竟“继续与原型链”是怎样表现的。

“继续与原型链”像什么

不同于Java等的静态言语,在JavaScript这门言语里,我们没有“类”这个观点,一切的继续都是基于原型的。我们先直接看个例子:

var obj = {
    a: 0,
    f: function() {
        return this.a + 1
    }
}

var obj_1 = {} // 我们希冀cat也能有sound属性跟speak要领

obj_1.__proto__ = obj

console.log(obj_1.a) // 0
console.log(obj_1.f()) // 1

如上,我们定义obj_1这个对象的时刻,并没有声明a属性跟f要领,然则我们依旧可以找到它们。这是由于在JavaScript中,你在一个对象上寻觅某个属性(JavaScript对象都是键值对的情势,所以要领实在也可以算一个属性),他起首会在该对象当地寻觅,假如没有,他会顺着原型链一层层往上寻觅。

在上面的栗子中,对象obj_1当地没有定义任何属性,所以当我们实行obj_1.a的时刻,会顺着原型链往上找。在obj_1.__proto__ = obj这句里,我们将obj赋值给了obj_1__proto__属性。

然则等等,__proto__是什么?

__proto__属性指向的就是obj_1的原型,obj的原型是什么呢?我们可以打印obj.__proto__来看看,效果打印出来一大堆东西,这些实在就是Object.prototype,也就是“最终原型”,这个对象不再继续任何原型。依据之前说的,obj_1应该也能直接访问到这上面的属性。现实也确实云云,比方:

obj_1.hasOwnProperty('a') // false

我们并没有在obj_1上定义hasOwnProperty要领,然则依旧可以找到该要领。现实上,一切以对象字面量(Object Literal)情势建立出来的对象,都继续了有Object.prototype上的一切属性。

那末我们能不能建立一个不继续自任何原型的对象呢?答案是可以的。

JavaScript为我们供应了一个要领叫Object.create,经由过程它,我们可以建立一个原型为特定对象的对象。假如我们传入一个null,那末我们就可以建立一个“原型为空”的对象。

var a = Object.create(null)

在这个例子里,a成了一个空的对象,不仅当地没有任何属性,连原型链都没有,也就是说它以至都没有继续Object.prototype。(思索:如许的空对象究竟有什么作用呢?)

如许一来,我们也可以应用Object.create来完成继续咯?对的。

var obj = {
    a: 0,
    f: function() {
        return this.a + 1
    }
}

var obj_2 = Object.create(obj)
console.log(obj_2.a) // 0
console.log(obj_2.f()) // 1

然则从新设想,继续的实质是什么?继续原型!那末不管用什么要领,只要在我的原型链上能找到你就好了。

如今有一个题目,obj上定义了一个属性a,假如我在obj_2上再定义一个属性a,那末打印出来的会是谁的a呢?

var obj = {
    a: 0,
    f: function() {
        return this.a + 1
    }
}

var obj_2 = Object.create(obj)
obj_2.a = 2
console.log(obj_2.a) // 2

答案是不言而喻的,由于我们在寻觅一个属性的时刻,老是从当前对象当地最先的,假如在当前对象上找到了这个属性,那末查询就住手了。所以,假如原型链太长,在查找一个靠前的原型上的属性的时刻,就会比较耗时。我们应该只管防止这类太长的原型链。

“继续与原型链”是什么

读到这里,置信我们已可以对继续原型链做一个定义了。

原型链

原型链就是从一个对象的__proto__最先,一直到这条线的最末端,大部分情况下,这个最末端就是Object.prototype。比方上面的谁人例子:

var obj = {
    a: 0,
    f: function() {
        return this.a + 1
    }
}

var obj_2 = Object.create(obj)

// obj_2.__proto__ === obj
// obj.__proto__ === Object.prototype

继续

在这个例子里,obj --- Object.prototype就组成了一个原型链,顺着原型链,我们可以找到这个对象最最先继续自哪一个对象,同时,原型链上的每个节点都可以继续上游对象的一切属性。继续形貌的应该是一种关联,或许一种行动。

new运算符

在前面的篇幅里我们晓得,在JavaScript里,对象可以用字面量的情势与Object.create的情势来建立。然则JavaScript里另有一种体式格局来建立一个对象,那就是运用new运算符。

var obj = new Object

console.log(obj) // {}

依据前面的内容,我们可知obj继续了Object.prototype对象上的属性。关于new操纵符,可以看我的另一篇专栏当我们在JavaScript中new一个对象的时刻,我们究竟在做什么。那末Object是什么?

我们来实行一下typeof Object,打印出来的是”function”。对的,Object是一个函数,正确地说,它是一个组织函数。new运算符操纵的,应该是一个函数。

我们可以对恣意函数实行new操纵。然则一个函数假如被用作了组织函数来实例化对象,那我们倾向于把它的首字母大写。

var Foo = function(x) {
    this.x = x
}

var boo = new Foo(1)
console.log(boo, boo.x) // Foo {x: 1} 1

组织函数能让我们初始化一个对象,在组织函数里,我们可以做一些初始化的操纵。一般我们在编写一些JavaScript插件的时刻会在全局对象上挂载一个组织函数,经由过程实例化这个组织函数,我们可以继续它的原型对象上的一切属性。

既然组织函数有属于本身的原型对象,那末我们应该能让另一个组织函数来继续他的原型对象咯?

var Human = function(name) {
    this.name = name
}
var Male = function(name) {
    Human.call(this, name)
    this.gender = 'male'
}

var jack = new Male('jack')
console.log(jack) // Male {name: "jack", gender: "male"}

我们在组织函数内部实行了Human函数并改变了Human函数内部的this指向(实在这个this指向的是实例化以后的对象)。同时,我们在Male的原型上定义一个本身的属性gender,如许,实例化出来的对象同时有了两个属性。

然则这个继续完全么?继续是须要继续原型的,然则jack的原型链上并没有Human,我们须要分外两步。

var Human = function(name) {
    this.name = name
}
var Male = function(name) {
    Human.call(this, name)
    this.gender = 'male'
}

Male.prototype = Object.create(Human.prototype)
Male.prototype.constructor = Male

var jack = new Male('jack')
console.log(jack) // Male {name: "jack", gender: "male"}

如许一来,我们就可以在jack的原型链上找到Human了。

ES6的类

实在前面一节看起来会比较艰涩,由于在ES6之前,JavaScript没有类的观点(固然以后也没有),然则我们却有“组织函数”,那上面一节的栗子就应该说是组织函数Male继续了组织函数Human

我记得当时排场有点为难,人人都搓动手低着头都不晓得说点儿什么

幸亏ES6里我们有了Class的关键字,这是个语法糖,实质上,JavaScript的继续照样基于原型的。然则,最少情势上,我们可以依据“类”的体式格局来写代码了。

class Human {
    constructor(name) {
        this.name = name
    }
}
class Male extends Human {
    constructor(name) {
        super(name)
        this.gender = 'male'
    }
}

var jack = new Male('jack')
console.log(jack) // Male {name: "jack", gender: "male"}

在掌握台上顺着__proto__一层层往下翻,我们会能找到class Maleclass Human,这说明我们的继续胜利了。同时,我们也可以明白成“类Male继续了类Human”,虽然在JavaScript实在并没有类这个东西。

结语

实在通篇的中心照样那句话:JavaScript的继续是基于原型的。许多内容我没有睁开解说许多,表达了骨干即可。

援用

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