手记

JavaScript 面向对象的程序设计:创建对象的方式

最近几天看完了JavaScript 高级程序设计(第3版)第六章 面向对象的程序设计,作为一个以前只学过C语言从没接触过面向对象语言的人来说,感觉许多概念都很新,在这里借手记把知识点和自己的理解做一个总结,也帮助自己巩固。
例子里的代码都出自高程三。


创建对象的几种方式和优缺点

  • 创建一个Object实例:
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
    alert(this.name);
};

也可以用对象字面量语法来写:

var person = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    
    sayName: function(){
        alert(this.name);
    }
}

缺点:使用同一接口创建很多对象,会产生大量重复代码。

  • 工厂模式:抽象了创建具体对象的过程,用函数封装以特定接口创建对象的细节
function createPerson(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 person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

优点:解决了创建多个相似对象的问题
缺点:不能识别对象类型

  • 构造函数模式:创建自定义的构造函数,从而定义自定义对象类型的属性和方法
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, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true

通过new操作符调用就是构造函数,否则就是普通函数
与工厂模式的区别:

  1. 没有显式地创建对象;
  2. 直接将属性和方法赋给this对象;
  3. 没有return语句
  4. 函数名以大写开头(为了区别于不是用来创建对象的普通函数,任何函数用 new操作符调用就可以作为构造函数,否则就是普通函数)

创建自定义的构造函数意味着可以将它的实例标识为一种特定的类型,所有对象均继承自Object。

缺点:对象的每个方法都要在每个实例上重新创建一遍,即上面例子里每个Person实例都包含一个不同的Function实例,会导致不同的作用域链和标识符解析,不同实例上的同名函数是不相等的:

function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("alert(this.name)"); // 与声明函数在逻辑上是等价的
}
alert(person1.sayName == person2.sayName); //false
  • 原型模式:每个函数都有一个prototype属性,这是一个指针,指向一个对象,这个对象包含可以由特定类型的所有实例共享的属性和方法,prototype可以理解为通过调用构造函数而创建的那个对象实例的原型对象;好处是可以让所有对象实例共享它所包含的属性和方法。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName (); //"Nicholas"
alert(person1.sayName == person2.sayName); //true

仍然可以通过调用构造函数来创建新对象,而且新对象的属性和方法由所有实例共享

理解原型对象

函数的prototype属性指向函数的原型对象,所有原型对象自动获得一个constructor(构造函数)属性(原型最初只包含constructor属性,也是由实例共享的,即实例也有这个constructor属性),这个属性包含一个指向prototype属性所在函数的指针,即指向构造函数;调用构造函数创建的实例包含一个[[Prototype]]指针,指向构造函数的原型对象(与prototype相同),这也是实例与原型之间的连接。
Firefox、Safari 和Chrome 在每个对象上都支持一个属性__proto__(注意这里前后都是两个下划线)。

用字面量语法重写原型对象

避免重复输入xxx.prototype,从视觉上更好地封装原型的功能

function Person(){}
Person.prototype = {
    constructor : Person,//这里手动将constructor指向person,否则由于用字面量方式重写了prototype,constructor将指向Object构造函数
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

原型的动态性

function Person(){}
/*这里是先重写原型对象再创建实例*/
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
        
var friend = new Person();
        
Person.prototype.sayHi = function(){
    alert("hi");
};
        
friend.sayHi();   //"hi" – works!

在上面这个例子里先重写原型对象再创建实例,由于实例与原型之间通过[[prototype]]指针连接,实例并不是原型的副本,因此在创建实例后添加的sayHi方法实例也能具有。

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

而这个例子里先创建实例再重写整个原型对象,调用构造函数创建实例后,实例的[[prototype]]指针指向最初的原型对象,用字面量语法重写原型对象相当于把原型对象修改为另一个对象,现在的这个原型对象与重写之前创建的实例是没有联系的,因此要注意创建实例和重写原型对象的顺序。

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

用构造函数定义实例非共享的属性,用原型定义实例共享的方法和属性。
优点:每个实例都有自己的实例属性的副本,同时共享原型方法,节省内存,支持向构造函数传递参数

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
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); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

这种方式是用来定义引用类型的一种默认模式。


这一篇里对照书总结了一下创建对象的几种方式,下一篇进一步总结原型链和继承。

2人推荐
随时随地看视频
慕课网APP