深入理解 JS 的面向对象的程序设计

98 阅读5分钟

对象:无需属性的集合,其属性包含基本值、对象或函数。

(一)理解对象的属性类型

  1. 数据属性

内部特性:是为了实现 JavaScript 引擎用的,不可直接访问。

  • [[configurable]]: 表示是否能通过 delete 删除属性从而重新定义属性。默认为 true
  • [[enumerable]]: 表示是否能通过 for-in 循环返回属性。默认为 true
  • [[writable]]:表示是否修改属性的值。默认为 true
  • [[value]]:属性的数据值。默认 undefined

要修改属性默认的特性,博旭使用 object.defineProperty() 方法。

var person = {}
Object.defineProperty(person, 'name', {
    configurable: false,
    value: ''nico
})

注意:configurable 设置为 false 的操作是不可逆的,也就是说一旦把属性定义为不可配置的,就不能再把它变回可配置的了。

2.访问器属性

访问器属性包含一对 getter 和 setter 函数。

  • getter:获取属性值
  • setter:设置属性值,这个函数决定如何处理数据

访问器属性有4个特性:

  • configurable
  • enumerable
  • get
  • set

访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。

var book = {
    __year:2004,
    edition:1
}
Object.defineProperty(book, 'year', {
    get(){
        return this.__year
    },
    set(val){
        if(val>2004){
            this.__year == val
            this.edition += val - 2004
        }
    }
})

tips: __year 前面的下划线是一种常用的记号,表示只能通过对象方法访问的属性。

(二)定义多个属性的方法

很多开发场景中会用到为对象定义多个属性,因此 ECMAscript5 又定义了一个 Object.definedProperties() 方法。

var book = {}
book.defineProperties(book, {
    __year: {
        value: 2004
    },
    edition: {
        value: 1
    },
    year: {
        get(){},
        set(){}
    }
})

(三)读取属性的特性

Object.getOwnPropertyDescriptor() 获取给定属性的描述符。

  • 参数:属性所在的对象和要读取其描述符的属性名称
  • 返回值:返回值是一个对象,如果
    是访问器属性,这个对象的属性有 configurable、 enumerable、 get 和 set;如果是数据属性,这
    个对象的属性有 configurable、 enumerable、 writable 和 value。

(四)创建对象的方式

虽然 Object 构造函数或对象字面量都可以用来创建单个对象,但是使用通过一个接口创建很多对象,会产生大量的重复代码,因此我们诞生了工厂模式。

1.工厂模式

这是一种广为人知的设计模式,抽象了创建具体对象的过程。即用函数来封装以特定接口创建对象的细节:

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");

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题。

随着 JavaScript 的发展,又一个新模式出现了。

2.构造函数模式

通过了解 object 和 Array 这样的原生构造函数,我们知道在运行时会自动出现在执行环境中。因此我们可以创建自定义的构造函数,自定义对象类型的属性和方法:

function Person(name, age, job){
    this.name = name
    this.age = age
    this.job = job
    this.sayName = function(){
        alert(this.name)
    }
    var person1 = new Person('nico', 29, 'ss')
    var person2 = new Person('greg', 27, 'Doctor')
    
    alert(person1.constructor == Person); //true  
    alert(person2.constructor == Person); //true
}

这个例子就是用 Person() 函数取代了 createPerson() 函数。要创建 Person 的新势力,必须使用 new 操作符。使用了 new 操作符会经历 4 个步骤:

(1)创建一个新对象

(2)this 指向这个新对象

(3)为这个新对象添加属性

(4)返回新对象

tips:构造函数的命名一般使用 首字母大写,主要是为了区别于 其他函数。

使用构造函数需要注意的地方:

1.构造函数与普通函数唯一的区别:调用方式不同

任何函数只要通过 new 操作符来调用,都可以作为构造函数

2.构造函数的问题

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

3.原型模式

我们创建的每一个函数都会有一个 prototype 属性,这是一个指针,指向一个对象,即原型对象,而这个对象就是包含实例共享的属性和方法。

默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,指向构造函数,通过构造函数我们可以继续为原型对象添加其他属性和方法。

image.png

Person 构造函数、 Person 的原型属性以及 Person 现有的两个实例之间的关系。
在此, Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person。
原型对象中除了包含 constructor 属性之外,还包括后来添加的其他属性。 Person 的每个实例——
person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype;换句话说,它们
与构造函数没有直接的关系。此外,要格外注意的是,虽然这两个实例都不包含属性和方法,但我们却可以调用 person1.sayName()。这是通过查找对象属性的过程来实现的。

hasOwnProperty() 可以检测一个属性是存在于实例中,还是存在于原型中,当只有该属性存在于对象实例时,才会返回 true。

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

这种方式的优点是实现每个实例都有属于自己的一份实例属性副本,同时又共享方法的引用,最大限度的节省内存。

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

这种模式也是目前使用最广泛、认同度最高的一种。