JS 6.2 创建对象

6.2 创建对象

工厂模式

例子

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        alert(this.name);
    };
    return o;
}

var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

不能解决对象识别问题

构造函数模式

例子

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

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

1.可以instanceof识别对象

2.重复创建函数很浪费,补救方式

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

function sayName() {
    alert(this.name);
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

然而,明明是个全局函数却指定了只能由某个对象来调用,再者,封装 is a joke

原型模式

一个例子

function Person() {
    Person.prototype.name = "Nicholas";
    Person.prototype.age = "29";
    Person.prototype.job = "se";
    Person.prototype.sayName = function () {
        alert(this.name);
    }
}

var person1 = new Person();
person1.sayName(); // "Nicholas"

var person2 = new Person();
person2.sayName(); // "Nicholas"

alert(person1.sayName == person2.sayName); //true

1.创建一个新函数的同时,会根据一组特定的规则为该函数创建一个prototype属性,这个函数指向原型对象

2.所有的原型对象自动获得一个constructor属性,该属性包含一个指向prototype属性所在函数的指针,Person.prototype.constructor指向Person

3.调用构造函数创建一个新实例后,该实例的内部包含一个指针指向构造函数的原型对象

4.读取属性时,会先搜索实例自定义的属性,再查找原型对象的属性

hasOwnProperty()可以判断一个属性是否存在于实例中

delete关键字可以删除一个属性

delete person1.name;

in 可以判断是否能访问到该属性,无论是实例属性还是原型属性

alert("name" in person1);

Object.keys获取对象上所有可枚举的实例属性

function Person() {
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
    alert(this.name);
};

var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"

getOwnPropertyNames()获取所有实例属性,无论它是否可枚举

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"

更简单的原型语法

function Person() {
    
}

Person.prototype = {
  name: "",
  age: 29,
  sayName: function () {
      alert(this.name);
  }  
};

但是这种方式下constructor不再指向Person,补救方式

Person.prototype = {
  constructor: Person,
  name: "",
  age: 29,
  sayName: function () {
      alert(this.name);
  }  
};

但是这样的话呢,constructor会变成可枚举类型[Enumerable]为true,原生的constructor是不可枚举的, 补救方式

Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});
  • es5下使用defineProperty来设置constructor

原型的动态性

使用普通形式创建的原型对象,修改顺序不影响调用

var friend = new Person();
Person.prototype.sayHi = function () {
    alert("hi");
};
friend.sayHi(); //"hi"(没有问题!)

使用简化模式创建的对象则不然

function Person() {
}

var friend = new Person();
Person.prototype = {
    constructor: Person,
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function () {
        alert(this.name);
    }
};
friend.sayName(); //error

因为它改写了整个原型对象,实例拥有一个指向最初原型的指针,原型对象改写后,指针并不会智能指向新的原型对象

为原生对象定义新的方法

String.prototype.startsWith = function (text) {
    return this.indexOf(text) == 0;
};
var msg = "Hello world!";
alert(msg.startsWith("Hello")); //true

然而并不推荐这么做,把原型xjb加来加去除非这个原型所有的实现你都了解从而保证不会冲突和写重吧

原型对象的问题

1.省略了构造函数传递初始化参数的环节,所有实例在默认情况下拥有相同的属性值

2.引用类型的共享

function Person() {
}

Person.prototype = {
    constructor: Person,
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    friends: ["Shelby", "Court"],
    sayName: function () {
        alert(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true

显然,这并不是大部分情况下想要的结果

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

例子

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}

Person.prototype = {
    constructor: Person,
    sayName: function () {
        alert(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

这种方法目前来说认同度最高,应用最广泛

动态原型模式

例子

function Person(name, age, job) {
//属性
    this.name = name;
    this.age = age;
    this.job = job;
//方法
    if (typeof this.sayName != "function") {
        Person.prototype.sayName = function () {
            alert(this.name);
        };
    }
}

var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

1.把原型也写在构造函数中,为了避免重复构建,只有sayName不存在时才会执行

2.这里对原型的修改能够立即在实例中得到反馈

3.这种模式创建的对象可以使用instanceof操作符确定它的类型

4.不可以使用对象字面量重写原型,也就是简化模式不可以

寄生构造函数模式

除了使用new操作符并把包装函数叫做构造函数以外,这个模式跟工厂模式其实一模一样

所以不做赘述,例子

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");
alert(colors.toPipedString()); //"red|blue|green"

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

稳妥构造函数模式

稳妥对象:没有公共属性,其方法也不引用this的对象

稳妥对象最适合在一些安全的环境中,这些环境中会禁止使用this和new,或者在防止数据被其它应用程序如Mashup程序改动时使用

稳妥构造函数与寄生构造函数类似,有两点不同:

1.新创建对象的实例方法不引用this

2.不适用new操作符调用构造函数

例子

function Person(name, age, job) {
//创建要返回的对象
    var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
    o.sayName = function () {
        alert(name);
    };
//返回对象
    return o;
}

除了调用sayName()以外没有别的方式可以访问到其数据成员

当然,这个也不能用instanceof去判断对象