黑马程序员技术交流社区

标题: javascript创建对象的几种方式? [打印本页]

作者: 啦啦啦啦lll    时间: 2020-12-6 09:14
标题: javascript创建对象的几种方式?
0、Object 构造函数或对象字面量
可以用来创建单个对象。
但要创建很多对象,就会产生大量的重复代码。
// 创建 Object 的实例
var person = new Object();
person.firstname = 'Captain';
person.lastname = 'Flag';
person.sayName = function sayName () {
    console.log(this.firstname + ' ' + this.lastname);
};

// 字面量
var person = {
    firstname: 'Captain',
    lastname: 'Flag',
    sayName: function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    }
};
1、工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程,考虑到在 ECMAScript 中无法创建类,开发人员
就发明了一种函数,用函数来封装以特定接口创建对象的细节
function createPerson (firstname, lastname) {
    var o = new Object ();
    o.firstname = firstname;
    o.lastname = lastname;
    o.sayName = function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    };
    return o;
}

var cap = createPerson('Captain', 'Flag');

优点:解决了创建多个相似对象的问题。
缺点:没有解决对象识别的问题(即怎样知道一个对象的类型)。

2、构造函数模式
function Person (firstname, lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
    this.sayName = function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    };
}

var cap = new Person('Captain', 'Flag');
new 操作符调用构造函数的过程:

创建一个新对象;
将新对象的[[protorype]]设置为Person.prototype;
构造函数Person被传入参数并调用(执行构造函数中的代码,为这个新对象添加属性),关键字this被设定指向新对象;
除非函数Person显性返回一个对象,否则自动返回新对象。



优点:
有解决对象识别的问题,可以将实例标识为一种特定的类型。


缺点:
无法进行函数复用。(以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建 Function 新实例的机制仍然是相同的。因此,不同实例上的同名函数是不相等的。但创建两个完成同样任务的 Function 实例的确没有必要。)
解决:把函数定义转移到构造函数外部。
function sayName () {
    console.log(this.firstname + ' ' + this.lastname);
}
function Person (firstname, lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
    this.sayName = sayName;
}

3、原型模式
可以当作构造函数的function都有一个属性prototype,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
function Person () {}
Person.prototype.firstname = 'Captain';
Person.prototype.lastname = 'Flag';
Person.prototype.sayName = function sayName () {
    console.log('Captain Flag');
};

var cap = new Person();

// 更简单的原型语法
function Person () {}
Person.prototype = {
    // 这种写法会将默认的Person.prototype完全覆盖重写,
    // Person也不会有`constructor`属性
    // `Person.prototype.hasOwnProperty('constructor')`的结果为false
    // 如果仍需要`constructor`属性,自行增加
    constructor: Person,
    firstname: 'Captain',
    lastname: 'Flag',
    sayName: function sayName () {
        console.log('Captain Flag');
    }
};

// 以上一种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true
// 默认情况下,原生的 constructor 属性是不可枚举的
// 兼容 ECMAScript 5 的 JavaScript 引擎上,可用 Object.defineProperty() 修复
function Person () {}
Person.prototype = {
    firstname: 'Captain',
    lastname: 'Flag',
    sayName: function sayName () {
        console.log('Captain Flag');
    }
};
    // 重设constructor属性,只适用于 ECMAScript 5 兼容的浏览器
Object.defineProperty(Person.prototype, 'constructor', {
    enumerable: false,
    value: Person
});
后两者写法,都直接重写了原型,如果有实例在原型重写之前生成,这些实例与更改后的原型是没有联系的。


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


缺点:

不能传递参数。
原型模式的最大问题是由其共享的本性所导致的。原型中所有属性是被很多实例共享的。

这种共享十分适用于函数,
对于原始数据类型尚可,
但引用类型在原型中也只是保留了一个指针,在实例中被修改后,会影响原型的值。function Person () {}
Person.prototype.arr = [1,2,3];

var cap = new Person();
cap.arr.push('a');
console.log(Person.prototype.arr); // [1, 2, 3, "a"]

4、组合使用构造函数模式和原型模式
模式   所定义的内容
构造函数模式  实例属性
原型模式  方法和共享的属性

定义引用类型的一种默认模式。
function Person (firstname, lastname) {
    this.name = [firstname, lastname];
}
Person.prototype.sayName = function sayName () {
    console.log(this.name.join(' '));
};

var cap = new Person('Captain', 'Flag');
cap.name.push('1911');
cap.sayName(); // Captain Flag 1911


优点:支持向构造函数传递参数。每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。

5、动态原型模式
function Person (firstname, lastname) {
    this.name = [firstname, lastname];
    // 只在 sayName() 方法不存在的情况下,才会将它添加到原型中。
    // 这段代码只会在初次调用构造函数时才会执行。
    // 此后,原型已经完成初始化,不需要再做什么修改了。
    // 其中, if 语句检查的,可以是初始化之后应该存在的任何属性或方法,
    // 并且不必检查每个属性和每个方法,只要检查其中一个即可。
    if (typeof this.sayName !== 'function') {
        Person.prototype.sayName = function sayName () {
            console.log(this.name.join(' '));
        };
    }
}

var cap = new Person('Captain', 'Flag');
cap.name.push('1911');
cap.sayName(); // Captain Flag 1911


优点:
有其他 OO 语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常困惑。动态原
型模式正是致力于解决这个问题的一个方案,它把所有信息都封装在了构造函数中,而通过在构造函数
中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过
检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

对于采用这种模式创建的对象,还可以使用 instanceof 操作符确定它的类型



6、寄生构造函数模式
基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数。
function Person (firstname, lastname) {
    var o = new Object ();
    o.firstname = firstname;
    o.lastname = lastname;
    o.sayName = function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    };
    return o;
}

var cap = new Person('Captain', 'Flag');
工厂模式 + 构造函数(除了使用 new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。)
构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个 return 语句,可以重写调用构造函数时返回的值。

优点:
在特殊的情况下用来为对象创建构造函数。
比如:创建一个具有额外方法的特殊数组。由于不能直接修改 Array 构造函数,因此可以使用这个模式。

缺点:
返回的对象与构造函数或者与构造函数的原型属性之间没有关系。不能依赖 instanceof 操作符来确定对象类型。

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

一是新创建对象的实例方法不引用 this;
二是不使用 new 操作符调用构造函数。

function Person (firstname, lastname) {
    // 创建要返回的对象
    var o = new Object ();
    // 定义私有变量和函数
    o.firstname = firstname;
    o.lastname = lastname;
    // 添加方法
    o.sayName = function sayName () {
        console.log(this.firstname + ' ' + this.lastname);
    };
    // 返回对象
    return o;
}

var cap = Person('Captain', 'Flag');

优点:
安全性高,使得它非常适合在某些安全执行环境。

缺点:
与寄生构造函数模式类似,创建的对象与构造函数之间也没有什么关系,因此 instanceof 操作符对这种对象也没有意义。




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2