1. 对象字面量

通过这种方式创建对象极为简单,将属性名用引号括起来,再将属性名和属性值之间以冒号分隔,各属性名值对之后用逗号隔开,最后一个属性不用逗号隔开,所有的属性名值对用大括号括起来,像这样:

var o={"name":"yangyule","age":23,"sex":"male"};

属性名的引号可以是单引号也可以是双引号,大多数的时候属性名可以不加引号,但如果属性名为JavaScript关键字(保留字)的时候则必须加引号。大多数情况下最后一个属性之后加上逗号也没有问题,但是不建议这样做,因为这不符合ECMAScript规范,部分浏览器(IE7及之前的版本,Opera)将报错,可能导致代码无法运行。将上述代码加上一个函数作为属性如下:

var o = {"name":"yangyule",
"age":23,
"sex":"male",
"makeSelfIntroduction":function(){
console.log("My name is"+this.name);
console.log("I am "+this.age+" years old");
}
};

因为可读性更好,我们其实有些时候更愿意将对象字面量写作上述样式,在应用一些库的时候经常会在初始化的时候传入大量的参数,使用的就是这种方式。我们将代码完善一下:

var o={"name":"yangyule",
"age":23,
"sex":"male",
"makeSelfIntroduction":function(){
console.log("My name is"+this.name); //My name isyangyule
console.log("I am "+this.age+" years old"); //I am 23 years old
}
}; o.makeSelfIntroduction(); //此处输出 for(key in o){
console.log(key+" :"+o[key]); /*
name :yangyule
age :23
sex :male
makeSelfIntroduction :function (){
console.log("My name is"+this.name);
console.log("I am "+this.age+" years old");
}
*/
}

我省略了HTML部分的代码,运行结果以注释给出。以上代码的for部分输出了对象o的所有属性及属性值,在Java中读取未知对象的属性名的技术叫做反射,而在JavaScript中for就可以搞定。

这种方式创建对象简单明了,可读性好,但是缺点就是代码复用问题,当需要大量同类对象的时候重复代码将占据很大的一部分代码量,不易维护且效率极低。

2. Object构造函数

这种方式创建对象和C++、Java稍微有点儿类似,通过在new操作符之后加上Object构造函数创建,属性可以通过点运算符来添加,示例如下:

var o=new Object();
o.name="yangyule";
o.age=23;
o.sex="male";

如果对象的属性之前就存在,则将之前的的属性值替换当前赋值的属性值,例如在上述代码之后再次添加属性o.age=24,则此时o的age属性值为24。

这种创建对象的方式比较简便,也容易理解,但是缺点同样明显,如果需要大量的同类对象那意味着大量的重复代码,效率极低。

3. 工厂模式

工厂模式是软件领域一种广为人知的设计模式,JavaScript对象的创建也可以借用这种模式,如下:

function createPerson(name,age,sex,job){
var o=new Object();
o.name=name;
o.age=age;
o.sex=sex;
o.job=job;
o.sayName=function(){
console.log(this.name);
}
return o;
} var person1=createPerson("yangyule",23,"male","student"); for(key in person1){
console.log(key+": "+person1[key]);
}

工厂模式解决了创建多个对象代码重复的问题,但是缺点也颇为明显,就以上代码来说,每次调用createPerson函数,均会为每个对象创建一次sayName函数,换句话说,这个函数在每个Person中都有一份,而不是所有Person对象共享,在JavaScript中函数都是对象,这意味着每个Person对象都会占用一份函数的内存,造成了内存的浪费,这是其一,其二是对象识别的问题,即使用instanceof运算符无法得到对象的类型

4. 构造函数模式

前面我们介绍了通过Object构造函数创建对象的方式,其实除了使用Object构造函数之外还可以使用自定义构造函数创建对象,使用自定义构造函数的方法重写上一个例子:

function Person(name,age,sex,job){
this.name=name;
this.age=age;
this.sex=sex;
this.job=job;
this.sayName=function(){
console.log(this.name);
}
} var person1=new Person("yangyule",23,"male","student"); for(key in person1){
console.log(key+" :"+person1[key]);
}

以上代码中函数名Person首字母大写,这是一种约定,作为构造函数的函数首字母需要大写,普通函数首字母小写,遵循驼峰命名法。创建Person的实例的时候使用了new操作符,这种调用方式实际上经历了以下4个步骤:

1. 创建一个新对象

2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)

3. 执行构造函数中的代码(为这个新对象添加属性)

