MyException - 我的异常网
当前位置:我的异常网» JavaScript » JavaScript | 创造对象的9种方法详解

JavaScript | 创造对象的9种方法详解

www.MyException.Cn  网友分享于:2013-09-28  浏览:0次
JavaScript | 创建对象的9种方法详解

—————————————————————————————————————————————————————————

创建对象

标准对象模式

"use strict";
// *****************************************************************
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){alert(this.name);};

 

字面量形式

"use strict";
// *****************************************************************
var person = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function(){alert(this.name);}
};

 

工厂模式

  • 抽离了创建具体对象的过程,使用函数来封装以特定接口创建对象的细节
  • 优点:可以反复创建相似的对象
  • 缺点:无法进行对象识别

    <<script.js>>

"use strict";
// 工厂模式
function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        console.log(this.name);
    }
    return o;
}
var person1 = createPerson("name1", 1, "hehe");
console.log(person1);

 

构造函数模式

  • 优点:

    可以解决工厂作用的无法对象识别问题

    没有显示地创建对象,直接将属性和方法赋给了this对象,没有return语句

  • 缺点:

    在案例中,每个Person对象都包含一个不同的Function实例的本质,以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建Function新实例的机制仍是相同的。而如果将方法放到全局作用域中,自定义的引用类型就没有封装性可言

  • 通过new关键字来创建自定义的构造函数
  • 创建自定义的构造函数意味着将来可以将它的实例标识为一种特性的类型
  • 以该方法定义的构造函数是定义在Global对象中的
  • 调用构造函数实际操作步骤:
    • 创建一个新对象
    • 将构造函数的作用域赋给新对象(this指向该对象)
    • 执行构造函数中的代码(为新对象添加属性)
    • 返回新对象
"use strict";
// 构造函数模式
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        console.log(this.name);
    }
}
var person1 = new Person("name2", 2, "hehe");
console.log(person1);
// 检测对象类型
console.log(person1.constructor == Object); // false
console.log(person1.constructor == Person); // true
console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
// 当作构造函数使用
var person2 = new Person("name3", 3, "hehe");
person2.sayName();
// 作为普通函数调用
// Person("name4", 4, "hehe"); // 添加到window,严格模式下无法访问
// window.sayName(); // name4
// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "name5", 5, "111"); // 在对象o中调用
o.sayName(); // o就拥有了所有属性和sayName()方法
// 创建两个完成同样任务的Function实例是没必要的,有this对象在,不需要在执行代码前就把函数绑定到特定对象上面
console.log(person1.sayName == person2.sayName); // false,但方法是相同的
// 通过把函数定义转移到构造函数外来解决
function Person2(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName2;
}
function sayName2() { // 在这种情况下person1和person2共享同一个全局函数
    console.log(this.name);
}
var person1 = new Person2("name6", 6, "hehe");
var person2 = new Person2("name7", 7, "hehe");
console.log(person1.sayName == person2.sayName); // true

 

原型模式

  • 优点:

    可以解决构造函数模式创建多个方法实例的问题

    可以让所有对象实例共享原型所包含的属性和方法,不必在构造函数中定义对象实例的信息,而可以直接将信息添加到原型对象中

  • 缺点:

    原型中的所有属性是被很多实例共享的,对于包含引用类型值(数组等)的属性来说是(大问题)

    省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值,需要各自单独传入参数(这应该不是问题吧)

    所以很少有人单独使用原型模式,见下面的综合使用

  • 我们创建的每一个函数都有一个prototype(原型)属性,是一个指向对象的指针。
  • 关于原型的理解:

    任何时候创建一个新的函数,都会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。

    默认情况下,所有原型对象都会获得一个constructor(构造函数)属性,包含一个指向prototype属性的指针

    在实例中,Person.prototype.constructor Person,通过构造函数可以继续为原型对象添加其他的属性和方法

    创建自定义构造函数后,原型对象默认只取得constructor属性,其他方法从Object继承而来,原型指针叫[[prototype]],但在脚本中没有提供访问方式,在其他实现中这个属性不可见,但浏览器为对象增加了一个_proto_属性。

    原型指针的连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

    图解:

  • 关于原型的属性:

    实例:person1,原型:Person

    查找属性时先查找person1中的属性有没有name,如果有则返回person1.name的值,如果没有则查找原型Person中有没有name,参照原型链与对象的结构

  • in操作符和hasOwnProperty()区别:

    in操作符:无论属性是在实例还是原型中,都返回true,只有在不存在的情况下才会false

    hasOwnProperty(): 只有在调用的实例或原型中的属性才会返回true

  • 案例中整个重写原型的问题图解:

    <<script.js>>

