Where is JavaScript?

JavaScript入坑之this和对象原型

跟着《你不知道的JavaScript》系列丛书由语言原理入手深入理解this和对象原型

this

this隶属于JavaScript的函数运行机制,是记录函数调用时的各种条件的上下文的一个特殊属性,会在函数执行的过程中用到。

  • this不是author-time binding,而是 runtime binding。

函数

在提 this 指向问题之前,肯定是有必要说明 function ,既然 function 是对象,那么就能像普通的值一样传递。嗯,在匿名函数中,这样的做法是非常常见的。

函数会在代码的运行前进行解析,这就保证了函数存在于当前上下文的任意一个地方,即在函数定义的前面去调用也是正确的。

              
  • 1
  • 2
foo (); function foo () {};

函数是一个对象,所以我们常常会看见把一个匿名的函数给一个值。

              
  • 1
var foo = function () {};

赋值语句只有在执行的时候才会运行,也就是说所看到的var foo = 1 是分为两部分的:

              
  • 1
  • 2
  • 3
  • 4
var foo; ... foo = 1; ...

所以可能会出现下面的问题

              
  • 1
  • 2
  • 3
  • 4
  • 5
foo; //undefined foo(); //TypeError var foo = function () { console.log(1); }

因为他和下面的写法是一样的

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
var foo; foo; foo(); foo = function () { console.log(1); }

正常的,我们这样写是没有问题的

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
foo; // undefined var foo = function () { console.log(1); } foo(); // 1

this指向问题

this 是和执行上下问环境息息相关的。他是上下文环境的一个属性,而不是某个变量对象的属性。

这个特点很重要,因为和变量不同,this 是没有一个类似搜寻变量的过程。当你在代码中使用了this,这个 this 的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻。this 的值只取决中进入上下文时的情况。

