JavaScript高级程序设计之创建对象模式的理解

132 阅读6分钟

例子:

// 采用new 创建单个对象 对象字面量方式同理  
var p1 = new Object();
p1.name = "A";
p1.age = 18;
p1.sayName = function(){
    console.log(p1.name);
}

var p2 = new Object();
p2.name = "B";
p2.age = 19;
p2.sayName = function(){
    console.log(p2.name);
}
p1.sayName();   // A
p2.sayName();   // B

// 缺点:如果我还要在创建姓名分别为 "C" "D"  "E"的同学 岂不是有大量的重复代码

缺点

创建相同结构的对象,会产生大量的重复代码。

工厂模式

优点

解决了前面提到的 创建多个相同结构对象的问题

缺点

但是不能识别对象具体是什么类型了

例子:

 function Person(name,age) {
    var o = new Object();   // << 1 >>  这里是new Object()
    o.name = name;
    o.age = age;
    o.sayName = function (){
        console.log(o.name);
    }
    return o;    
}

var p1 = Person("A",18);
var p2 = Person("B",19);
p1.sayName();  // A
p2.sayName();  // B
// 以上解决了创建多个相似对象的问题  创建姓名为"C"的同学 var p3 = Person("C",19);

//但是怎么知道对象的类型?
console.log(p1 instanceof Person); // false  原因 见 << 1 >> 处
console.log(p1 instanceof Object); // true

构造函数模式

优点

解决创建多个相似对象 及 识别对象类型的问题

// 跟工厂模式的差异之处 <1>  <2>  <3>  <4> 
function Person(name,age) {
    // var o = new Object();   // <1> 去掉  
    this.name = name;          // <2> o.name 改成 this.name 
    this.age = age;            // <2> o.age 改成 this.age 
    this.sayName = function (){ // <2> o.sayName 改成 this.sayName
        console.log(this.name);
    }
    // return o;              // <3> 去掉 
}

var p1 = new Person("A",18);  // <4> 加上了new 
var p2 = new Person("B",19);  // <4> 加上了new 
p1.sayName();  // A
p2.sayName();  // B
// 以上解决了创建多个相似对象的问题

// 也能知道对象是什么类型了
console.log(p1 instanceof Person); // true  现在能知道p1是Person类型了
console.log(p1 instanceof Object); // true

缺点

就是每个方法都要在每个实例上重新创建一遍

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

var p1 = new Person("A",18);
var p2 = new Person("B",19);
p1.sayName();  // A
p2.sayName();  // B

// 相当于p1中创建了一个sayName  p2中也创建了一个sayName 
// 但是p1中sayName和p2中sayName中的功能一模一样 为什么要在堆里面开辟两个空间呢
// 如果还有p3 p4 p5那么sayName还有在堆中创建多个 能不能p1 p2 p3....调用的是同一个sayName呢
console.log(p1.sayName == p2.sayName); //false

方法1:把函数定义转移到构造函数外部 (不是最优)

function Person(name, age) {
    this.name = name;
    this.age = age;
    // this.sayName = function (){
    //     console.log(this.name);
    // }
    this.sayName = sayName;
}

// 把函数定义转移到构造函数外部  sayName此时在全局作用域
function sayName() {
    console.log(this.name);
}

var p1 = new Person("A", 18);
var p2 = new Person("B", 19);
p1.sayName();  // A
p2.sayName();  // B

console.log(p1.sayName == p2.sayName);  // true 只调用一个sayName方法 无需创建多个
缺点
  1. 如果对象需要定义很多方法,那么就要定义很多个全局函数 没有封装性可言

解决:可以将sayName放在Person的原型上 即下文的原型模式

  1. 在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实

全局作用域创建的sayName函数却只被Person调用 所以说有点名不副实

方法2: 原型模式

原型模式

优点

可以让所有对象实例共享它所包含的属性和方法


function Person(){}

Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 

Person.prototype.sayName = function(){ 
 console.log(this.name); 
};

var p1 =  new Person();
var p2 =  new Person();

console.log(p1.name == p2.name);  // true
console.log(p1.age == p2.age);    // true
console.log(p1.sayName == p2.sayName); // true