4. 返回新对象

以这种方式调用函数时还有一点需要说明,就是return语句。在这种情况下,如果函数中没有return语句,函数返回新创建的对象;如果有return语句,而且是return一个对象,则返回这个对象,如果return其它类型的值,则返回this。

function Person(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
return {};
}
var o=new Person("yangyule",23,"male");
console.log(o); //Object {}

这种方式创建的对象就具有了一定的标识,通过使用new操作符创建出来的对象可以通过instanceof操作符检查类型,例如以上代码中可以使用:

console.log(person1 instanceof Person)  //true
console.log(person1 instanceof Object); //true

这就是自定义构造函数方式优于工厂模式的地方,但是这种方式依然存在对象方法重复占用内存的问题,虽然可以将上述代码改为以下形式解决此问题:

function Person(name,age,sex,job){
this.name=name;
this.age=age;
this.sex=sex;
this.job=job;
this.sayName=sayName;
} function sayName(){
console.log(this.name)
} var person1=new Person("yangyule",23,"male","student");

但是又引发了另一个问题,构造函数没有了封装性,上述代码中仅仅有一个方法,如果方法有10个呢,100个呢,那么此时就完全没有必要使用构造函数的方式创建对象。

5. 原型模式

在JavaScript中,每个函数都是对象,都是通过Function()创建的,每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象就是函数的原型对象,这个原型对象默认有一个属性constructor,这个属性指向函数本身。而每个对象都有个内部属性,ECMA-262第五版管这个属性叫[[Prototype]],在脚本中并没有标准的访问这个属性的方式,但在Firefox,Safari,Chrome中可以通过__proto__(左右均为两个下划线)访问这个属性,这个属性指向的对象叫做这个对象的原型或者是原型对象,对象的原型指向创建这个对象的函数的原型对象。看如下代码:

function Person(name,age,sex,job){
this.name=name;
this.age=age;
this.sex=sex;
this.job=job;
this.sayName=function(){
console.log(this.name);
}
}
var person1=new Person("yangyule",23,"male","student");

以上代码中存在着这样的关系:

Person.prototype == person1.__proto__

在浏览器中验证:

console.log(Person.prototype == person1.__proto__); //true

具体的关于对象,原型对象,函数之间的关系请看我另一篇博客《JavaScript高级特性-对象、原型、函数之间的关系》 ,本文假设读者理解原型对象。

以上介绍的通过工厂模式和自定义构造函数的模式创建对象的方法均有一个问题,就是批量创建的同类对象,每个函数均会占用一份内存空间,造成内存的浪费。在java虚拟机中,对象的非静态内部属性都是在堆中,而对象的方法都在方法区,由同一个类创建的对象共享方法区的方法,所有同类对象均使用一份函数,极大的节省了内存空间。 在JavaScript中对象之间共享变量和方法就可以通过原型对象来实现,原因就是通过同一个构造函数创建的对象的原型对象就是构造函数的原型对象,换句话说就是所有通过同一个构造函数创建的对象的原型对象都是同一个对象,而这个对象就是构造函数的原型对象,通过给构造函数的原型对象添加属性从而实现属性和方法的共享。代码如下:

function Person(){
} Person.prototype.name="yangyule"
Person.prototype.age=23;
Person.prototype.sex="male"
Person.prototype.sayName=function(){
console.log(this.name);
} var person1=new Person();
var person2=new Person(); person1.sayName(); //yangyule
person2.sayName(); //yangyule

以上代码中person1的sayName输出yangyule,而person2的sayName也输出yangyule,这样就实现了对象之间属性的共享,在上述代码的底部添加如下代码:

person2.name="vile";

    person1.sayName();  //yangyule
person2.sayName(); //vile

输出结果以注释给出。出现上述结果的原因是,对象在访问自己的方法和属性时,优先搜索自己的属性和方法,自己的属性和方法中没有找到再去搜索原型的属性和方法,如果原型中也没有找到,就去原型的原型中搜索,一直沿着原型这条链搜索,直到找到匹配的为止,如果一直没有找到,如果要找的属性是变量就报undefined,方法就报TypeError。以上代码的第一句其实是给person2对象添加了一个自己的属性name,所以会输出vile。

还可以通过直接给函数的prototype属性赋值为一个自定义对象实现共享:

function Person(){
} Person.prototype={
name:"yangyule",
age:23,
sex:"male",
sayName:function(){
console.log(this.name);
}
}; var person1=new Person();
var person2=new Person();

