JS建立对象

建立对象,主要有工厂模式、构造函数模式、原型模式三种。本文主要分析了这三种模式的特色、利弊,以及一些细节问题。 参考:js高程 红宝书(第四版)数组

1、工厂模式

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");
复制代码
  • 优势:建立多个类似对象的问题
  • 缺点:没有解决对象识别问题(即新建立的对象是什么类型?)

2、构造函数模式

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

  • 没有显式地建立对象
  • 属性和方法直接赋值给了 this
  • 没有 return
  • 函数名 Person 的首字母是大写

2. 用 new 调用构造函数会执行以下操做:函数

  • 内存中建立一个新对象。
  • 这个新对象内部的 [[Prototype]]_ 特性被赋值为构造函数的 prototype 属性
  • 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
  • 执行构造函数内部的代码(给新对象添加属性)。
  • 若是构造函数返回非空对象,则返回该对象;不然,返回刚建立的新对象

3. 构造函数的问题:性能

  • 其定义的方法在每一个实例上都建立一遍,不一样实例的方法不是同一个 Function 实例(JS 中函数是对象,所以每次定义函数时,都会初始化一个对象。)
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

  • 虽解决了相同逻辑的函数重复定义问题,可是全局做用域被搞乱了。若这个对象须要多个函数,就须要在全局做用域中定义多个函数。
  • 该问题可用原型模式来解决。

3、原型模式

每一个函数都会建立一个 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 循环中使用。

  • 在单独使用时,in 操做符会在能够经过对象访问指定属性时返回 true,不管该属性是在实例上仍是在原型上。

若是要肯定某个属性 是否存在于原型上,则能够像下面这样同时使用 hasOwnProperty()和 in 操做符:

function hasPrototypeProperty(object, name){ 
    return !object.hasOwnProperty(name) && (name in object); 
}
复制代码
  • for-in 循环中使用 in 操做符时,能够经过对象访问能够被枚举的属性都会返回,包括实例属性和原型属性。

7. Object.keys():得到对象上全部可枚举的实例属性,返回一个字符串数组。

8. Object.getOwnPropertyNames():列出全部实例属性,不管是否能够枚举。

let keys = Object.getOwnPropertyNames(Person.prototype); 
console.log(keys); // "[constructor,name,age,job,sayName]"
复制代码

注意,返回的结果中包含了一个不可枚举的属性 constructor。

4、对象迭代

这两个静态方法Object.values()和 Object.entries()接收一个对象,返回它们内容的数组。

1. 其余原型语法:

function Person() {};

Person.prototype = {
    name: "Nicholas", 
    age: 29, 
    job: "Software Engineer", 
    sayName() { 
        console.log(this.name); 
    } 
};
复制代码

这样存在一个问题:Person.prototype 的 constructor 属性就不指向 Person 了。

  • 在建立函数时,会自动建立它的 prototype 对象,同时会自动给这个原型的 constructor 属性赋值。
  • 上面的写法彻底重写了默认的 prototype 对象,所以其 constructor 属性也指向了彻底不一样的新对象(Object 构造函数),再也不指向原来的构造函数。
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 
});
复制代码

2. 原型的动态性

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]] 指针是在调用构造函数时自动赋值的,指向最初建立的原型。(原型被重写后,即是另外一个原型对象了,会切断最初原型与构造函数的联系)

3. 原型的问题

(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
复制代码
相关文章
相关标签/搜索