第4章 构造函数和原型对象

由于JavaScript(ES5)缺乏类,但可用构造函数和原型对象给对象带来与类相似的功能。

4.1 构造函数

构造函数的函数名首字母应大写,以此区分其他函数。
当没有需要给构造函数传递参数,可忽略小括号:

var Person = {
    // 故意留空
}
var person = new Person;

尽管 Person 构造函数没有显式返回任何东西,但 new 操作符会自动创建给定类型的对象并返回它们。

每个对象在创建时都自动拥有一个构造函数属性(constructor,其实是它们的原型对象上的属性),其中包含了一个指向其构造函数的引用。
通过对象字面量形式({})或Object构造函数创建出来的泛用对象,其构造函数属性(constructor)指向 Object;而那些通过自定义构造函数创建出来的对象,其构造函数属性指向创建它的构造函数。

console.log(person.constructor === Person); // true
console.log(({}).constructor === Object); // true
console.log(([1,2,3]).constructor === Object); // true

// 证明 constructor是在原型对象上
console.log(person.hasOwnPrototype("constructor")); // false
console.log(person.constructor.prototype.hasOwnPrototype("constructor")); // true

尽管对象实例及其构造函数之间存在这样的关系,但还是建议使用 instanceof 来检查对象类型。这是因为构造函数属性可以被覆盖。(person.constructor = "")。

当你调用构造函数时,new 会自动自动创建 this 对象,且其类型就是构造函数的类型(构造函数就好像类,相当于一种数据类型)。

你也可以在构造函数中显式调用 return。如果返回值是一个对象,它会代替新创建的对象实例而返回,如果返回值是一个原始类型,它会被忽略,新创建的对象实例会被返回。

始终确保要用 new 调用构造函数;否则,你就是在冒着改变全局对象的风险,而不是创建一个新的对象。

var person = Person("Nicholas"); // 缺少 new

console.log(person instanceof Person); // false
console.log(person); // undefined,因为没用 new,就相当于一个普通函数,默认返回 undefined
console.log(name); // "Nicholas"

当Person不是被 new 调用时,构造函数中的 this 对象等于全局 this 对象。

在严格模式下,会报错。因为严格模式下,并没有为全局对象设置 this,this 保持为 undefined。

以下代码,通过 new 实例化 100 个对象,则会有 100 个函数做相同的事。因此可用 prototype 共享同一个方法会更高效。

var person = {
    name: "Nicholas",
    sayName: function(){
        console.log(this.name);
    }
}

4.2 原型对象

可以把原型对象看作是对象的基类。几乎所有的函数(除了一些内建函数)都有一个名为 prototype 的属性,该属性是一个原型对象用来创建新的对象实例。所有创建的对象实例(同一构造函数,当然,可能访问上层的原型对象)共享该原型对象,且这些对象实例可以访问原型对象的属性。例如,hasOwnProperty()定义在 Object 的原型对象中,但却可被任何对象当作自己的属性访问。

var book = {
    title : "book_name"
}

"hasOwnProperty" in book; // true
book.hasOwnProperty("hasOwnProperty"); // false
Object.property.hasOwnProperty("hasOwnProperty"); // true

鉴别一个原型属性

function hasPrototypeProperty(object, name){
    return name in object && !object.hasOwnProperty(name);
}

4.2.1 [[Prototype]] 属性

一个对象实例通过内部属性 [[Prototype]] 跟踪其原型对象。该属性是一个指向该实例使用的原型对象的指针。当你用 new 创建一个新的对象时,构造函数的原型对象就会被赋给该对象的 [[Prototype]] 属性。

Paste_Image.png

由上图可以看出,[[Prototype]] 属性是如何让多个对象实例引用同一个原型对象来减少重复代码。

Object.getPrototypeOf() 方法可读取 [[Prototype]] 属性的值。

var obj = {};
var prototype = Object.getPrototypeOf(Object);

console.log(prototype === Object.prototype); // true

大部分JavaScript引擎在所有对象上都支持一个名为 __proto__ 的属性。该属性使你可以直接读写 [[Prototype]] 属性。

isPrototypeOf() 方法会检查某个对象是否是另一个对象的原型对象,该方法包含在所有对象中。

var obj = {}
console.log(Object.prototype.isPrototypeOf(obj)); // true

当读取一个对象的属性时,JavaScript 引擎首先在该对象的自有属性查找属性名。如果找到则返回。否则会搜索 [[Prototype]] 中的对象,找到则返回,找不到则返回 undefined。

