3

【上一篇】:JavaScript对象内部属性及其特性总结

JS创建对象的七种模式

工厂模式(★★)

先在内部显示地创建一个临时对象,根据接收的参数来构建(赋值属性和方法)该对象,并返回该对象。
缺点:没有解决对象识别的问题(即无法确认一个对象的类型)。
function PersonFactory (name, age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function(){
        console.log(this.name)
    }
    return o;
}

var p1 = PersonFactory();
var p2 = PersonFactory();
p1 instanceof Object; // true
p2 instanceof Object; // true
p1 instanceof PersonFactory; // false  无法识别具体对象类型
p2 instanceof PersonFactory; // false

构造函数模式(★★★)

  1. 函数名首字母一般大写(如:Person,借鉴OO语言命名习惯)区别于其他函数;
  2. 构造函数本身也是函数,只不过可以用来参加对象而已;
  3. 每个新建的实例对象都有一个constructor属性,该属性指向构造函数自身Person
  4. 构造函数也可以被当做普通函数来调用;

    • 在全局作用域中调用一个函数时,this对象总是指向Global对象(浏览器中即window对象)
  5. 【优点】自定义构造函数可以将它的实例标识为一种特定的类型;
  6. 【缺点】构造函数的主要问题是,每个方法都要在每个实例上重新创建一遍;

调用构造函数实际经历4个步骤

  1. 创建(隐式地)一个新对象;
  2. 将构造函数的新对象赋值给新对象(此时this指向这个新对象);
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象(该构造函数的实例);

与工厂模式比较

  1. 没有显示地创建对象;
  2. 直接将属性和方法赋值给this对象;
  3. 没有return语句;
  4. 优点:可以确认具体对象类型;
function Person (name, age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    }
}
// 当做普通函数
var p1 = new Person('Tom', 20);
var p2 = new Person('Greg', 30);
p1 instanceof Object;         // true
p2 instanceof Object;         // true
p1 instanceof PersonFactory;  // true 可以确认具体对象类型
p2 instanceof PersonFactory;  // true

// 作为普通函数调用
Person('Green', 30);  // 属性和方法都被添加到window对象了
window.sayName();     // 'Green'

// 在另一个对象的作用域中调用
var obj = new Object();
Person.call(obj , 'Jim', 23);
obj .sayName(); // 'Jim'

原型模式(★★★)

  1. 构造函数变为空函数;
  2. 将所有属性和方法都添加到了构造函数的prototype属性中;

与构造函数模式比较

  1. 不同:新对象的属性和方法由所有实例共享(p1和p2访问的都是同一组属性和同一个函数);
function Person(){}

Person.prototype.name = 'Tom';
Person.prototype.age = 24;
Person.prototype.sayName = function(){
    console.log(this.name);
}

var p1 = new Person(), p2 = new Person();
p1.sayName();// 'Tom'
p2.sayName();// 'Tom'
console.log(p1.sayName === p2.sayName);// true

利用更简单的原型语法

  1. 每创建一个函数,就会同时创建它的prototype对象,该prototype对象也会自动获得constructor属性。
  2. 用对象字面量形式创建的对象,直接赋值给函数的原型对象(Person.prototype),本质上完全重写了其prototype对象,
  3. 因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。
  4. 此时instanceof操作符可以返回正确结果,但通过constructor已经无法确定对象类型了。
function Person(){}
Person.prototype = {
    // construtor : Person, // 需要重新指设constructor属性
    name : 'Tom',
    age : 24,
    sayName : function(){
        console.log(this.name);
    }
}

var p = new Person(); 
console.log(p instanceof Object); // ture
console.log(p instanceof Person); // ture
console.log(p.construtor == Object); // ture
console.log(p.construtor == Person); // false

安全地重设constructor

以上述方式重设constructor属性会导致它的[[Enumerable]]特性被设置为true;
默认情况下,原生的constructor属性是不可枚举的;
因此可利用Object.defineProperty()重设;
Object.defineProperty(Person.prototype, 'constructor', {
    enumerable : false,
    value : Person
});

