建立对象,主要有工厂模式、构造函数模式、原型模式三种。本文主要分析了这三种模式的特色、利弊,以及一些细节问题。 参考:js高程 红宝书(第四版)数组
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
};
return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
复制代码
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
复制代码
1. 构造函数模式 与 工厂模式 是比较像的,只是有以下区别:markdown
2. 用 new 调用构造函数会执行以下操做:函数
3. 构造函数的问题:性能
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("console.log(this.name)"); // 逻辑等价
}
复制代码
要解决这个问题,能够把函数定义 转移到 构造函数外部:ui
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
复制代码
构造函数内 sayName 属性中包含的只是一个指向外部全局函数的指针,因此 person1 和 person2共享了定义在全局做用域上的 sayName()函数。可是这样处理也是有弊端的:this
每一个函数都会建立一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。spa
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName(); // "Nicholas"
let person2 = new Person();
person2.sayName(); // "Nicholas"
console.log(person1.sayName == person2.sayName); // true
复制代码
1. isPrototypeOf():肯定 实例 和 原型对象 的关系;prototype
console.log(Person.prototype.isPrototypeOf(person1)); // true
console.log(Person.prototype.isPrototypeOf(person2)); // true
复制代码
2. Object.getPrototypeOf():返回参数的内部特性[[Prototype]]的值。指针
console.log(Object.getPrototypeOf(person1) == Person.prototype); // true
console.log(Object.getPrototypeOf(person1).name); // "Nicholas"
复制代码
3. Object.setPrototypeOf():向实例的私有特性[[Prototype]]写入一个新值。会严重影响代码性能,不推荐!能够使用Object.create()代替code
4. Object.create():建立新对象,并为其指定原型对象
let biped = {
numLegs: 2
};
let person = Object.create(biped);
person.name = 'Matt';
console.log(person.name); // Matt
console.log(person.numLegs); // 2,访问原型上的属性
console.log(Object.getPrototypeOf(person) === biped); // true
复制代码
5. hasOwnProperty()方法用于肯定某个属性 是在实例上 仍是在原型对象上。属性存在于调用它的对象实例上时返回 true
console.log(person1.hasOwnProperty("name")); // false
person1.name = "Greg";
console.log(person1.name); // "Greg",来自实例
console.log(person1.hasOwnProperty("name")); // true
复制代码
6. 原型和 in 操做符
in 操做符有两种使用方式:单独使用和在 for-in 循环中使用。
若是要肯定某个属性 是否存在于原型上,则能够像下面这样同时使用 hasOwnProperty()和 in 操做符:
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
复制代码
7. Object.keys():得到对象上全部可枚举的实例属性,返回一个字符串数组。
8. Object.getOwnPropertyNames():列出全部实例属性,不管是否能够枚举。
let keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // "[constructor,name,age,job,sayName]"
复制代码
注意,返回的结果中包含了一个不可枚举的属性 constructor。
这两个静态方法Object.values()和 Object.entries()接收一个对象,返回它们内容的数组。
function Person() {};
Person.prototype = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
复制代码
这样存在一个问题:Person.prototype 的 constructor 属性就不指向 Person 了。
let friend = new Person();
console.log(friend instanceof Object); // true
console.log(friend instanceof Person); // true
console.log(friend.constructor == Person); // false
console.log(friend.constructor == Object); // true
复制代码
别急,稍做修改便可:
function Person() {}
Person.prototype = {
// constructor: Person, 用这种方式恢复 constructor 属性会建立一个[[Enumerable]]为 true 的属性。而原生 constructor 属性默认是不可枚举的
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
// 恢复 constructor 属性
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
复制代码
let friend = new Person();
Person.prototype.sayHi = function() {
console.log("hi");
};
friend.sayHi(); // "hi",没问题!
复制代码
原型被重写后:
function Person() {}
let friend = new Person();
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
friend.sayName(); // 错误
复制代码
实例的 [[Prototype]] 指针是在调用构造函数时自动赋值的,指向最初建立的原型。(原型被重写后,即是另外一个原型对象了,会切断最初原型与构造函数的联系)
(1)原型主要有这两个问题:
因此实际开发中一般 不单独 使用原型模式 。
(2)原型共享特性 适用场景分析:
function Person() {}
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
friends: ["Shelby", "Court"],
sayName() {
console.log(this.name);
}
};
let person1 = new Person();
let person2 = new Person();
person1.friends.push("Van"); // friends属性是原型上的,该属性也会在其余实例上体现出来
console.log(person1.friends); // "Shelby,Court,Van"
console.log(person2.friends); // "Shelby,Court,Van"
console.log(person1.friends === person2.friends); // true
复制代码