这样, javascript中的 this 和一般语言中的 this 是有点区别的,在不同的情况下,指向也有所不同:

  1. 全局范围内
    指向的是全局对象。在普通浏览器中指向的是window, 在Node中指向的是全局对象global(全局环境中) 或者module.exports(模块环境中)。

  2. 函数调用中
    在普通的函数调用中,this依旧指向全局对象。(这个设计似乎并没有什么作用,应该是一个错误的设计。并且在很多的情况下不注意都会引来很多的麻烦)

                  
    • 1
    • 2
    • 3
    • 4
    • 5
    Foo.method = function () { function test () { //这里的this指向的不是Foo.而是全局对象 } }

    一般我们会创建一个局部的变量去指代上一层的对象

                  
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    Foo.method = function () { that = this; //按照下面一条规则,这儿的this指向的是Foo function (test) { // that } }
  3. 方法调用中
    指向调用它的对象

  4. 调用构造函数
    指向新创建的对象

  5. 显式设置this
    指向显式指向的对象。即指向call或者apply的第一个参数

注意:
像下面这样对方法进行赋值的时候,函数内 this 的指向也不是赋值时所调用方法的对象。

              
  • 1
  • 2
var test = Obj.method(); test(); // 第二条规则,this指向的不是Obj

对象

JS里一切皆对象,理解对象是理解js语言的关键,下面从几个角度解释js里的对象。

封装,继承,多态

在C++/Java等传统面向对象编程中,类(class)是对象(object)的模板, class 不占用内存空间, object 占用内存, object 也称为 class instance,同一个 class 可以被 new 出很多个 object

Javascript 语言不支持"类",但 Javascript 仍然是面向对象语言,面向对象的3要素在JavaScript 里都支持:

  • 封装 - 数据与方法封装在一起,方法可以操作数据,这就是JS里的object
  • 继承 - 新创建的对象可以继承父对象的数据和方法,js里有多种方法实现继承,譬如原型方式继承,拷贝继承等
  • 多态 - 一个接口有多种实现
    实际上,js里一切皆对象

什么是对象?

js里的对象(object)就是一组键值(name-value)的集合, name总是string类型, value可以是各种类型, 可以是基本数据类型, 可以是数组或其他对象, 也可以是函数, object很像是一个hash map. object里的name-value是无序的

对象是动态的,可以动态地增加属性和方法

创建对象的方式

C++/Java等语言里创建object是使用"new YourClassName()"的方式,js里没有类,可以使用下面几种方式创建object:

  1. 字面量:

                  
    • 1
    var person = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"};

    属性名可以加引号也可以不加引号,如果属性名有空格,则必须加引号。

  2. 使用new:

                  
    • 1
    • 2
    • 3
    • 4
    • 5
    var person = new Object(); person.firstName = "John"; person.lastName = "Doe"; person.age = 50; person.eyeColor = "blue";

    这种方式也可以创建其他js内建对象: Function, String, Number, Date, RegExp等,但这种方式没有方法1效率高,因此不推荐.

  3. new构造函数:

                  
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    function Person(first, last, age, eyecolor) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eyecolor; } var myFather = new Person("John", "Doe", 50, "blue"); var myMother = new Person("Sally", "Rally", 48, "green");

    这一方法其实和方法2是类似的,方法2里new调用的是js内建的构造函数,这里new调用的是自己写的构造函数。

    js里构造函数和普通函数没有任何区别,因此为了区分这是一个构造函数,一般在命名上区分,构造函数使用驼峰式命名,普通函数使用半驼峰式命名。

  4. Object.create()
    这是ECMAScript 5新定义的方式,假设已经有方法1里的person对象,可以创建新的对象:

                  
    • 1
    var myself = Object.create(myFather);

    这种方式是让一个现有的object(myFather)变成新创建对象(myself)的原型。

    如果浏览器不支持Object.create()方法,可以用下面的方法模拟:

                  
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    if (!Object.create) { Object.create = function (o) { function F() {} F.prototype = o; return new F(); }; }

    方法4和方法3是不同的,方法3里创建的对象(myFather)拥有自己的属性(firstName, lastName等),方法4里创建的对象(myself)没有自己的属性,虽然通过myself可以访问到firstName,lastName等属性,但这些属性属于其原型对象myFather.

访问对象属性

访问对象包括访问对象属性和调用对象方法。

  1. 读取属性
    访问对象非常方便,有如下几种方式:

                  
    • 1
    • 2
    • 3
    • 4
    var array = []; array.push(123); //方法1: 使用.访问 array["push"](456); //方法2: 使用[]访问

    方法1比较直观,但当属性名是一个变量时,方法2会比较有用。

  2. 判断属性是否存在
    判断对象person1是否有属性name:

                  
    • 1
    • 2
    • 3
    if (person1.name) {} //method 1: use if if ("name" in person1) {} //method 2: use in if (person1.hasOwnProperty("name")) {} //method 3: use hasOwnProperty()

    说明:
    方法1: 如果person1有属性name,但name的值为null, undefined, 0, false, NaN, ''时,会返回false
    方法2: 如果name是person1原型对象里的属性,in也会返回true
    方法3: hasOwnProperty判断最严格,只有person1本身拥有属性name才会返回true

  3. 删除属性
    使用delete可以删除属性:

                  
    • 1
    • 2
    • 3
    console.log("name" in person1); //true delete person1.name; console.log("name" in person1); //false
  4. 枚举属性列表
    使用for-in loop可以循环对象里的所有属性:

                  
    • 1
    • 2
    • 3
    • 4
    for (var property in person1) { console.log("Name: " + property); console.log("Value: " + person1[property]); }

    ECMAScript5里提供了一个新方法 Object.keys():

                  
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    var properties = Object.keys(person1); var i, len; for (i=0, len=properties.length; i < len; i++){ console.log("Name: " + properties[i]); console.log("Value: " + person1[properties[i]]); }

    注意:for-in loop会返回原型对象里的属性,Object.keys()只会返回own properties

属性分类与详解

属性分类

我们平时使用的基本都是data properties,js对象里实际上有3种类型的属性:

  • 命名数据属性(named data properties) - 拥有一个确定的值的属性, 这也是最常见的属性.
  • 命名访问器属性(named accessor properties) - 通过getter和setter进行读取和赋值的属性.
  • 内部属性(internal properties) - 由js引擎内部使用的属性,不能通过js代码直接访问到,不过可以通过一些方法间接的读取和设置.
访问器属性

我们可以为accessor properties定义getter方法和setter方法,如果只有getter,说明这是只读的,如果只有setter,说明这是只写的。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
var person1 = { _name: "Nicholas", get name() { console.log("Reading name"); return this._name; }, set name(value) { console.log("Setting name to ", value); this._name = value; } }; console.log(person1.name); // 打印"Reading name" 和 "Nicholas" person1.name = "Greg"; // 打印"Setting name to Greg" console.log(person1.name); // 打印"Reading name" 和 "Greg"
内部属性

由js引擎内部使用的属性,不能通过js代码直接访问到,不过可以通过一些方法间接的读取和设置, 比如:每个对象都有一个内部属性[[Prototype]], 你不能直接访问这个属性, 但可以通过Object.getPrototypeOf()方法间接的读取到它的值.虽然内部属性通常用一个双中括号包围的名称来表示,但实际上这并不是它们的名字,它们是一种抽象操作,是不可见的,根本没有上面两种属性有的那种字符串类型的属性名.

其他的内部属性还有: [[Extensible]], [[DefineOwnProperty]], [[Put]], 以及function特有的内部属性 [[Call]]

属性特性

每个属性(property)都拥有4个特性(attribute).数据属性和访问器属性一共有6种属性特性:

  • 数据属性特有的特性:
    • [[Value]]: 属性的值.
    • [[Writable]]: 控制属性的值是否可以改变.
  • 访问器属性特有的特性:
    • [[Get]]: 存储着getter方法.
    • [[Set]]: 存储着setter方法.
  • 两种属性都有的特性:
    • [[Enumerable]]: 如果一个属性是不可枚举的,则在一些操作下,这个属性是不可见的,比如for...in和Object.keys()
    • [[Configurable]]: 如果一个属性是不可配置的,则该属性的所有特性(除了%%[[Value]]%%)都不可改变
防止对象被修改

js对象很容易被修改,但有时我们希望对象不被修改,有如下几种方法:

  1. Object.preventExtensions()

                  
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    var person1 = { name: "Nicholas" }; console.log(Object.isExtensible(person1)); //true Object.preventExtensions(person1); console.log(Object.isExtensible(person1)); //false person1.age = 50; console.log("age" in person1); //false
                  
    • 1
    %20%u5728strict%u6A21%u5F0F%u4E0B%uFF0C%u5982%u679C%u8BD5%u56FE%u5411non-extensible%u7684%u5BF9%u8C61%u589E%u52A0%u5C5E%u6027%uFF0C%u7A0B%u5E8F%u4F1A%u629B%u51FA%u5F02%u5E38%u3002
  2. Object.seal()
    seal是密封/封口/盖章/封上信封的意思:

                  
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    var person1 = { name: "Nicholas" }; console.log(Object.isExtensible(person1)); //true console.log(Object.isSealed(person1)); //false Object.seal(person1); console.log(Object.isExtensible(person1)); //false console.log(Object.isSealed(person1)); //true person1.age = 50; console.log("age" in person1); //false
  3. Object.freeze()
    freeze冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。

                  
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    var person1 = { name: "Nicholas" }; console.log(Object.isExtensible(person1)); //true console.log(Object.isSealed(person1)); //false console.log(Object.isFrozen(person1)); //false Object.freeze(person1); console.log(Object.isExtensible(person1)); //false console.log(Object.isSealed(person1)); //true console.log(Object.isFrozen(person1)); //true person1.age = 50; console.log("age" in person1); //false
new操作符的本质

JS没有类,但有new操作符,new操作符可以用于调用构造函数,并改变了构造函数里的this:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
function Person(first, last, age, eyecolor) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eyecolor; } var myFather = new Person("John", "Doe", 50, "blue");

上面的代码实际上类似于:

              
  • 1
  • 2
  • 3
  • 4
// create an empty object var myMother = {}; // call the function as a method of the empty object Person.call(myMother, "Sally", "Rally", 48, "green");

所以,new操作符的本质就是,new其实就是创建了一个空对象,然后以这个空对象为context(即this)来调用构造函数,然后构造函数里的赋值语句就动态的给空对象增加了属性和方法。

注意:上面的方法虽然和new很类似,但和new还是有区别的,myFather的原型对象是Person.prototype,而myMother的原型对象是Object.prototype

对象的原型[[prototype]]

在js中所有对象都有一个隐含的属性[[prototype]]指向其原型对象,原型对象也有自己的原型,如此下去便形成一个原型链,所有对象的原型链的顶层都是Object.prototype

请注意,[[prototype]]并不是一个真实的属性名,因此无法通过这个属性名获得原型对象,但js提供了方法来读取和判断对象的原型:

              
  • 1
  • 2
  • 3
  • 4
// 接前面的例子 console.log(Object.getPrototypeOf(myFather) === Person.prototype); //true console.log(Person.prototype.isPrototypeOf(myFather)); //true console.log(Person.prototype.isPrototypeOf(myMother)); //false

对象的原型链通常是只读的,用户无法修改某个对象的原型,所以无法修改对象的继承关系。

属性__proto__

在js规范里,对象原型通常是不可见的属性,因此无法直接访问。但某些浏览器里支持proto属性,firefox/chrome/safari/nodejs都支持__proto__属性,在这些浏览器里对象原型是可见的,可以直接访问对象的__proto__属性得到对象原型,也可以通过修改对象的__proto__属性来修改对象的原型链。

ECMAScript 6正在讨论把__proto__属性标准化,但目前属性__proto__还不是标准。

本文接下来提到的__proto__, [[prototype]]都是指一个意思,即自身对象里指向其原型对象的属性。

prototype

函数也是对象,所以函数也有属性__proto__,通过字面量声明的函数其原型对象是Function.prototype,当然Function.prototype的原型对象是Object.prototype

函数还有一个特有的属性prototype,每个函数都有一个prototype属性(特别注意,prototype属性是函数对象特有的属性,不要和js中每个对象到其原型的连接相混淆,那个是隐藏的,只是在firefox/chrome等浏览器中你可以使用__proto__访问到)。

__proto__, prototype, constructor的关系

下面的例子解释了__proto__prototypeconstructor三者的关系:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
function Person(name) { this.name = name; } Person.prototype.sayName = function() { console.log(this.name); }; var person1 = new Person("Nicholas"); var person2 = new Person("Greg");

上述代码对应的内存模型如下:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
person1 -------- __proto__ ----------- ------------------------------------- name:"Nicholas" | | | |--> Person.prorotype | person2 | ----------------- | -------- | constructor --------------> Person | __proto__ ----------- sayName:function() --------- | name:"Greg" __proto__ --> prototype ----> | __proto__ ---> V | Object.prototype v ^ Function.prototype | ------------------ <-------------------- __proto__

Person 是构造函数(当然也是对象),new 出来的 person1person2 这两个对象都有一个属性 __proto__ 指向 Person.prototype 对象,Person.prorotype 对象有一个属性 constructor 指向 Person 对象, Person 对象的 prorotype 属性对应的就是原型对象。

参考阅读 - JavaScript 的对象本质(更适合有 java、c++、c# 等背景的)

Object.prototype

js里所有对象的原型链的最顶端对象都是Object.prototype, 也就是说, 所有js对象都具有Object.prototype对象里的属性和方法

在 ES6 规范中,引入了 class 的概念。使得 JS 开发者终于告别了,直接使用原型对象模仿面向对象中的类和类继承时代。

但是JS 中并没有一个真正的 class 原始类型, class 仅仅只是对原型对象运用语法糖。所以,只有理解如何使用原型对象实现类和类继承,才能真正地用好 class

ES6:class

通过类来创建对象,使得开发者不必写重复的代码,以达到代码复用的目的。它基于的逻辑是,两个或多个对象的结构功能类似,可以抽象出一个模板,依照模板复制出多个相似的对象。就像自行车制造商一遍一遍地复用相同的蓝图来制造大量的自行车。

使用 ES6 中的 class 声明一个类,是非常简单的事。它的语法如下:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
class Person { constructor(name){ this.name = name } hello(){ console.log('Hello, my name is ' + this.name + '.'); } } var xiaoMing = new Person('xiaoMing'); xiaoMing.hello() // Hello, my name is xiaoMing.

xiaoMing 是通过类 Person 实例化出来的对象。对象 xiaoMing 是按照类 Person 这个模板,实例化出来的对象。实例化出来的对象拥有类预先订制好的结构和功能。

ES6 的语法很简单,但是在实例化的背后,究竟是什么在起作用呢?

class 实例化的背后原理

使用 class 的语法,让开发者告别了使用 prototype 模仿面向对象的时代。但是,class 并不是 ES6 引入的全新概念,它的原理依旧是原型继承

typeof class == "function"

通过类型判断,我们可以得知,class 的并不是什么全新的数据类型,它实际只是 function (或者说 object)

              
  • 1
  • 2
  • 3
  • 4
  • 5
class Person { // ... } typeof Person // function

为了更加直观地了解 Person 的实质,可以将它在控制台打印出来,如下
https://static.littlewin.wang/blog/18-1.jpeg

Person 的属性并不多,除去用 [[...]] 包起来的内置属性外,大部分属性根据名字就能明白它的作用。需要我们重点关注的是 prototype__proto__ 两个属性

实例化的原理: prototype

prototype 属性依然指向一个特殊性对象:原型对象

原型对象所以特殊,是因为它拥有一个普通对象没有的能力:将它的属性共享给其他对象

在 ES6 规范 中,对 原型对象 是如下定义的:

object that provides shared properties for other objects

原型对象是如何将它的属性分享给其他对象的呢?

这里使用 ES5 创建一个类,并将它实例化,来看看它的实质。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
function Person() { this.name = name } // 1. 首先给 Person.prototype 原型对象添加了 describe 方法 Person.prototype.describe = function(){ console.log('Hello, my name is ' + this.name + '.'); } // 2. 实例化对象的 __proto__ 指向 Person.prototype var jane = new Person('jane'); jane.__proto__ === Person.prototype; // 3. 读取 describe 方法时,实际会沿着原型链查找到 Person.prototype 原型对象上 jane.describe() // Hello, my name is jane.

上述使用 JS 模仿面向对象实例化的背后,实际有三个步骤:

首先给 Person.prototype 属性所指的原型对象上添加了一个方法 describe
在使用 new 关键字创建对象时,会默认给该对象添加一个原型属性 proto,该属性指向Person.prototype原型对象 在读取describe方法时,首先会在jane对象查找该方法,但是jane对象并不直接拥有describe方法。所以会沿着原型链查找到Person.prototype原型对象上,最后返回该原型对象的describe` 方法。
JS 中面向对象实例化的背后原理,实际上就是 原型对象

为了方便大家理解,从网上扒了一张的图片,放到这来便于大家理解。
https://static.littlewin.wang/blog/18-2.png

class 定义属性

当我们使用 class 定义属性(方法)的时候,实际上等于是在 class 的原型对象上定义属性。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
class Foo { constructor(){ /* constructor */ } describe(){ /* describe */ } } // 等价于 function Foo (){ /* constructor */ } Foo.prototype.describe = function(){ /* describe */ }

constructor 是一个比较特殊的属性,它指向构造函数(类)本身。可以通过以下代码验证。

              
  • 1
Foo.prototype.constructor === Foo // true

类继承

在传统面向对象中,类是可以继承类的。这样子类就可以复制父类的方法,达到代码复用的目的。

ES6 也提供了类继承的语法 extends,如下:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
class Foo { constructor(who){ this.me = who; } identify(){ return "I am " + this.me; } } class Bar extends Foo { constructor(who){ // super() 指的是调用父类 // 调用的同时,会绑定 this 。 // 如:Foo.call(this, who) super(who); } speak(){ alert( "Hello, " + this.identify() + "." ); } } var b1 = new Bar( "b1" ); b1.speak();

当实例 b1 调用 speak 方法时,b1 本身没有 speak,所以会到 Bar.prototype 原型对象上查找,并且调用原型对象上的 speak 方法。调用 identify 方式时,由于 this 指向的是 b1 对象。所以也会先在 b1 本身查找,然后沿着原型链,查找 Bar.prototype,最后在 Foo.prototype 原型对象上找到 identify 方法,然后调用。

实际上,在 JavaScript 中,类继承的本质依旧是原型对象。

他们的关系如下图所示:
https://static.littlewin.wang/blog/18-3.png

行为委托

https://static.littlewin.wang/blog/18-4.png
委托是说,我这里没有,但是你那里有,我就委托你帮我做这件事。
比如上面的原型图,对于对象 s1 而言,它有的仅仅是 nametype 两个属性,而没有 say() 方法,但最后却能调用 say() 成功,是因为,s1 没有这个方法,但是它的原型链上,Person.prototype 对象有 say() 方法,于是,没有这个方法没关系,我可以委托 Person.prototype 对象给做这这件事嘛。
所以,委托的本质,就是对象与对象之间的关键,这和原型链是一致的。
所以,如果我们用委托的思想来重新思考一下上面的代码,将构造函数去掉,只剩下原型对象呢:

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
var Person = { init:function(name){ this.name = name; }, say:function(){ console.log('my name is ' + this.name); } } var Student = Object.create(Person); Student.settup = function(name){ this.init(name); this.type = 'student'; } Student.sayHi = function(){ this.say(); } var s1 = Object.create(Student); s1.settup('coolcao'); s1.sayHi();

这段代码简洁了许多,我们只是把对象关联起来,并不需要那些既复杂又令人困惑的模仿类的行为(构造函数、原型以及 new)。
这里,Person,Student 是两个对象,而非函数。Student 的原型对象指向Person ,而 s1 对象的原型指向Student,因此三者构成了原型链:s1 -> Student -> Person,这里没有了构造函数,没有了类的误导,同样也可以很好的工作。
而且原型链关系更简洁,就是上面原型链图,去掉了构造函数 ,将Person.prototypeStudent.prototype替换为PersonStudent两个对象。

https://static.littlewin.wang/blog/18-5.png

对象 Students1 都是通过Object.create()创建,Student 委托了Persons1 委托了 Student
因此,在这里,使用委托去理解js的原型链更符合常规,更清晰易懂。

小结

我们从委托的角度,理解了一下js的原型链,发现一切都变的那么明朗。但是,为什么这么些年来,大家都用类的角度去分析,然后模拟类和继承来实现复杂的js代码呢?
其实很简单,用基于类的思想去组织管理代码,维护性更好,也更符合大型系统的开发思维。虽然如此理解js的原型有些偏颇
尤其ES6出了class和extends两个关键字后,代码看起来更像类式面向对象编程语言
但在理解上,把原型链理解为委托似乎更妥些
仁者见仁,智者见智

本文于 2015-11-4  发布在  编程  分类下, 当前已被围观 525 次

并被添加「 JavaScript 前端 」标签

本站使用「 署名 4.0 国际」创作共享协议