JS 创建对象的方法及各种方法的优劣比较 ----JS 学习笔记(四)

一、最简单的创建对象的两种方法

1. 创建一个 Object 的实例,然后在为他添加属性和方法:

var person = new Object();
person.name = "Jhon";
person.age = 23;
person.sayHi = function() {
    console.log("hello,", person.name);
}
// 调用对象的方法 person.sayHi() // hello,Jhon

2. 对象字面量的方法创建对象:目前比较常用的是这种对象字面量的方法

var person = {
    name: "Jhon",
    age:"23",
    sayhello : function () {
        console.log("Hello,", person.name)
    }
}
person.sayhello();

上面两种方法的缺点:虽然能用来创建单个对象,但是,使用一个接口创建很多对象,会产生大量重复的代码。为解决这个问题,产生了下面的各种创建对象的方法:

二、工厂模式,用函数来封装以特定接口创建对象的细节:

 

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayhi = function() {
        console.log("hello,", o.name)
    };
    return o;
}

var p1 = createPerson("Jack", "23", "doctor");
var p2 = createPerson("Mary", "11", "student");

console.log(p1 instanceof createPerson); // false
console.log(p1 instanceof Object); // true

工厂模式的缺点:虽然解决了创建多个相似对象的问题,但是没有解决对象识别问题。例如上面,只能判断实例的类型是 Object,但判断不出来是 createPerson。

三、构造函数模式:

 

function Person(name, age, obj) {
    this.name = name;
    this.age = age;
    this.obj = obj;
    this.sayHi = function () {
        console.log(`hello, ${this.name}`);
    }
}

var p3 = new Person("Jhon", "22", "doctor")
p3.sayHi();
var p4 = new Person("Tom", "22", "doctor")
console.log(p3.sayHi() === p4.sayHi()) // false
 

 

对比和工厂模式的不同:1. 没有显示的创建对象;2. 直接将属性和方法赋给了 this 对象;3. 没有 return 语句;4. 使用的时候要用 new 来创建一个新的实例。5. 解决了对象识别问题:constructor 属性指向 Person;或者用 p1 instanceof Person 进行检测。解决对象标识问题上,构造函数模式,优于工厂模式。

new 的过程经过四个步骤:1. 创建一个新的对象; 2. 将构造函数的作用域赋给新的对象(因此,this 就指向了这个新的对象。注意总结 this 的情况);3. 执行函数中的代码(为这个新的对象添加属性和方法); 4. 返回新的对象。或者这样理解:先把参数传进去,或者不传参数。函数执行的时候,函数里面的 this 先变成一个空对象,再在函数里面对 this. name this.age 进行赋值,赋值之后,返回这个 this。最后,再把存好地址赋值给 doctor 这个变量。

构造函数缺点:每个方法都要在每个实例上重新创建一遍。这样,不同实例上的同名函数式不相等的。解决方法1. 把所有实例共用的方法放在构造函数的外面 --- 全局作用域中。这样,所有的实例,都可以调用这个方法了。但随之而来的问题是:全局作用域中定义的方法实际上只对某几个实例调用,让全局作用域有点名不副实。而且,对象需要定义很多方法,那么就需要定义很多全局作用域的函数,那么我们自定义的这个引用类型就毫无封装性可言了。所以可以用原型模式来解决这个问题。

四、原型模式:每个函数都有 prototype(原型)属性,这个属性是一个指针,指向原型对象,这个原型对象里面包含了实例共享的属性和方法。prototype 就是调用构造函数而创建的实例的原型对象。使用原型对象的好处是,让所有对象实例共享原型对象里面的方法和属性。换句话说就是,不必在构造函数中定义实例的信息,而是将这些信息直接添加到原型对象中。

function Person() {

}

Person.prototype.name = "Jhon";
Person.prototype.age = "12";
Person.prototype.obj = "doctor";
Person.prototype.sayName = function() {
    console.log(this.name);
}

var p5 = new Person();
p5.sayName()  // "Jhon"
var p6 = new Person();
p6.sayName()  // "Jhon"

console.log(p5.sayName() === p6.sayName())  // true

理解原型对象

原型模式缺点:修改一个实例的引用类型(array,function)的属性,也会影响到另一个实例的引用类型属性。例如下面例子中的 friends 属性,修改了 p7 的,会影响到 p8。注意对于基本类型的属性,修改后不会有影响。在实例中添加一个同名的基本类型的属性,可以隐藏原型中对应的属性。解决方式,结合使用构造函数模式和原型模式。

function Person() {

}

Person.prototype = {
    name : "Jack",
    age : '23',
    friends : ["a", 'b']
}

var p7 = new Person();
var p8 = new Person();
p7.friends.push("Tom");
console.log(p7.friends);  // [ "a", "b", "Tom" ]
console.log(p8.friends);  // [ "a", "b", "Tom" ]

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

每个实例都会有一份自己的实例属性副本,但同时又共享着对方法的引用,最大限度的节省了内存。把每个实例不同的写入构造函数中,相同的写入 prototype 中。

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.friends = ['a', 'b']
}

Person.prototype = {
    sayhi : function() {
        console.log("hi,", this.name)
    }
}

var p9 = new Person('Jhon', "23")
var p10 = new Person('Tom', "12")

p9.friends.push("Jack");

console.log(p9.friends)   // [ "a", "b", "Jack" ]
console.log(p10.friends)  // [ "a", "b"]

 

posted @ 2019-04-30 10:31  碳烤牛肉干  阅读(199)  评论(0编辑  收藏  举报