var obj = new Object();
console.log(obj.toString()); // "[object Object]"

obj.toString = function(){
    return "[object Custom]";
}
console.log(obj.toString()); // "[object Custom]"

delete obj.toString; // true
console.log(obj.toString()); // "[object Object]"

delete obj.toString; // 无效,delete不能删除一个对象从原型继承而来的属性
cconsole.log(obj.toString()); // // "[object Object]"

MDN:delete 操作符不能删除的属性有:①显式声明的全局变量不能被删除,该属性不可配置(not configurable); ②内置对象的内置属性不能被删除; ③不能删除一个对象从原型继承而来的属性(不过你可以从原型上直接删掉它)。

一个重要概念:无法给一个对象的原型属性赋值。但我们可以通过 obj.constructor.prototype.sayHi = function(){console.log("Hi!")} 向原型对象添加属性。

Paste_Image.png

(图片中间可以看出,为对象obj添加的toString属性代替了原型属性)

4.2.2 在构造函数中使用原型对象

在原型对象上定义公用方法
在原型对象上定义数据类型

开发中需要注意原型对象的数据是否共享。

function Person(name){
    this.name = name
}

Person.prototype.sayName = function(){
    console.log(this.name);
}

Person.prototype.position = "school";
Person.prototype.arr = [];

var person1 = new Person("xiaoming");
var person2 = new Person("Jc");

console.log("原始类型")
console.log(person1.position); // "school"
console.log(person2.position); // "school"

person1.position = 2; // 这是在当前属性设置position,引用类型同理
console.log(person1.hasOwnProperty("position")); // true
console.log(person2.hasOwnProperty("position")); // false

console.log("引用类型");
person1.arr.push("pizza"); // 这是在原型对象上设置,而不是直接在对象上
person2.arr.push("quinoa"); // 这是在原型对象上设置
console.log(person1.hasOwnProperty("arr")); // false
console.log(person2.hasOwnProperty("arr")); // false
console.log(person1.arr); // ["pizza", "quinoa"]
console.log(person2.arr); // ["pizza", "quinoa"]

上面是在原型对象上一一添加属性,下面一种更简洁的方式:以一个对象字面形式替换原型对象

function Person(name){
    this.name
}

Person.prototype = {
    sayName: function(){
        console.log(this.name);
    },
    toString: function(){
        return "[Person ]" + this.name + "]";
    }
}

这种方式有一种副作用:因为原型对象上具有一个 constructor 属性,这是其他对象实例所没有的。当一个函数被创建时,它的 prototype 属性也会被创建,且该原型对象的 constructor 属性指向该函数。当使用字面量时,因没显式设置原型对象的 constructor 属性,因此其 constructor 属性是指向 Object 的。
因此,当通过此方式设置原型对象时,可手动设置 constructor 属性。

function Person(name){
    this.name
}

// 建议第一个属性就是设置其 constructor 属性。
Person.prototype = {
    constructor: Person,

    sayName: function(){
        console.log(this.name);
    },
    toString: function(){
        return "[Person ]" + this.name + "]";
    }
}

构造函数、原型对象和对象实例之间的关系最有趣的一方面也许是:
对象实例和构造函数直接没有直接联系。(对象实例只有 [[Prototype]] 属性指向其相应的原型对象,而原型对象的 constructor 属性指向构造函数,而构造函数的 prototype 指向原型对象)

Paste_Image.png

4.2.3 改变原型对象

因为每个对象的 [[Prototype]] 只是一个指向原型对象的指针,所以原型对象的改动会立刻反映到所有引用它的对象。
当对一个对象使用封印 Object.seal() 或冻结 Object.freeze() 时,完全是在操作对象的自有属性,但任然可以通过在原型对象上添加属性来扩展这些对象实例。

4.2.4 内建对象(如Array、String)的原型对象

String.prototype.capitalize = function(){
    return this.charAt(0).toUpperCase() + this.substring(1);
}

总结

  • 构造函数就是用 new 操作符调用的普通函数。可用过 instanceof 操作符或直接访问 constructor(实际上是原型对象的属性) 来鉴别对象是被哪个构造函数所创建的。
  • 每个函数都有一个 prototype 对象,它定义了该构造函数创建的所有对象共享的属性。而 constructor 属性实际上是定义在原型对象里,供所有对象实例共享。
  • 每个对象实例都有 [[Prototype]] 属性,它是指向原型对象的指针。当访问对象的某个属性时,先从对象自身查找,找不到的话就到原型对象上找。
  • 内建对象的原型对象也可被修改
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容