更简单的原型语法

避免每次添加一个属性和方法就要敲一遍Person.prototype和更好的封装Person.prototype,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象

function Person() { }

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

var p1 = new Person();
var p2 = new Person();

console.log(p1.name == p2.name);  // true
console.log(p1.age == p2.age);    // true
console.log(p1.sayName == p2.sayName);  // true

console.log(Person.prototype.constructor == Person); // false
console.log(Person.prototype.constructor == Object); // true

和未用对象字面量比只有一处不同:constructor属性不再指向Person了,而是指向Object
如果想要constructor属性指向Person了 可以这样写

function Person() { }

Person.prototype = {
    constructor: Person,   // 加上
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function () {
        console.log(this.name);
    }
}

var p1 = new Person();
var p2 = new Person();

console.log(Person.prototype.constructor == Person); // true

原型对象的缺点

省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值

最大问题是由其共享的本性所导致的 对引用类型值的属性来说

function Person() { }

Person.prototype = {
    constructor: Person,   
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    arr : ["数据结构","算法"], // 加上 引用类型 (Array)
    sayName: function () {
        console.log(this.name);
    }
}

var p1 = new Person();
var p2 = new Person();

// 能实现 给p2加上一门课""计算机网络",而p1不加嘛 ? 不能 因为共享arr属性
p2.arr.push("数据结构");
console.log(p1.arr);  // [ '数据结构', '算法', '数据结构' ]
console.log(p2.arr);  // [ '数据结构', '算法', '数据结构' ]
console.log(p1.arr == p2.arr); // true

这个也是为什么很少看到有人 单独 使用原型模式的原因

组合使用构造函数模式和原型模式 (最广泛,认可度最高)

构造函数模式 用于定义不共享的属性
原型模式       用于共享的属性和方法

function Person(name,age,job) { 
    this.name = name;
    this.job = job;
    this.age = age;
    this.arr = ["数据结构","算法"];
}

Person.prototype = {
    constructor: Person,   
    
    sayName: function () {
        console.log(this.name);
    }
}

var p1 = new Person();
var p2 = new Person();

// 可以实现 给p2加上一门课""计算机网络",而p1不加嘛 ? 可以
p2.arr.push("数据结构");
console.log(p1.arr);  // [ '数据结构', '算法' ]
console.log(p2.arr);  // [ '数据结构', '算法', '数据结构' ]
console.log(p1.arr == p2.arr); //  false

动态原型模式

原理跟组合使用构造函数模式和原型模式一样,但是把原型定义操作封装在了构造函数中。本质是通过构造函数初始化原型。

注意:使用动态原型模式时,不能使用对象字面量来重写原型。这样会切断现有实例与新原型之间的联系,现有原型还指向之前的原型

function Person(name,age,job) { 
    this.name = name;
    this.job = job;
    this.age = age;
    this.arr = ["数据结构","算法"];

    // 把原型定义放在了构造函数中
    // 无需用多个if 来初始化原型
    // 只需从 共享的属性或方法中随意挑一个出来 用if 来初始化原型
    // 只有第一次 new Person的时候才会初始化 undefined != "function" 
    //     第二次 new Person由于原型的动态性 已经有sayName了
  
    if (typeof this.sayName != "function") {    
        Person.prototype.sayName = function(){  
            console.log(this.name);
        }
    }
}

// Person.prototype = {
//     constructor: Person,   
    
//     sayName: function () {
//         console.log(this.name);
//     }
// }

var p1 = new Person();
var p2 = new Person();

// 可以实现 给p2加上一门课""计算机网络",而p1不加嘛 ? 可以
p2.arr.push("数据结构");
console.log(p1.arr);  // [ '数据结构', '算法' ]
console.log(p2.arr);  // [ '数据结构', '算法', '数据结构' ]
console.log(p1.arr == p2.arr); //  false

遗留

寄生构造函数模式

用途:专门为js原生函数的构造函数添加特殊的属性和方法 如创建一个特殊的数组

稳妥构造函数模式

本文使用 mdnice 排版