此时的Person.prototype即为自定义的对象,但是此时Person原型对象中的constructor属性为Object()方法,而并非Person()方法,所以如果想让constructor属性指向Person(),我们可以这样做:

function Person(){
} Person.prototype={
constructor:Person,
name:"yangyule",
age:23,
sex:"male",
sayName:function(){
console.log(this.name);
}
};

这样就解决了问题。

以上代码虽然解决了对象间属性共享的问题,但是却只有共享属性没有私有属性,所以我们大多数情况下使用下一种方式创建对象——组合使用构造函数和原型模式。

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

这种方式是创建对象的最常用方式,对象私有的属性和方法用构造函数实现,需要共享的属性和方法用原型来实现,结合了构造函数方式和原型模式的优点,摒弃了这两种方式的缺点,示例代码如下:

function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
} Person.prototype.sayName=function(){
console.log(this.name);
} var person1=new Person("yangyule",23,"male");
var person2=new Person("vile",23,"male"); person1.sayName(); //yangyule
person2.sayName(); //vile

或者改为如下方式:

function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
} Person.prototype={
constructor:Person,
sayName:function(){
console.log(this.name);
}
}; var person1=new Person("yangyule",23,"male");
var person2=new Person("vile",23,"male"); person1.sayName(); //yangyule
person2.sayName(); //vile

7. 动态原型模式

在使用上面的组合构造原型方式创建对象时,对象的私有属性和共享属性并不在一个代码块中,换句话说,私有属性和共享属性并不能在构造函数中完成,必须把共享属性放到函数的外边,这对于其它OO语言的编程人员来说会感到很困惑,而动态原型模式可以很好的解决上述问题。

function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex; if(typeof this.sayName!= "function"){
Person.prototype.sayName=function(){
console.log(this.name);
}
}
} var person1=new Person("yangyule",23,"male");
var person2=new Person("vile",23,"male"); person1.sayName();
person2.sayName();

在构造函数中写if语句是为了判断是否是第一次执行Person()函数,如果是第一次执行Person()函数,if语句中typeof this.sayName的值为undefined,所以给Person()的原型对象添加函数sayName(),如果不是第一次执行,那表明函数Person()的原型对象已经有了sayName()函数,此时if语句中的typeof this.sayName值为function,所以不再执行语句块中的内容。这样就将共享的函数封装在了构造函数中。上述代码中即使有更多的函数也不需要为每个函数名执行一次判断,可以将所有的函数写在一个if语句块中,判断一次就可以。

8. 寄生构造函数模式

寄生构造函数模式和工厂模式的代码和思想是一模一样的,至于为什么叫工厂模式和寄生构造函数模式,我也不太清楚,因为我也没用过这种模式。它的大致思想就是在已有的对象上添加新的属性和方法但是不能修改原来的对象,例如,给数组添加一个新的方法,但又不能使所有的数组都拥有此方法,就可以使用这种模式:

function SpecialArray(){
var values =new Array();
values.push.apply(values,arguments); values.toPipedString= function(){
return this.join("|");
}; return values;
} var colors = new SpecialArray("red","blue","yellow");
console.log(colors.toPipedString()); //red|blue|yellow

以上的代码为数组对象添加了一个新的方法toPipedString,但是这个方法并不是所有数组都具有的。

9. 稳妥构造函数模式

所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全环境中(这些环境中会禁止使用 this和new)或者防止数据被其它应用程序修改时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同,一是新创建的对象的实例方法不引用this,二是不使用new操作符调用构造函数。示例如下:

function Person(name,age,job){
var o =new Object();
o.sayName=function(){
console.log(name);
}; return o;
} var me = Person("yangyule",23,"student");
me.sayName(); //yangyule

这种方式创建的对象很有特点,其实属性都不是这个对象的,而是该对象的上级作用域中的,也就是构造函数的(姑且这样叫吧,我也不知道该叫什么),很像闭包。这样创建对象在外部是没有办法直接访问它的属性的。

总结

这几种创建对象的方式中,最常用的也是最经典的莫过于组合构造原型和动态原型模式,我第一次阿里的面试就是栽在这个上面的/(ㄒoㄒ)/~~

