《JavaScript高级程序设计》读书笔记6.2创建对象

虽然Object构造函数或对象字面量可以创建单个对象,但他们有个明显缺点:使用同一个接口创建很多对象,会产生大量的重复代码

1.工厂模式

ECMAScript中没有类,用函数来封装以特定接口创建对象的细节。但没有解决对象识别问题,不能判断对象的类型。

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sanName=function(){
alert(this.name);
};
return o;
}

var person1=createPerson("Nicholas",29,"Doctor");

2.构造函数模式

原生构造函数+自定义构造函数

1
2
3
4
5
6
7
8
9
10
11
12
自定义构造函数
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,"Engineer");
var person2=new Person("greg",28,"Doctor");
  • 构造函数必须以大写字母开头
  • 要创建新实例,必须使用new操作符,会经历4个步骤
    • 创建一个新对象
    • 新对象原型链接到构造函数原型对象
    • 将构造函数的作用域赋给新对象(this绑定到这个新对象)
    • 执行构造函数中的代码(为这个新对象添加属性)
    • 返回新对象
  • 显式的return表达式将会影响返回结果,但仅限于返回的是一个对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function Bar() {
    return 2;
    }
    new Bar(); // 返回新创建的对象

    function Test() {
    this.value = 2;

    return {
    foo: 1
    };
    }
    new Test(); // 返回的对象
2.1将构造函数当作函数

构造函数与其他函数唯一区别,在于调用方式不同,用不用new!
用new就是构造函数
不用就是普通函数

1
2
3
4
5
6
7
8
9
10
11
12
//当作构造函数使用
var person=new Person("libowen",29,"Engineer");
person.sayName(); //"libowen"

//作为普通函数使用
Person("libo",23,"writer");
window.sayName(); //"libo" Person中this指向Global对象(浏览器中就是window对象)

//在另一个对象的作用域中调用
var o=new Object();
Person.call(o,"yechun",40,"sailer");
o.sayName(); //"yechun"
2.2构造函数的问题
  • 主要问题:每个方法都要在每个实例上重新创建一遍。从而不同实例上的同名函数是不想等的。

  • 解决方法:把函数定义转移到构造函数外部

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=sayName;
    }

    function sayName(){
    alert(this.name);
    }
  • 引出新的问题:如果对象定义很多方法,那么就要定义很多个全局函数,于是自定义的引用类型全无封装性了。

  • 新问题解决方法:原型模式

3.原型模式(原型对象是实例对象的子对象,一个属性)

  • 含义:创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含着特定类型的所有实例共享的属性和方法。
  • prototype:是原型对象,是通过构造函数而创建的那个对象实例的原型对象。
  • 好处:不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Person(){

    }

    Person.prototype.name="libowen";
    Person.prototype.age=29;
    Person.prototype.sayName=function(){
    alert(this.name);
    };
3.1理解原型对象
  • 原型对象:只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。

  • constructor属性:默认情况下,所有原型对象自动获得一个constructor属性,该属性是一个指针,指向prototype属性所在函数的指针。constructor属性可以人为修改。

  • [[Prototype]]:当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。这个指针(内部属性)是[[Prototype]]。

  • [[Prototype]]是存在于实例与原型对象之间的连接,不是存在于实例和构造函数的连接。
    原型

  • isPrototypeOf():来确定原型对象或构造函数与对象实例之间的关系。

    1
    2
    alert(Person.prototype.isPrototyprOf(person1));        //true
    alert(Person.prototype.isPrototypeOf(person2)); //true
  • Object.getPrototypeOf():ECMAScript5中新增。返回[[prototype]]的值。

    1
    2
    alert(Object.prototype.getPrototypeOf(person1)==Person.prototype);   //true
    alert(Object.prototype.getPrototypeOf(person2).name); //"libowen"
  • 对象实例共享原型所保存的属性和方法的基本原理:先从对象实例本身开始搜索,如果找不到,继续搜索指针指向的原型对象。

  • 屏蔽与屏蔽原因:当为对象实例添加一个属性时,这个属性会屏蔽原型对象中保存的同名属性。原因则是:添加这个属性会阻止我们访问原型中的那个属性,但不会修改那个属性。

  • nulldelete:null只会在实例中设置属性,不能恢复指向原型的链接,delete可以完全删除实例属性,恢复原型链接。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function Person(){

    }

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

    var person1=new Person();
    var person2=new Person();

    person1.name="Greg";
    alert(person1.name); //"Greg",来自实例
    alert(person2.name); //"libowen",来自原型

    delete(person1.name);
    alert(person1.name); //"libowen",来自原型
  • hasOwnProperty():只在给定属性存在于对象实例中,才返回true。

3.2原型与in操作符
  • 两种使用方式:单独使用、for-in循环。
    • 单独使用时,不区分实例和原型,能够访问到给定属性就返回true
    • 确定属性在原型中的方法
    • for-in循环,返回的是所有能够通过对象访问的、可枚举的属性。
      1
      2
      3
      function hasPrototypeProperty(object,name){
      return !object.hasOwnProperty(name)&&(name in object);
      }
3.3原型的动态性
  • 由于在原型中查找值是一次搜索,因此对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建实例后修改原型也是如此。
  • 如果重写整个原型对象,就切断现有原型(构造函数的prototype属性只指向现有原型)与任何之前已经存在的对象实例之间的联系,对象实例引用的仍然是最初的原型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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

过程如图
clipboard.png

3.4原型对象的问题
  • 缺点:不能单独使用原型模式的原因。
    • 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
    • 所有实例都共享原型的属性,而且原型的属性可以动态更改,导致实例没有属于自己的全部属性。包含基本类型值的属性可以添加屏蔽;包含引用类型值的属性被操作修改后,则会被所有共享反映出来。

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

构造函数模式用于定义实例属性
原型模式用于定义方法和共享的属性
最常用、默认

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["libo","liufang"];
}

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); //"libo,liufang,van"
alert(person2.friends); //"libo,liufang"
alert(person1.friends===person2.friends); //false
alert(person2.sayName===person2.sayName); //true

5.动态原型模式

可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//例子
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();

只在sayName()方法不存在的情况下,才会将它添加到原型中。

6.寄生构造函数模式

基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(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 friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
  • 除了使用 new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实
    是一模一样的。
  • 通过在构造函数的末尾添加一个return语句且返回的是一个对象,可以重写调用构造函数时返回的值。
  • 这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊
    数组。由于不能直接修改Array构造函数,因此可以使用这个模式。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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操作符来确定对象类型
  • 建议在可以使用其他模式的情况下,不要使用这种模式

7.稳妥构造函数模式

  • 稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象
  • 用处: 在一些安全的环境中(这些环境中会禁止使用thisnew),或者在防止数据被其他应用程序(如 Mashup程序)改动时使用。
  • 特点:稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同
    1. 新创建对象的实例方法不引用this
    2. 不使用new操作符调用构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(name, age, job){ 

//创建要返回的对象
var o = new Object();

//可以在这里定义私有变量和函数

//添加方法
o.sayName = function(){
alert(name);
};

//返回对象
return o;
}

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

代码解释:

  • 变量 friend中保存的是一个稳妥对象,而除了调用sayName()方法外,没有别的方式可以访问其数据成员。
  • 即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。
liborn wechat
欢迎您扫一扫上面的微信二维码,订阅我的公众号!