"use strict";
// *****************************************************************
// 原型模式
function Person() {};
Person.prototype.id = 0;
Person.prototype.name = "name0";
Person.prototype.sayName = function() {
    console.log(this.name);
};

var person1 = new Person();
person1.sayName();
var person2 = new Person();
person2.name = "name2";
person2.sayName();
console.log(person1.sayName == person2.sayName);
Person.prototype.name = "111"; // 对原型中的初始值修改后,所有的子实例都会修改初始值
person1.sayName();
person2.name = "222";
person2.sayName();
delete person2.name; // 删除person2.name
person2.sayName(); // 111,来自原型

// *****************************************************************
// isPrototypeOf():确定原型关系的方法
console.log(Person.prototype.isPrototypeOf(person1)); // true
var person3 = new Object();
console.log(Person.prototype.isPrototypeOf(person3)); // false
// getPrototypeOf():返回原型[[prototype]]属性的方法
// in操作符
console.log(Object.getPrototypeOf(person2)); // 包含Person.prototype的对象
console.log(Object.getPrototypeOf(person2) == Person.prototype); // true
console.log(Object.getPrototypeOf(person2).name); // 111,初始值

// hasOwnProperty():检测一个属性是唉实例中还是在原型中
console.log(Person.hasOwnProperty("name")); // true
console.log(person1.hasOwnProperty("name")); // false 在上面的操作中没有为person1添加name
console.log("name" in person1); // true
person2.name = "333";
console.log(person2.hasOwnProperty("name")); // true
console.log("name" in person2); // true

// p.s.Object.getOwnPropertyDescriptor()方法必须作用于原型对象上
console.log(Object.getOwnPropertyDescriptor(person1, 'name')); // undefined
console.log(Object.getOwnPropertyDescriptor(Person, 'name')); // Object{...}

// *****************************************************************
// 简单写法
// 以对象字面量的形式来创建新的对象原型
// p.s.此时constructor属性不再指向Person,而是指向Object,因为此处重写了整个对象原型
function Per() {};
Per.prototype = {
    id: 0,
    name: "Per_name",
    sayName: function() {
        console.log(this.name);
    }
}
// 在该写法中要重写constructor属性,如果直接重写constructor属性会导致[[Enumberable]]=true,可枚举,原生的constructor属性不可枚举
// 正确的重写方法
Object.defineProperty(Per.prototype, "constructor", { enumberable: false, value : Per });
var per1 = new Per();
console.log(person1.constructor); // Person()
console.log(per1.constructor); // Per(),如果不加constructor:Per返回Obejct()

// 图解见上部
// 如果直接重写整个原型对象,然后在调用per1.sayName时候会发生错误,因为per1指向的原型中不包含以改名字明明的属性,而且整个重写的对象无法修改
// function Per() {};
// var per1 = new Per();
// Per.prototype = {
//     constructor:Per,
//     id: 0,
//     name: "Per_name",
//     sayName: function() {
//         console.log(this.name);
//     }
// }
// var per2 = new Per();
// per1.sayName(); // error
// per2.sayName(); // Per_name
// *****************************************************************
// 问题
// 对一个实例的数组进行操作时,其他所有实例都会跟随变化
function Per2() {};
Per2.prototype = {
    constructor:Per2,
    id: 0,
    name: "Per_name",
    arr : [1,2]
}
var per3 = new Per2();
var per4 = new Per2();
console.log(per3.arr); // [1, 2]
console.log(per4.arr); // [1, 2]
per3.arr.push("aaa");
console.log(per3.arr); // [1, 2, "aaa"]
console.log(per4.arr); // [1, 2, "aaa"]
console.log(per3.arr === per4.arr); // true

 

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

  • 是最常见的方式,构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。
  • 优点:

    每个实例都有自己的一份实例属性的副本, 同时又共享着对方法的引用,最大限度节省内存。

    支持向构造函数传递参数

    <<script.js>>