JavaScript高级特性-创建对象的九种方式的更多相关文章

  1. JavaScript常见的创建对象的几种方式

    1.通过Object构造函数或对象字面量创建单个对象 这些方式有明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码.为了解决这个问题,出现了工厂模式. 2.工厂模式 考虑在ES中无法创建类( ...

  2. JavaScript 创建对象的七种方式

    转自:xxxgitone.github.io/2017/06/10/JavaScript创建对象的七种方式/ JavaScript创建对象的方式有很多,通过Object构造函数或对象字面量的方式也可以 ...

  3. JavaScript创建对象的几种 方式

    //JavaScript创建对象的七种方式 //https://xxxgitone.github.io/2017/06/10/JavaScript%E5%88%9B%E5%BB%BA%E5%AF%B9 ...

  4. JavaScript中创建对象的三种方式!

    JavaScript中创建对象的三种方式! 第一种 利用对象字面量! // 创建对象的三种方式! // 1 对象字面量. var obj = { // 对象的属性和方法! name: 'lvhang' ...

  5. JavaScript高级特性-数组

    1. JavaScript中的数组 在C++.Java中,数组是一种高效的数据结构,随机访问性能特别好,但是局限性也特别明显,就是数组中存放的数据必须是同一类型的,而在JavaScript中,数组中的 ...

  6. javascript高级特性

    01_javascript相关内容02_函数_Arguments对象03_函数_变量的作用域04_函数_特殊函数05_闭包_作用域链&闭包06_闭包_循环中的闭包07_对象_定义普通对象08_ ...

  7. javascript高级特性(面向对象)

    javascript高级特性(面向对象): * 面向对象: * 面向对象和面向过程的区别: * 面向对象:人就是对象,年龄\性别就是属性,出生\上学\结婚就是方法. * 面向过程:人出生.上学.工作. ...

  8. OOP 创建对象的7种方式

    JavaScript OOP 创建对象的7种方式   我写JS代码,可以说一直都是面向过程的写法,除了一些用来封装数据的对象或者jQuery插件,可以说对原生对象了解的是少之又少.所以我拿着<J ...

  9. JS 面向对象 ~ 创建对象的 9 种方式

    一.创建对象的几种方式 1.通过字面量创建 var obj = {}; 这种写法相当于: var obj = new Object(); 缺点:使用同一个接口创建很多单个对象,会产生大量重复代码 2. ...

随机推荐

  1. 没有安装hiredis

    在redis的发行包中的deps目录中就包含hiredis的源码,手动编译安装,或者自行下载一份.地址:hiredis的地址 cd /deps/hiredis make make install 然后 ...

  2. odoo开发笔记 -- 进入后台调试模式

    ./odoo-bin shell -d test1 -c /home/odoo/odooshare/odoo.conf ./odoo-bin shell -d 数据库名 -c 指定配置文件

  3. 细说spring事务配置属性

    一.spring事务配置 1.spring配置 在配置数据源的下方配置 <!-- 事务配置 --> <bean id="transactionManager" c ...

  4. docker storage driver

    docker默认有2种方式用于持久化数据,volumes和bind mounts,也可以使用tmpfs,其中使用volume是持久化数据的最好方式,volume由docker控制管理,使用docker ...

  5. zabbix 自定义监控项简单案例

    例如:获取被监控主机的登录用户数 以uptime为例: 输入命令:uptime | awk '{print $6}'  可以获得当前登录用户数(不通终端打印出的位置不同) 1.被监控主机修改zabbi ...

  6. #18 turtle模块

    前言 这一节继续记录模块,本节将记录Python中一个非常重要的画图模块——turtle,Here we go! 一.turtle模块 turtle(海龟)模块是Python中强大的内置画图模块,可以 ...

  7. RocketMQ的broker启动失败解决

    RocketMQ的broker用如下命令启动: nohup sh bin/mqbroker -n localhost:9876 & 使用jps查看,系统非常卡顿,broker的名字也未显示.使 ...

  8. SSH原理和应用

    SSH(Secure SHell)是为远程登录, 远程通信等设计的安全通信协议, 由芬兰研究员于1995年提出,其目的是用于替代非安全的Telnet.rsh.rexec等不安全的远程Shell协议. ...

  9. elasticsearch6.7 05. Document APIs(10)Reindex API

    9.REINDEX API Reindex要求为源索引中的所有文档启用_source. reindex 不会配置目标索引,不会复制源索引的设置.你需要在reindex之前先指定mapping,分片数量 ...

  10. 第一篇 Spring boot 配置文件笔记

    spring boot 不需要配置太多文件程序便可正常运行,特殊情况需要我们自己配置文件. 项目以IDEA写实例,系统会默认在src/main/java/resources目录下创建applicati ...