手记

你真的熟知JavaScript面向对象程序设计?

ECMAScript面向对象的程序设计

了解过JavaScript语言的都会知道,这门语言没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。

理解对象

想要认识你的“对象”前,我们还是需要了解下它基本概况,才能模仿出它的样子。

例如创建一个对象date

var data = {} || new Object()
//当然你也可以向data对象添加属性(property)和方法(method)
data.username = 'aierui'
data.getName = function(){
    return this.username
}

对于上面的例子你也可以换一种写法,如

var data = {
    username: 'aierui',
    getName: function(){
        return this.username
    }
}
对象属性
objectName.propertyName
对象方法
objectName.methodName()

属性类型

这里你还需要知道ECMAScript存在两种属性:数据属性和访问器属性

数据属性包含一个数据值的位置,可以读取和写入。数据属性有4个描述其行为的特性。

参数 默认 含义
configurable true 能否通过delete删除属性从而重新定义属性
enumerable true 能够通过for-in循环返回属性
writable true 能够修改属性的值
value undefined 属性的数据值

可以通过Object.defineProperty(属性所在的对象,属性的名称,一个描述符对象) 方法修改对应特性值。

访问器属性 不含数据值,但是含一对getter()和setter() (不过这两个都不是必需的)

参数 默认 含义
configurable true 能否通过delete删除属性从而重新定义属性
enumerable true 能够通过for-in循环返回属性
get undefined 读取属性时调用的函数
set undefined 写入属性时调用的函数

读取属性的特性 Object.getOwnPropertyDescriptor(属性所在的对象,要读取其属性描述名称) 方法

创建对象

在js中创建对象方法有很多,分别如下

  • 工厂模式
  • 构造函数
  • 原型模式
  • 组合使用构造模式和原型模式
  • 动态原型模式
  • 寄生构造函数模式
  • 稳妥构造函数模式

工厂模式

使用简单的函数创建对象,为对象添加属性和方法,然后返回对象,不推荐使用。

function person(name,age,job){
    var data = {} || new Object()
    data.name = name
    data.age = age
    data.job = job
    data.getName = function(){
        alert(this.name) 
    }
    return data 
}

var obj = person('aierui',20,'student')

构造函数

自己来定义对象类型的属性和方法。

function Person(name, age, job){
    this.name = name
    this.age = age
    this.job = job
    this.getName = function(){
        alert(this.name)
    }
}

var obj = new Person('aierui',20,'student')

这里有几点注意地方

  • 将属性和方法赋值给了this
  • 没有直接创建对象、没有return返回语句
  • 函数名是大写字母P开头,表示构造函数 //借鉴其他OO语言
  • 不同实例的同名函数是不相等 alert(obj.getName == obj1.getName) //false
  • 构造函数主要问题:每个方法都要在每个实例上重新创建一遍,然后创建两个完成同样任务的Function是没有必要的,可以通过将构造函数中定义在this上的函数转移到构造函数外部,通过调用函数名即可。

原型(property)模式

创建的每个函数都有一个原型(prototype)属性,这个属性是一个指针,指向一个对象。原型模型好处在于可以让所有对象实例共享它所包含的属性和方法,也就是说,不用在构造函数中定义对象实例的信息,而是将这些信息直接添加到原型对象中。问题是,他省略了构造函数传递初始化参数。
构造函数、原型、实例三者之间的关系?
每个构造函数都有一个原型对象,每个原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

function Person(){

}
Person.property.name = 'aierui'
Person.property.age = 20
Person.property.job = 'student'
Person.property.getName = function(){
    alert(this.name)    
}
var obj1 = new Person();
obj1.getName()

var obj2 = new Person()
obj2.getName()

它们之间有什么关系呢?
原型最初只包含constructor属性,而该属性也是共享的,因此可以通过对象实例访问。


我们在调用obj1.getName()的时候,会先执行两次搜索。
首先,解释器会问:“实例obj1中有getName()吗?”
答:“没有。”然后继续执行
再问:“obj1的原型(prototype)中有getName吗?”
答:“有。” 于是进过这两次从下到上的过程读取到相应的结果

检测一个属性是存在原型还是存在实例中

hasOwnProperty() 当属性存在对象实例中,返回true

in操作符
先说下in操作符,分为单独使用和在for-in循环使用
单独使用时,in操作符会在公国对象能够访问的给定属性时返回true,无论该属性是存在原型还是实例中。

hasPrototypeProperty() 当属性讯在原型中,返回true

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

集前两种之长。也建议常用这种方式创建对象

function Person(name, age, job){
    this.name = name
    this.age = age
    this.job = job
}

Person.prototype = {
    constructor: Person,
    getName:function(){
        alert(this.name)
    }
}
var obj1 = new Person('aierui' ,20, 'student')
obj1.getName();//aierui

动态原型模式

什么是动态原型模式呢?换句话说,可以通过检查某个应该存在的方法是否有效

function Person(name, age, job){
    this.name= name
    this.age = age
    this.job = job
    //首先判断
    if(typeof this.getName != 'function'){
        Person.prototype.getName = function(){
            alert(this.name)
        }
    }
}

寄生构造函数模式

这种模式和第一种工厂模式写法完全一样,但是有特殊之处,用来为对象创建构造函数,由于不能够直接修改Array构造函数,因此可以使用这种模式。我个人不喜欢这种模式,55555~

稳妥构造函数模式

他是指没有公共属性,一个属性只被一个方法访问。
稳妥构造函数与寄生构造函数有两点不同:1、新创建对象实例方法不引用this 2、不适用new操作符条用构造函数

对象继承

ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链实现的

  • 原型链
  • 借助构造函数
  • 组合继承
  • 原型链继承
  • 寄生式继承

原型链

其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。实现的本质是重写原型对象

function Animal(){
    this.anproperty = false;
}

Animal.prototype.getAnimalValue = function(){
    return this.anproperty
}

function Person(){
    this.property = true
}
//继承了Animal
Person.prototype = new Animal();
Person.prototype.getPersonValue = function(){
    return this.property
}

var obj = new Person()
alert(obj.getAnimalValue) //false;

获取原型对象方法的比较
如前所述,proto属性指向当前对象的原型对象,即构造函数的prototype属性。

var obj = new Object();

obj.__proto__ === Object.prototype
// true
obj.__proto__ === obj.constructor.prototype
// true

上面代码首先新建了一个对象obj,它的proto属性,指向构造函数(Object或obj.constructor)的prototype属性。所以,两者比较以后,返回true。

因此,获取实例对象obj的原型对象,有三种方法。

obj.__proto__
obj.constructor.prototype
Object.getPrototypeOf(obj)

上面三种方法之中,前两种都不是很可靠。最新的ES6标准规定,proto属性只有浏览器才需要部署,其他环境可以不部署。而obj.constructor.prototype在手动改变原型对象时,可能会失效。

var P = function () {};
var p = new P();

var C = function () {};
C.prototype = p;
var c = new C();

c.constructor.prototype === p // false

上面代码中,C构造函数的原型对象被改成了p,结果c.constructor.prototype就失真了。所以,在改变原型对象时,一般要同时设置constructor属性。

C.prototype = p;
C.prototype.constructor = C;

c.constructor.prototype === p // true

所以,推荐使用第三种Object.getPrototypeOf方法,获取原型对象。

var o = new Object();
Object.getPrototypeOf(o) === Object.prototype
// true

借助构造函数

组合继承

原型链继承

寄生式继承

文/aierui(github)

文章第一时间发于:https://github.com/Aierui/jstraining

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