组合模式(构造函数模式和原型模式)(★★★★)

  • 定义

    1. 组合使用构造函数模式和原型模式;
    2. 构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性;
  • 优点:

    1. 每个实例都会有自己的一份实例属性的副本,同时又共享着对方方法的引用,最大限度节省了内存;
    2. 这种混成模式支持向构造函数传递参数;
  • 目前使用最广泛、认同度最高的一种自定义类型的方法,可以说是用来定义引用类型的一种默认模式
// 实例属性均在构造函数中定义
function Person(name, age){
    this.name = name;
    this.age = age;
    this.friends = ['Nicholas', 'Jim'];
}
// 由所有实例共享属性constructor和方法则是在原型中定义的
Person.prototype = {
    construtor: Person,
    sayName: function(){
        console.log(this.name);
    }
}

var p1 = new Person('Tom', 25);
var p2 = new Person('Greg', 26);
p1.friends.push('Tony');
console.log(p1.friends); // ["Nicholas", "Jim", "Tony"]
console.log(p2.friends); // ["Nicholas", "Jim"]
console.log(p1.friends === p2.friends); // false
console.log(p1.sayName === p2.sayName); // true

动态原型模式(★★★★★)

  • 定义

    1. 动态原型模式把所有信息都封装在了构造函数中;
    2. 通过在构造函数中初始化原型(仅在必要的情况下,比如第一次新建实例时);
    3. 通过检查某个应该存在(原型中)的方法是否有效,来决定是否需要初始化原型;
  • 优点:保持了同时使用构造函数和原型的特点;

注意点:

  1. 下段代码中只会在初次调用构造函数时才会执行,此后原型已经完成初始化,无需再做修改;
  2. 这里对原型所做对修改,能够立即在所有实例中得到反映;
  3. if语句检查的可以是初始化之后应该存在的任何属性或方法,检查其中一个即可;
function Person(name, age){
    // 属性
    this.name = name;
    this.age = age;
    // 方法
    if(typeof this.sayName != 'function'){
        Person.prototype.sayName = function(){
            console.log(this.name);
        }
    }
}

寄生构造函数模式(★★)

  • 【定义】: 基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象
  • 【特点】

    1. 返回的对象与构造函数或者与构造函数的原型属性之间没有关系;
    2. 寄生构造函数返回的对象与在寄生构造函数外部创建的对象没有什么不同;
    3. 不能依赖instanceof操作符来确定对象类型;

与工厂模式的比较

  1. 除了使用new操作符并把使用的包装函数叫做构造函数之外,该模式和工厂模式其实是一模一样的;

与构造函数模式的比较

  1. 从表面上看,很像典型的构造函数;
  2. 构造函数在不返回值的情况下,默认会返回新对象实例
  3. 寄生构造函数通过在构造函数的末尾加一个return语句,重写了调用构造函数时返回的值

    • 此时返回的对象的__proto__属性指向Object;
function Person(name, age){
    // 创建临时新对象
    var o = new Object();
    // 初始化该对象
    o.name = name;
    o.age = age;
    o.sayName = function(){
        console.log(this.name);
    }
    // 返回该临时新对象
    return o;
}

var p1 = new Person('Tom', 25);
var p2 = new Person('Greg',30);
console.log(p1 instanceof Object); // true
console.log(p1 instanceof Person); // false
console.log(p1.sayName == p2.sayName); // false
console.log(p1.constructor == Object); //true

稳妥构造函数模式(★★)

所谓稳妥对象,就是指没有公共属性,而且其方法也不引用this的对象。
稳妥对象最适合在一些安全的环境中(这些环境会禁用thisnew),或者防止数据被其他应用程序改动时调用。

与寄生构造函数模式的比较

与寄生构造函数类似,但又不同:

  1. 一是新创建的对象实例方法不引用this;
  2. 二是不使用new操作符调用构造函数;
function Person (name, age){
    var o = new Object();
    // 可定义私有变量和函数
    // ...
    
    // 新创建的对象实例方法未引用this;
    o.sayName = function(){
        console.log(name)
    }
    return o;
}

var p = Person('Tom', 23);
p.sayName(); // 'Tom'
console.log(p instanceof Person); // false
  • 除了sayName()方法外,没有别的方法可以访问其数据成员;
  • 即使有其他代码给这个对象添加方法或数据成员,也不可能有别的办法访问传入到构造函数中的原始数据
  • 与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间也没有关系(故instanceof操作符对这种对象也无意义);

吉叶
46 声望4 粉丝