文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

概述

本文将探讨使用 JavaScript 创建对象的各种方式, 通过这个探讨来发现更多关于学习语言的更多的东西!

内容

  • 介绍
  • 普通对象
  • 构造函数
  • 'new' 的实现原理
  • 探索原型链和 instanceof
  • 原型在什么时候不是原型?
  • constructor属性
  • 关于 'this' 的疑惑
  • 私有对象
  • 利用“闭包”实现私有状态
  • 总结
  • 结论 

简介

随着基于web的应用程序不断普及以及一些插件的消逝(Flash,Siverlight,Java Applets, ...),越来越多的开发者发现他们正在使用JavaScript编写复杂应用程序.

第 1 段(可获 1.29 积分)

虽然表面看起来用过C#和Java编程的人会对JavaScript有一种熟悉的感觉,但是不久后你就会发现它们有相当多本质上的差异。不幸的是,对JavaScript来说,第一次接触时这种熟悉的感觉随后会变成困惑,这让JavaScript变得更不受欢迎。因此有相当多的不情愿的JavaScript开发人员!

让C#和Java开发者感到困惑的第一个JavaScript特性很可能就是它的原型继承,虽然JavaScript用了New这个关键字,想让Java开发者感到熟悉,但是这是个有点失败的尝试。

第 2 段(可获 1.49 积分)

很多开发者情愿借助一些具有继承功能的第三方框架诸如prototype.js之类的,以便可以按以前熟悉的方式编程,而不用自己去实现一个继承。我也一样。然而,很快我就因为对它的原理知之甚少而感到愧疚。

我起初本想写一篇关于javascript继承模式的文章,不久之后我发现即便是一个很简单的对象创建也是有很多地方可以深入研究的。

我知道你可能会说,创建一个javascript的对象?这再容易不过了,像这样子:

 

第 3 段(可获 1.23 积分)
var myObject = {}; 

对象怎么能写一整篇文章呢?

希望你的好奇心驱使你读完这篇文章然后找到答案。

普通的对象

JavaScript 是一门很简单的语言,只有几种内置的类型。在这一节中我们会详细研究对象类型,它是怎么创建的和它继承的属性。JavaScript 里对象只是属性的集合。属性由(唯一的)名和任意类型的值组成

对象是动态的,对象被创建以后,你可以增加和删除它的属性。下面的代码创建了一个“空”的对象,然后加入了几个属性:

第 4 段(可获 1.45 积分)
var shape = {};
shape.color = 'red';
shape.borderThickness = '2.0';

你也可以像下面这样子更简单地实现对象的初始化:

var shape = { 
    color : 'red',
    borderThickness : '2.0'
}

上面两种形式得到的对象是相同的。

我们还有第三种方式,是用Object的构造函数:

var shape = new Object(); 

这也是与上面其他两种方式等价的,然而我们忽略了一个问题!

我们所创建的这个对象到底长什么样子?以及它有什么属性?可以通过谷歌浏览器(或者IE、Safari、firefox等等)的开发者工具我们可以很容易地审查它。通常我们可以通过添加监视或者简单地在控制台输入它的名字来审查一个对象。

第 5 段(可获 1.36 积分)

这里就是那个shape的对象:

 

正如你所看到的它有两个属性,color和borderThickness,这是被显式地添加的。但是这个神秘的__proto__又是什么东西呢?

从它是用的下横线你或许会猜到它是一个私有属性。然而,众所周知javascript只有公有属性,谁也不能阻止你在代码里使用它们(即使有危险)。

__proto__ 这个属性指向了对象的prototype,打开这个对象之后,你会发现他有不少的函数。

第 6 段(可获 1.16 积分)

这揭露了JavaScript 真正的继承机制是围绕着原型链的。如果你试着访问一个不在对象中的属性(无论该属性是一个值还是一个函数),JavaScript 运行时就会检查这个对象的原型是否有这个属性。如果有,它就返回这个属性,否则会顺着原型链向上一直查找。

你可以试一下。在控制台中试着访问属性 toString (你可以用点方法或者索引方法),这个属性是一个函数,你可以调用它。

第 7 段(可获 1.45 积分)

虽然结果并不是十分有趣!

你可以看见,JavaScript 的对象十分简单,就是一个“袋子”,装着属性和允许属性继承的“隐藏的”原型。

