JavaScript 创建对象(工厂、构造函数、原型)

一 工厂模式创建对象

在 ECMAScript 中可以使用工厂模式来创建对象

function createPerson(name,age) {
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayHello = function () {
        console.log("hello");
    };

    return obj;
}

let person = createPerson(“acey",22);
person.sayHello();

createPerson 函数可以接收参数来构造对象所需要的信息,可以被无数次的调用,每次调用都会返回一个唯一的对象,但我们无法辨认出我们创建的对象是什么类型。随着 JavaScript 的发展,构造函数模式就出现了。

二 构造函数模式

使用构造函数模式改造前面的栗子

function Person(name,age) {
    this.name = name;
    this.age = age;
    this.sayHello = function () {
        console.log("hello");
    };
}

let person1 = new Person("acey1",22);
let person2 = new Person("acey2",22);

在这个栗子中, Person 函数取代了 createPerson 函数,构造函数模式创建对象的特点在于

  • 没有显示的创建对象
  • 直接将属性和方法赋值给 this
  • 没有 return 语句

使用构造函数创建对象,在实例化对象的时候必须使用 new 来创建,创建的过程如下

  1. 创建一个新对象
  2. 将构造函数的作用域赋值给该对象(this 就指向了该对象)
  3. 执行构造函数中的代码
  4. 返回新对象

其中 person1person2 分别着 Person 的一个不同的实例,它们都有一个 constructor (构造器)属性,且该属性都指向 Person

console.log(person1.constructor === Person); //true
console.log(person2.constructor === Person);//true

构造函数和普通函数最大的区别就是创建的方式使用了 new 关键字

//构造函数
let person1 = new Person("acey1", 22);
/普通函数
Person("acey", 22);

person1.sayHello();
sayHell(); //Global

使用 new 来创建一个函数时,this 会指向创建的对象本身(Person),如果不用 new 创建,那么 this 会指向 Global 对象(在浏览器中就是
window 对象),这个时候我们就可以全局调用。

构造函数虽然好用,但还是存在一个问题,当我们每创建一个对象,那么对象中的方法也会被重新创建一遍,这样就会导致不同的作用域链和标识解析。

console.log(person1.sayHello() === person2.sayHello())//false

想要避免这种情况,我们需要将对象中的方法全局化,这个时候可以使用原型模式来创建对象。

三 原型模式

浅析

我们创建的每一个函数都会有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的作用就是包含由特定类型的所有实例所共享的属性和方法。
比如前面创建 person1 函数时,该函数就会有一个 prototype 属性,而这个属性指向的对象就是 Person 对象。

使用原型模式改写上面的栗子

function Person() {

}
Person.prototype.name = "acey";
Person.prototype.age = 22;
Person.prototype.sayHello = function () {
    console.log("hello");
};

let person1 = new Person();
person1.sayHello(); //hello
let person2 = new Person();
person2.sayHello();//hello
console.log(person1.sayHello === person2.sayHello);//true

在这我们将name、age、sayHello 直接添加到了 Person 中的prototype 属性中,personprototype 中的属性都被 Person 的实例化对象所共享。

理解原型对象

每当我们创建一个函数,就会根据一种特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象都会有一个 constructor 属性,这个属性指向 prototype 属性所在的函数的指针。Person.prototype.constructor 就指向 Person

当调用一个构造函数创建一个实例后,该实例就会获得一个指针叫[[Prototype]](内部属性),该指针指向构造函数的原型对象。在 FireFox、Safari 和 Chrome 是 _proto_。但在其它实现中这个属性是完全不可见的。要记住一点就是,这个连接是存在实例与构造函数的原型对象之间的,而不是实例与构造函数之间的

我们可以使用 isPrototypeOf() 方法来判断对象是否是同一个原型对象

console.log(Person.prototype.isPrototypeOf(person1));//true

在 ECMAScript 5 中有一个方法 Object.getPrototypeOf() 可以返回某个对象的原型

console.log(Object.getPrototypeOf(person1).name); //acey

属性的搜索

每当读取某个属性时,都会进行一次搜索。搜先会搜索对象实例本身,如果搜索到了该具体名字的属性,就会返回该属性的值,如果没有搜索到,那么就会继续搜索指针指向的原型对象,在原型对象中如果搜索到的话就会返回属性值。

可以发现,在搜索的时候是先搜索的是对象实例本身,然后才是原型对象。如果在对象本身和原型对象含有相同的属性,那么原型对象中的属性和方法就会被对象实例中相应的属性和方法所覆盖。(可以使用 delete 操作符来删除对象中的属性和方法, delete person1.name;

相关方法

  • hasOwnProperty() :检测某个属性是否存在与某个实例中。
person2.hasOwnProperty("sayHello");//true
  • in 操作符:检测属性是否存在与对象实例或是原型对象中
console.log("name" in person1);//true
  • Object.keys():接收一个对象作为参数,返回该对象所有可枚举属性的字符串数组
let keys = Object.keys(Person)

原型对象存在的问题

原型对象省略了构造函数传递参数初始化的步骤,结果导致所有的实例都会共享相同的属性,这是非常不方便的。如果创建的属性而引用类型的话,那么会造成不同的实例的混乱。

四 组合使用构造函数和原型模式

最佳的方式应该是这样。构造函数用来定义属性,而原型模式定义共享的属性和方法。这样每个实例都会有一份实例属性副本,又同时含有一份共享的属性和方法,这样最大限度的节省了内存。上面的栗子改写成

function Person(name,age) {
    this.name = name;
    this.age = age;
}

Person.prototype.say = function () {
    console.log(this.age)
};

let p1 = new Person("acey", 22);
let p2 = new Person("acey1", 22);

console.log(p1.say === p2.say);//true

当然了这种方式并不是很完美的,因为构造函数和原型两个独立起来了。我们动态原型模式将其合并起来。

function Person(name,age) {
    this.name = name;
    this.age = age;

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

推荐阅读更多精彩内容