"use strict";
// *****************************************************************
// 组合使用构造函数模式和原型模式
function Person(id, name) {
    this.id = id;
    this.name = name;
    this.friends = [1, 2, '3'];
}
Person.prototype = {
    constructor: Person,
    sayName: function() {
        console.log(this.name);
    }
}
var person1 = new Person(1,"p1_name");
var person2 = new Person(2,"p2_name");
person1.friends.push("4");
console.log(person1.friends); // 1,2,3,4 不会相互影响
console.log(person2.friends); // 1,2,3
console.log(person1.friends === person2.friends); // false
console.log(person1.sayName === person2.sayName); // true 共用一个代码块

 

动态原型模式

  • 将所有信息都封装在构造函数中,通过在构造函数中初始化原型(必要情况下)由保持了同时使用构造函数和原型的优点
  • 优点:

    可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型

  • p.s.

    在该模式下不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,会切断现有实例和新原型之间的联系。

    <<script.js>>

"use strict";
// *****************************************************************
// 组合使用构造函数模式和原型模式
function Person(id, name) {
    this.id = id;
    this.name = name;
    this.friends = [1, 2, '3'];
    // 只有在sayName()方法不存在的情况下,才会将它添加到原型中
    // if这段代码只会在初次调用构造函数时才会执行
    // 这里对原型所做的修改,能够立即在所有实例中得到反映
    if (typeof this.sayName != "function") {
        Person.prototype.sayName = function() {
            console.log(this.name);
        }
    }
}
var person1 = new Person(1,"hugh");
person1.sayName();

 

寄生构造函数模式

  • 在前几种模式不适用的情况下,可以使用寄生(parasitic)构造函数模式
  • 创建一个函数,仅封装创建对象的代码,然后返回新创建的对象
  • 和工厂模式的区别:使用new操作,并把使用的包装函数叫做构造函数
  • 使用场景:假设我们想创建一个具有额外方法的特殊数组,由于不能直接修改Array构造函数,就可以使用这个模式(见代码)
  • p.s.

    返回的对象与构造函数或与构造函数的原型属性之间没有关系,也就是说,构造函数返回的对象与在构造函数外部创建的对象没有不同

    不能依赖instanceof操作符来确定对象类型

    如果可以使用其他模式的情况下,不要使用这种模式

    <<script.js>>

"use strict";
// *****************************************************************
function Person(id, name) {
    var o = new Object();
    o.id = id;
    o.name = name;
    o.sayName = function() {
        console.log(this.name);
    }
    return o; // 返回新创建的对象
}
var person1 = new Person(1, "111");
person1.sayName();

// 模拟使用场景
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","green");
console.log(colors);
console.log(colors.toPipedString());

 

稳妥构造函数模式

  • 所谓稳妥对象,指的是没有公共属性而且其方法也不引用this的对象
  • 使用场景:

    安全的环境中(这些环境会禁止使用thisnew

    防止数据被其他应用程序(如Mashup程序)改动时使用

  • 与寄生构造函数模式的区别:

    新建对象时不引用this

    不适用new操作符构造函数

  • 与寄生构造函数模式类似,该模式创建的对象与构造函数之间也没有什么关系,instanceof操作符也无意义

    <<script.js>>

"use strict";
// *****************************************************************
function Person(id, name) {
    var o = new Object();
    o.id = id;
    o.name = name;
    // p.s.在该模式下,除了sayName()方法外,没有其他办法访问name的值
    o.sayName = function() {
        console.log(name);
    }
    return o;
}
var person1 = Person(1, "111");
person1.sayName();
// console.log(personn1.name); // Error:person1 is not defined

文章评论

为什么程序员都是夜猫子
为什么程序员都是夜猫子
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
漫画:程序员的工作
漫画:程序员的工作
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
Java程序员必看电影
Java程序员必看电影
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
程序员和编码员之间的区别
程序员和编码员之间的区别
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
程序员应该关注的一些事儿
程序员应该关注的一些事儿
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
程序员的鄙视链
程序员的鄙视链
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
如何成为一名黑客
如何成为一名黑客
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
我的丈夫是个程序员
我的丈夫是个程序员
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
每天工作4小时的程序员
每天工作4小时的程序员
 程序员的样子
程序员的样子
老程序员的下场
老程序员的下场
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
旅行,写作,编程
旅行,写作,编程
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
中美印日四国程序员比较
中美印日四国程序员比较
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
那些争议最大的编程观点
那些争议最大的编程观点
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
10个调试和排错的小建议
10个调试和排错的小建议
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
我是如何打败拖延症的
我是如何打败拖延症的
编程语言是女人
编程语言是女人
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有