JavaScript 与生俱来的动态性让创建对象变得十分简单,然而,大部分复杂的应用都是利用类型(在C#和Java语言中定义为类)。类型描述了对象的属性和方法,允许你创建多个类型的实例。

下一节中,我们会看看如何用JavaScript实现类型。

第 8 段(可获 1.16 积分)

关于术语最后一点 - JavaScript 有函数,但是在对象属性上的函数被称为方法。

构造函数

上一节中我们简要地讲了原型链以及对象如何继承属性和方法。一旦对象被创建,你就不能更改它的原型。怎样给它添加属性和方法呢?

构造函数解决了上述问题,在深入之前,我们先给对象添加一个函数,使例子变得更贴合实际:

 

var shape = { 
    color : 'red',
    borderThickness : '2.0',
    describe : function() {
       console.log("I am a " + this.color + " shape, with a border that is " +
           this.borderThickness + " thick");
    }
} 
第 9 段(可获 1.13 积分)

这给对象shape加了一个函数(也就是方法)。如果你用控制台审查,你会看见describe这个函数是一个属性,和其他东西一样。你可以通过控制台调用这个方法:

如果你正在写一个绘画应用,你可能想创建多个 shape 对象的实例。这时 JavaScrip 构造函数的概念就派上用场了。

我们会从创建一个构造函数开始,这个构造函数可以创建带有 color 和 borderThickness 属性的对象:

function Shape(color, borderThickness) { 
    this.color = color;
    this.borderThickness = borderThickness;
}
第 10 段(可获 1.06 积分)

你可以使用new关键字执行构造函数创建一个对象:

var shape = new Shape('red', 2.0); 

利用JavaScript开发者工具审查这个对象的属性:

构造函数执行完成会在创建的“shape”对象上添加两个属性,同对象初始化创建的“shape”对象相比,你会发现两者看起来似乎一样。然而有个微妙的区别,初始化创建的“shape”对象有个"隐藏”属性__proto__,Chrome指定为Object,而构造函数创建的“shape”对象的__proto__指向Shape。(注:随后你会明白,Chrome使用的这种指定方式!)

译者注:不知道原文作者的Chrome版本,我的机子上Chrome版本是 53.0.2785.113 m (64-bit)。通过构造器构造的对象的__proto__还是指向一个Object,即Shape函数的prototype属性,理论上也应该是Object。后文翻译还是以原作者的Chrome为准:构造函数实例的__proto__为Shape,初始化对象的__proto__为Object。

第 11 段(可获 1.41 积分)

在讨论这两者微妙的区别的意图之前,我们来为shape对象添加一个describe方法。

你可以在刚才创建的对象的构造函数上直接添加方法。然而,这种构造模式(译者注:指构造函数创建对象的方式)提供了一种更为有力的替代方式。你可以在构造函数的原型属性上添加方法。如下所示:

function Shape(color, borderThickness) { 
    this.color = color;
    this.borderThickness = borderThickness;
}

Shape.prototype.describe = function() {
    console.log("I am a " + this.color + " shape, with a border that is " +
        this.borderThickness + " thick");
}; 
第 12 段(可获 0.7 积分)

若你创建一个shape对象并审查此对象,你会发现如下所示代码:

现在你的shape对象具有describe方法,但这并非对象本身的属性,而是该对象原型上的一个属性。通过上文,我们明白一个定义在Object原型上的方法是如何通过原型链被实例继承的。同样,所有的Shape实例会继承describe方法:

展开Shape的__proto__属性所指的对象,会显示此对象位于原型链的底部:

因此,通过Shape构造器创建的任意对象都可以访问上述显示的所有方法。

第 13 段(可获 1.38 积分)

后续我们会深入介绍构造函数的原型属性,在那之前,让我们详细地看下new关键字。

‘new’的实现原理

你或许还记得我在前文说过对象的原型是不能更改的。当通过构造函数创建对象时,对象的原型会在此时设置,一旦构造完成,该对象的原型链就不可更改。

说句题外话,虽然对象的原型链不可更改,但并不意味着你不能给原型链上的对象添加方法。下面这个例子为Shape原型对象添加了一个新方法,演示了这个方法是如何立即被shape实例访问到:

第 14 段(可获 1.54 积分)

这强调了JavaScript的简单性,你可以修改任何对象的属性,无论是对象实例,抑或是代表类型概念的对象。

那么new关键字到底做了什么呢? 当用new关键字调用一个函数时,会做如下几件事儿:

  1. 创建一个新对象,并且设置原型(通过__proto__属性暴露出来)为构造函数的原型属性。
  2. this绑定为该对象,执行构造函数。
  3. 若构造函数没有返回值, 会隐式地返回this 。
第 15 段(可获 1.29 积分)

有趣的是,这里需要注意一点,new关键字才是这种构造模式的关键,而不是函数本身。

为了验证这一点,我们不用new关键字,直接调用构造函数:

执行这段代码发生了什么呢? 由于构造函数Shape没有明确的return语句,不会返回任何值,因此notAShape变量是个undefined。

可以简单地做如下修改:

function Shape(color, borderThickness) { 
    this.color = color; 
    this.borderThickness = borderThickness; 
    return this; 
}

现在用new关键字调用这个构造函数会和之前一样。但是如果我们再次省略new关键字呢?

第 16 段(可获 1 积分)

从第二行代码来看似乎可以不用new关键字了,你看返回的对象同样具有borderThickness ,color属性,但是第三行代码表明结果并非如我们预期。notAShape变量并非Shape的实例,实际上它是全局对象Window的一个引用。因为直接调用构造函数会按照ECMAScript标准规定来绑定this引用,所以上例中的this绑定为全局对象(译者注:在浏览器环境就是Window,在Node中就是global,如果你安装了Node可以在控制台输入node -e console.log(this)查看global对象的属性)。

到目前为止,正如你所看到的,构造函数必须通过new关键字来调用,否则结果并非如你预期那样。

这种脆弱的性质受到JavaScript开发者的关注,因为开发者很容易忘记使用new关键字,而这也是我们惯例以大写字母作为构造函数的开头以示提醒的原因。

第 17 段(可获 1.6 积分)

这种方式是相当安全的,我相信你也那么认为的(译者注:此处作者小小反讽了下这种约定方式,因为并非每个开发者会遵循这种约定,不管是有意还是无意^_^)。

好了嘲讽完了,我会在下一节介绍一种更为强大的方式来确保构造函数的正确使用。

这一节的关键信息是,构造函数只是一个规范的函数并没有什么特别的,起作用的是new关键字。

最后我要指出一点,在这个例子中color,borderThickness属性被添加到新创建的对象上,describe方法添加到了原型上,但你完全可以不按这种方式做。你可以在原型上添加属性,通过构造器添加方法。然而,原型上的属性是被所有实例共享的,就像在我们例子中,所有通过构造函数创建的shape实例,都会通过原型链共享同一个describe方法的引用。因此,在原型上添加的所有属性是共享的。这有点类似于静态属性的概念。

第 18 段(可获 2.21 积分)

探索原型链和instanceof

查询对象类型的能力是类型系统中重要的一个特征。你已经明白原型链是如何通过对象的__proto__属性暴露出来的。那么,你可以通过将对象的原型和构造函数的原型属性对比,来手动确认对象的的类型。

以shape对象为例,你会发现下面代码都会返回true:

shape.__proto__ == Shape.prototype
shape.__proto__.__proto__ == Object.prototype  

因为对象引用的原型是不可变的,所以可以认为上述代码总是返回true。JavaScript提供了instanceof关键字检查对象的原型,不需要你手动遍历原型链。

第 19 段(可获 1.33 积分)

对于shape对象,下面语句也总是返回true:

shape instanceof Shape;
shape instanceof Object; 

instanceof关键字会遍历原型链,如果发现给定的构造函数的原型属性在原型链中,就会返回true。

回忆上节中我们提到的,new关键做的第一件事,就是创建一个对象且将构造函数的原型属性设置为该对象的原型。因此,instanceof检查原型链时会返回true。我们可以用这种特性来防止调用构造函数时不带new关键字,代码如下:

 

第 20 段(可获 1.29 积分)
 function Shape(color, borderThickness) { 
    if (!(this instanceof Shape)) {
        return new Shape(color, borderThickness);
    }
    this.color = color;
    this.borderThickness = borderThickness;
}

上面的代码会检查this绑定是否是Shape的实例,如果不是则用new关键字调用构造函数,且显示返回创建的对象。

现在你可以确定不用new关键字来调用这个构造函数了:

个人而言,我不赞同这种方式。new关键字非常重要,我不大喜欢把它的用途隐藏在内部,当然你的想法或许不同。

第 21 段(可获 0.84 积分)

原型在什么时候不是原型?

你或许注意到了,在文章先前部分中,对于原型的讨论有点混乱:

“创建一个新对象,并且设置原型(通过__proto__属性暴露出来)为构造函数的原型属性。”

这句话的问题在于提到了两个不同的原型。一个是决定对象类型的“真实”原型,另一个只是构造函数的原型属性。这是两个不同的东西,但是共用同一个名字。个人而言,如果称呼原型属性为'constructedObjectsPrototype'会表述地更清楚,虽然有点冗长。

第 22 段(可获 1.51 积分)

在下面这张图中,标出了所有的对象以及它们之间的关联。前方预警,这个有点儿复杂。这方面的知识也并非必要的,所以如果你不喜欢可以跳过这一节。

这是张完整的对象图,红色表示内建对象,蓝色表示构造函数和它的原型,绿色表示shape实例:

从上面的构造函数开始,你可以看到它通过prototype属性与Shape.prototype对象关联。无论何时创建一个函数,它都会自动与一个原型对象关联起来,虽然只有在构造对象时才会用到这个对象。

第 23 段(可获 1.39 积分)

你会发现我们添加的describe方法是Shape.prototype的一个属性,shape实例(左边绿色部分)通过__proto__属性来引用这个对象,借此继承这个方法。

希望这帮助你了解了一个对象的原型与函数原型属性之间的差异,前者是不可变的,通过__proto__属性‘暴露‘出来,用于继承,后者会在构造对象时用到(译者注:浏览器不同,对象关联它的原型的方式可能不同,这里Chrome通过__proto__关联)。

现在我相信你会同意我说的,原型这个名字有点混乱。

那么在这张图中的其它对象是什么呢?JavaScript函数本身也是对象,这意味着函数也有原型。从上图中,你可以看到Shape构造函数的原型是Function.prototype,同样Function.prototype也有原型是Object.prototype。Object.prototype没有原型它的__proto__指向null,是原型链的末端。

第 24 段(可获 1.81 积分)

注意函数的原型链在创建时自动生成,你不必使用new关键字。

与Shape构造函数关联的原型链具有实际意义。审查这个函数,展开两个关联的__proto__属性,你可以看到它继承的函数:

Shape构造函数从Function.prototype对象中继承了bind,call和apply这几个非常有用的函数(稍后会更详细地介绍),还继承了原型链更深处的Object.prototype对象上的所有方法。

注意在Function.prototype和Object.prototype上都有toString属性。JavaScript运行时总是调用最先找到的属性值,这里就是Function.prototype.toString属性,它重写了Object.prototype对toString的实现。

第 25 段(可获 1.43 积分)

你可以自己尝试下。若调用shape实例的toString,你会看到它与标准实现有所不同。如果你想要,当然可以沿着原型链向上调用Object.prototype中的toString:

提醒下,__proto__是隐藏属性,它是JavaScript语言特定的实现细节。你不应该在生产代码中依赖这个属性。

constructor属性

你或许已经注意到了Shape.prototype,Function.prototype和Object.prototype都有一个constructor属性。如果你更进一步探索下,会发现constructor属性值是这个原型对象关联的构造函数的引用。

第 26 段(可获 1.43 积分)

把构造函数添加到图中就像下图:

那么这个属性的实际作用是什么呢?

在我们的例子中,Shape.prototype是所有shape实例的原型,它们都自动继承了这个constructor属性,令这些实例具有一个指向构造函数的引用。

你能通过以下代码来确认这点:

shape.constructor == Shape 

你可以使用这个属性作为创建新对象的一种方式。比如下面的代码通过constructor属性创建了一个字符串对象:

但我不认为这样做有什么用处。

第 27 段(可获 1.09 积分)

注意了, 对象的原型引用是不可变的,但是constructor属性只是个普通的属性,是可以更改的。你可以把它设置为任何值,比如设为其它函数:

你也可以更改String.prototype.constructor,那样上面的例子中可能就不是创建字符串对象了。

关于'this'的疑惑

现在我们已经深入剖析了对象是如何创建的,接下去看下这种对象创建形式的实际用途。

实际情境中经常会调用对象的方法来响应DOM事件。现在让我们来试下……

第 28 段(可获 1.36 积分)
// 创建shape实例
var shape = new Shape('red', 2.0);

// 调用describe
shape.describe();

// 将describe方法绑定为DOM事件响应
$('#foo').click(shape.describe); 

运行上面的代码,调用shape.describe方法后,控制台会按照我们预期输出:

I am a red shape, with a border that is 2 thick 

然而,当你点击id为‘foo’的元素时,结果并非如你预期:

I am a undefined shape, with a border that is undefined thick 

好糟糕!

你或许知道怎么解决这个问题,只要用bind函数,一些库或工具函数都可以解决。这里有个非常简单的解决方式…

第 29 段(可获 0.83 积分)

你只要把shape.describe的调用放到一个函数里就行了,就像下面这样:

$('#foo').click(function() { shape.describe(); });

你看现在这句代码就没问题了:

I am a red shape, with a border that is 2 thick 

困惑吗?我承认,我也是第一次遇到这种情况!

理解这里发生了什么能够帮你了解一点‘执行上下文’的概念。

所有的JavaScript代码都运行在执行上下文中。每个上下文会包含当前作用域内的变量和this绑定值。这些上下文组成一个堆栈,在大多数编程语言中这是很常见的。

第 30 段(可获 1.18 积分)

这里不会深入细节,你要注意的是,当调用一个方法时会创建一个新的执行上下文,this是这个上下文的一个属性。这一点不像C#,Java处理this的方式,也不像Object-C处理self的方式。你可以通过call或者apply调用函数设置this引用,也可以用bind来绑定this引用的对象。

那么这段代码为何失败了,

$('#foo').click(shape.describe); 

但是这样确可以呢?

$('#foo').click(function() { shape.describe(); });

在第一个例子中,shape.describe仅仅传递了一个describe函数的引用。点击事件直接调用这个函数,而不是通过shape对象来调用。结果这个函数在点击事件的执行上下文中运行。那样会把this设置为被点击的DOM元素,在大多数情况下,这么做会很方便。

第 31 段(可获 1.73 积分)

在第二个例子中,函数在shape对象的上下文中被调用。这是个方法调用,会创建一个新的引用了shape对象的执行上下文。(译者注:还记得我在上一段加粗的文字吗~)

还是有点疑惑?

上面的例子非常贴近实际场景,我们可以简化下,把describe函数拆下来直接调用:

正如所见,结果同上面一样。执行拆卸下来的describe函数并不会创建一个新的执行上下文,因此父级执行上下文会决定this的引用。

值得指出的是,这与是否使用构造函数无关。你会发现通过对象初始化方式创建的shape对象具有同样的行为:

第 32 段(可获 1.5 积分)
var shape = { 
    color : 'red',
    borderThickness : '2.0',
    describe : function() {
        console.log("I am a " + this.color + " shape, with a border that is " +
            this.borderThickness + " thick");
    }
} 

这个问题有个非常标准的解决方法,就是使用bind函数。这个函数先前在Function.prototype中看到过,它被所有的函数继承。bind方法会返回一个函数,当调用该函数时会以给定的值绑定this。在我们的例子中,希望this绑定为shape对象,因此可以那么做:

$('#foo').click(shape.describe.bind(shape));
第 33 段(可获 0.73 积分)

个人不那么喜欢这种方式。

私有部分

(不,不该说私有部分)最后,我想讨论的是关于JavaScript对象的私有属性和私有方法。面向对象编程中对象有个很重要的特性,就是可以隐藏状态与逻辑代码,以此暴露简单的接口。JavaScript没有一级语言的一个特征,就是它不支持把信息隐藏在创建对象的上下文中。

第 34 段(可获 1.14 积分)

那么我们如何用JavaScript创建私有状态呢?继续以‘shape’为例。若你希望访问describe方法内的私有状态,唯一可用的方法是伪私有变量:

function Shape(color, borderThickness) { 
    this.color = color;
    this.borderThickness = borderThickness;
    this._describeCount = 1;
}

Shape.prototype.describe = function() {
    console.log("I am a " + this.color + " shape, with a border that is " +
        this.borderThickness + " thick. I have told you this " + this._describeCount++ + " times.");
};
第 35 段(可获 0.48 积分)

你看到describeCount变量变成私有属性了吗?没有是吧?那当然了。这段代码巧妙地利用了开发者对下划线作为前缀的变量的盲点。(译者注:一般开发者会默认下划线开头的变量是私有变量)

玩笑归玩笑,这是实现私有变量和私有方法普遍接受的方式。

这也难怪早先的Java和C#开发者嘲讽JavaScript!

利用“闭包”实现私有状态

到目前为止,我已经集中讲了原型继承和构造函数,这恰恰是了解JavaScript语言特性的一种很好的方式。然而,JavaScript开发者并非仅仅用这种对象创建模式。

第 36 段(可获 1.4 积分)

当前Shape构造函数为新创建的对象添加了color borderThicknesss属性。没人能阻止你在构造函数里直接把describe函数直接添加到这个对象上。如下代码:

function Shape(color, borderThickness) {
    this.color = color;
    this.borderThickness = borderThickness;
    
    this.describe = function() {
        console.log("I am a " + this.color + " shape, with a border that is " +
            this.borderThickness + " thick");
    }
}  

那么这有什么区别吗?每个新建的Shape类型的实例都会有describe方法,但这是对象自身的方法,而非通过原型继承。

第 37 段(可获 0.81 积分)

这个改变造成的实际影响是每个Shape类型的实例具有自己的describe方法,然而每个实例都会增加对内存的消耗。

那这种方式有何优点?

这种方式的主要优点是信息隐藏。上面记录方法调用次数的例子,可以通过下面这种方式实现:

function Shape(color, borderThickness) {
    var describeCount = 0;

    this.color = color;
    this.borderThickness = borderThickness;
    
    this.describe = function() {
        console.log("I am a " + this.color + " shape, with a border that is " +
            this.borderThickness + " thick - ", describeCount++);
    }
}
第 38 段(可获 0.83 积分)

由于‘闭包’,describe函数能够在构造函数返回后还能访问describeCount 变量。若你用上面这个函数创建一个shape对象,然后用JavaScript开发者工具审查该对象,你会发现并没有describeCount属性,它真的被藏起来了。

闭包也可以用来解决JavaScript对象的this绑定问题。回忆一下,我之前演示过你必须小心DOM事件句柄的绑定。用上面这种方式创建对象,你可以保存同个闭包内创建的对象的引用,如下所示:

第 39 段(可获 1.36 积分)
function Shape(color, borderThickness) {
    var describeCount = 0,
        self = this;

    this.color = color;
    this.borderThickness = borderThickness;
    
    this.describe = function() {
        console.log("I am a " + self.color + " shape, with a border that is " +
            self.borderThickness + " thick. I have told you this " + describeCount++ + " times.");
    }
}

describe函数调用时总能通过self变量引用到它所属的对象,这是原型方式创建对象的另一个优势。这种方式唯一不好的地方是,你需要在每个公有函数内用self代替this。

第 40 段(可获 0.68 积分)

总结

在JavaScript中,对象创建并不是一个简单的主题!希望通读此文后,你会学到些关于构造函数,原型或JavaScript语言的其它内容。

我写过的原型方式和闭包方式创建对象都是完美可行的解决方案,我不会阻止你使用其中的任何一种,但我希望你花时间理解两者的区别,以及它们造成的影响。

最后,我一个同事想我介绍一个闭包的小变种:

第 41 段(可获 1.14 积分)
function Shape(color, borderThickness) {
    var describeCount = 0,
        instance = {};

    instance.color = color;
    instance.borderThickness = borderThickness;
    
    instance.describe = function() {
        console.log("I am a " + instance.color + " shape, with a border that is " +
            instance.borderThickness + " thick. I have told you this " + describeCount++ + " times.");
    }

    return instance;
}

上面这段代码的结果是什么呢?构造函数内创建一个新的‘instance’对象,且将属性和方法添加上去,然后返回该对象。这种写法的好处是使用了闭包去保护私有变量,而且在写代码的时候也不必用self去替代this(因为这很容易让人遗忘),所以维护起来也更方便。

第 42 段(可获 0.81 积分)

然而,这也并非没有缺点。‘instance’对象不是通过‘new’关键字创建的,因此Shape.prototype不会设为其原型,这就意味着instanceof对它就不管用了,就算你能利用constructor属性可变的特性来手动更改shape实例与Shape构造函数之间的关系。(译者注:var shape = new Shape; shape.constructor = Shape;)

结论

我在本文中偶尔嘲笑JavaScript语言。这有时也许有点不公平!

虽然它不像我们大多数现在使用的流行语言,但当你开始了解它后,会发现这并不是一门糟糕的语言。最重要的是,你在学JavaScript时不要试图去照搬C#或Java中先入为主的观点。

阅读愉快~

第 43 段(可获 1.75 积分)

文章评论