JavaScript创建对象的几种方法

在JavaScript中,对象其实就是一组键值对的组合。

1、字面量对象(Object.Literals)

这是JS中创建对象的最简单、最常见的方法之一,只需要在花括号内定义属性及其值,如下所示:

let student = {name: 'Ross', rollno: 1};
// 或用Object构造
let person = new Object();
console.log(person); // {}
person.name = 'lisa';
person.age = 21;

这种方式会创建大量重复代码。

2、构造函数创建(Constructor Functions)

在构造函数之前,先来说说工厂模式:为了解决对象字面量在创建多个相似对象时,会产生大量重复代码的问题,于是有了工厂模式。

function createPerson (name, age) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.sayName = function () {
    console.log(this.name);
  };
  return o;
}
var person1 = new createPerson('zhang3', 29);
var person2 = new createPerson('li4', 2);

但是工厂模式也有不足,无法解决对象识别的问题,创建的所有实例都是 Object 类型,不知道是具体谁谁谁的实例。

构造函数创建的方式更多是用来在JS中实现继承、多态、封装等特性。构造函数是通过this为对象添加属性的。
比如,我们需要创建具有相同属性/结构集的多个实例,this关键字是指一个对象,该对象是执行当前代码位的任何对象。将new 关键字与函数名一起使用,将创建一个空对象,并且在该函数内部使用的this关键字将保留对该对象的引用。

function Animal (name) {
  this.name = name;
  this.say = function () {
    console.log(this.name)
  }
}
let cat = new Animal('Tom'); // Tom
let dog = new Animal('John'); // John
let lion = Animal('amy'); // undefined
//因为这里没有加new,所有属性都附加到了Windows对象。无法判断它是谁的实例,只能判断它是对象。

构造函数也有缺陷,就是其中的每个方法比如say(),在每次实例化时都会自动重新创建一遍,产生不同的作用域链,因此即使是同名函数也是不相等的,这样会造成资源浪费。比如:

let a1 = new Animal('zz')
let a2 = new Animal('ww');
console.log(a1.say === a2.say); // false

所以就有了原型模式,使用原型模式的好处就是可以让所有对象实例共享它所包含的属性和方法。

function Person () {
}
Person.prototype.name = 'zz';
Person.prototype.sayName = function () {
  console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // zz
var person2 = new Person();
person2.sayName(); // zz
console.log(person1.sayName === person2.sayName); // true

这里将sayName()方法和所有的属性都直接添加到了Person的prototype属性中,构造函数就成了空函数,但是也能调用构造函数创建新对象,新对象的属性和方法是所有实例共享的,也就是person1和person2访问都是同一组属性和同一个sayName()函数。但是原型模式也有缺点,当其中包含引用类型值属性时会出现问题,如下:

function Person(){
}

Person.prototype = {
    constructor: Person,
    name: 'zzx',
    age: '22',
    job: 'Programmer',
    friends: ['wc', 'rt'],
    sayName: function(){
        console.log(this.name);
    }
};

var person1 = new Person();
var person2 = new Person();

person1.friends.push('lol');
console.log(person1.friends); //[ 'wc', 'rt', 'lol' ]
console.log(person2.friends); //[ 'wc', 'rt', 'lol' ]

由于数组存在于Person.prototype中,当向数组中添加了一个字符串时,所有的实例都会共享这个数组。

组合使用构造函数和原型模式:是目前最常见的创建自定义类型对象的方式。构造函数用于定义实例属性,而原型模式用于定义方法和共享的属性。通过构造函数传递参数,这样每个实例都能拥有自己的属性值,同时实例还能共享函数的引用,最大限度节省了内存空间。如下:

function Person (name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype = {
  constructore: Person,
  sayName: function () {
    console.log(this.name)
  }
}
let person1 = new Person('king', 11);
let person2 = new Person('kkkk', 12);
console.log(person1.name); // king
console.log(person2.name); // kkkk
console.log(person1.sayName); // king
// 改变一个实例的属性值
person2.name = 'jing';
// 不影响另一个实例的属性值
console.log(person1.name); // king
console.log(person2.name); // jing

动态原型模式: 就是将原型对象放在构造函数内部,通过变量进行控制,只在第一次生成实例的时候进行原型的设置。相当于懒汉模式,只在生成实例时设置原型对象,其功能与构造函数和原型模式额混合模式是相同而。这是只有在sayName()不存在的情况下,才会将它添加到原型中。

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

    if (typeof this.sayName != "function") {
        Person.prototype.sayName = function () {
            console.log(this.name);
        };
    }
}

var person1 = new Person('zzx', 22, 'Programmer');
person1.sayName(); // zzx
3、Object.create()

ES5的新方法,我们可以使用Object.create()语法创建一个新对象,新对象的原型就是调用create方法时传入的第一个参数,第二个参数为添加的可枚举属性(自身属性)。如下:

// 例1
var a = {a: 1};
var b = Object.create(a); // b 的原型就是 a
console.log(b); // {}
b.__proto__ === a; // true
// 例2
// 把ross对象的属性挂到ross对象的原型上
var ross = Object.create(Object.prototype, {
  name: {
    value: 'ross',
    enumerable: true,
    writable: true,
    configurable: true
  },
  rollno: {
    value: 1,
    enumerable: true,
    writable: true,
    configurable: true
  }
});

对于每个属性,我们都将值、可枚举、可写和可配置的属性设置为true,使用对象文字或构造函数时,这自动为我们完成。
优点: 支持当前所有非微软版本或者 IE9 以上版本的浏览器。允许一次性地直接设置 proto 属性,以便浏览器能更好地优化对象。同时允许通过 Object.create(null)来创建一个没有原型的对象。

缺点:不支持 IE8 以下的版本;这个慢对象初始化在使用第二个参数的时候有可能成为一个性能黑洞,因为每个对象的描述符属性都有自己的描述对象。当以对象的格式处理成百上千的对象描述的时候,可能会造成严重的性能问题。

4、class创建

class关键字是ES6新引入的一个特性,它其实是基于原型和原型链实现的一个语法糖。

class Animal {
  constructor(name) {
    this.name = name
  }
}
let cat = new Animal('Tom');

Class 类中的constructor方法就相当于ES5中的构造函数,其实类中的所有方法都定义在了prototype上,prototype对象的constructor属性也指向class类本身,被所有实例共享。不同的是,class类只能通过new操作符调用,不能像ES5 的构造函数一样,当成普通函数调用。
constructor方法是类的默认方法,所有的类都有constructor方法,如果constructor方法没有被显式定义,js会自动添加一个空的。

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

推荐阅读更多精彩内容