首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

面向对象的程序设计

下述内容主要讲述了《JavaScript高级程序设计(第3版)》第6章关于“面向对象的程序设计”。

ECMA-262把对象定义为:”无序属性的集合,其属性可以包含基本值、对象或者函数。”

一、理解对象

1. 属性类型

ECMAScript中有两种属性:数据属性访问器属性。 数据属性包含一个值;访问器属性不包含值而定义了一个当属性被读取时调用的函数(getter)和一个当属性被写入时调用的函数(setter)。 (1)数据属性

特性

说明

描述

[[Configurable]]

可配置

能否删除、修改属性的特性

[[Enumerable]]

可枚举

能否通过for-in循环返回属性

[[Writable]]

可写

能否修改属性的值

[[Value]]

数据值

读取、写入值的位置

(2)访问器属性

特性

说明

描述

[[Configurable]]

可配置

能否删除、修改属性的特性

[[Enumerable]]

可枚举

能否通过for-in循环返回属性

[[Get]]

属性被读取时调用的函数

默认值undefined

[[Set]]

属性被写入时调用的函数

默认值undefined

修改属性默认的特性:Object.defineProperty(属性所在的对象, 属性, 描述符对象) 示例:数据属性

代码语言:javascript
复制
var person = {};
Object.defineProperty(person, "name", {
    configurable: true,
    enumerable: false,
    writable: false,
    value: "lg"
});

console.log(person.name);  // "lg"
for(var prop in person){
    console.log(prop);      // 未执行
}
person.name = "li";
console.log(person.name);  // "lg"

示例:访问器属性

代码语言:javascript
复制
var person = {
    _name: "ligang"
};
Object.defineProperty(person, "name", {
    configurable: false,
    enumerable: true,
    set: function(name){
        /* 此处可以做其他操作 */
        this._name = name;
    },
    get: function(){
        /* 此处可以做其他操作 */
        return this._name;
    }
});
console.log(person.name);  // "lg"
for(var prop in person){
    console.log(prop);      // "_name"
}
person.name = "li";
console.log(person.name);  // "li"

可以通过Object.getOwnPropertyDescriptor(属性所在的对象, 属性)取得给定属性的描述符;通过Object.defineProperties(属性所在的对象, {属性1:描述符对象1, 属性2:描述符对象2})一次性定义多个属性。

二、创建对象

1. 工厂模式

工厂模式抽象了创建具体对象的过程。

代码语言:javascript
复制
function createPerson(name, age){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function(){
        console.log(this.name);
    };
    return obj;
}
var p1 = createPerson("z3", 26);
var p2 = createPerson("l4", 27);

工厂模式可以解决创建多个相似对象的问题,但是会出现识别问题(即怎么知道一个对象的类型)。

2. 构造函数模式

代码语言:javascript
复制
function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    };
}
var p1 = new Person("z3", 26);
var p2 = new Person("l4", 27);
console.log(p1 instanceof Person); // true

可以标识类型(p1.constructor ==> Person),其方法都要在每个实例上重新创建一遍

3. 原型模式

每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型创建的所有实例共享的属性和方法。

代码语言:javascript
复制
function Person(){}
Person.prototype.name = "lg";
Person.prototype.age = 26;
Person.prototype.sayName = function(){
    console.log(this.name);
};
var p1 = new Person();
p1.name = "z3";
console.log(p1.name);                       // "z3" 实例
console.log("name" in p1);                  // true
console.log(p1.hasOwnProperty("name"));     // true
delete p1.name;
console.log(p1.name);                       // "lg" 原型
console.log("name" in p1);                  // true
console.log(p1.hasOwnProperty("name"));     // false

当为对象添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。hasOwnProperty()方法可以检测一个属性是否存在于实例中,还是存在于原型中;in操作符无论该属性存在于实例中还是原型中。 示例:判断属性存在于原型中还是对象中

代码语言:javascript
复制
/* 方式一:函数封装 */
function hasPrototypeProperty(object, name){
    return !object.hasOwnProperty(name) && (name in object);
}
hasPrototypeProperty(p1, "name");   // true 原型
/* 方式二:原型扩展 */
Object.prototype.hasPrototypeProperty = function(prop){
    return !this.hasOwnProperty(prop) && (prop in this);
};
p1.hasPrototypeProperty("name");    // true 原型

更简单的原型语法:

代码语言:javascript
复制
function Person(){}
Person.prototype = {
    name: "lg",
    age: 26,
    friends: ["camile"],
    sayName: function(){
        console.log(this.name);
    }
};
// 修正构造函数指向,不可枚举
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});
var p1 = new Person();
var p2 = new Person();

p1.friends.push("Gavin");
console.log(p1.friends);    // ["camile", "Gavin"]
console.log(p2.friends);    // ["camile", "Gavin"]

如果我们的初衷就是所有实例共享一个数组,那么其符合预期;若想每个实例都有属于自己的全部属性,会存在上述问题。

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

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。每个实例都会有自己的一份实例属性的副本,但同时又共享着方法的引用,最大限度地节省内存。

代码语言:javascript
复制
function Person(name, age, friends){
    this.name = name;
    this.age = age;
    this.friends = friends || [];
}
Person.prototype.sayName = function(){
    console.log(this.name);
};
var p1 = new Person("Gavin", 26);
var p2 = new Person("Camile", 26);
p1.friends.push(["James"]);
console.log(p1.friends);    // ["James"]
p2.friends.push(["Tom"]);
console.log(p2.friends);    // ["Tom"]

是目前ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法!!

5. 动态原型模式

将构造函数和原型结合,不再独立。

代码语言:javascript
复制
function Person(name, age){
    this.name = name;
    this.age = age;
    // 不存在情况下,才会添加
    if(typeof this.sayName !== "function"){
        Person.prototype.sayName = function(){
            console.log(this.name);
        }
    }
}
var p1 = new Person("Gavin", 26);
p1.sayName();   // "Gavin"
// Person.prototype.sayName不会被执行
var p2 = new Person("Camile", 26);

注意:不能使用对象字面量重写原型,其会切断现有实例与新原型之间的联系。

6. 寄生构造函数模式

其和典型的构造函数有略微的区别

代码语言:javascript
复制
function Person(name, age){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function(){
        console.log(this.name);
    };
    return obj;
}
var p1 = new Person("ligang", 26);
console.log(p1 instanceof Person);  // false
console.log(p1.constructor);        // Object

如果我们想创建一个具有额外方法的特殊属性,使用上述模式会达到很好的效果!!

代码语言:javascript
复制
function SpecialArray(){
    var ary = new Array();
    ary.push.apply(ary, arguments);
    ary.toPipedString = function(){
        return this.join("|");
    };
    return ary;
}
var colors = new SpecialArray("red", "yellow", "blue");
colors.toPipedString();

三、继承

JavaScript主要通过原型链实现继承。

1. 原型链

每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(__proto__)。

代码语言:javascript
复制
function Super(){
    this.property = true;
}
Super.prototype.getSuperValue = function(){
    return this.property;
};

function Sub(){
    this.subProperty = false;
}
Sub.prototype = new Super();  // 将一个类型的实例赋给另一个构造函数的原型
Sub.prototype.getSubValue = function(){
    return this.subProperty;
};

var instance = new Sub();
console.log(instance.getSubValue());     // false
console.log(instance.getSuperValue());   // true
console.log(instance.constructor);       // Super
console.log(instance instanceof Sub);    // true
console.log(instance instanceof Super);  // true

问题:(1)包含引用类型值的原型,会被所有实例共享;(2)创建子类型的实例时,不能向父类型的构造函数中传递参数。

2. 借用构造函数

在子类构造函数的内部调用父类的构造函数。

代码语言:javascript
复制
function Super(name){
    this.name = name;
    this.colors = ["red"];
}

function Sub(name, age){
    // 继承Super,可传递参数
    Super.call(this, name);
    this.age = age;
}

var instance = new Sub("Gavin", 26);
instance.colors.push("blue");
console.log(instance.name, instance.age);   // Gavin 26
console.log(instance.colors);   // ["red", "blue"] 独立的colors副本
console.log(new Sub().colors);  // ["red"] 独立的colors副本

问题:方法都在构造函数中定义,函数复用无从谈起。

3. 组合继承

将原型链和借用构造函数组合一起。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

代码语言:javascript
复制
function Super(name){
    this.name = name;
    this.color = ["red"];
}
Super.prototype.sayName = function(){
    console.log(this.name);
};
function Sub(name, age){
    Super.call(this, name);
    this.age = age;
}
Sub.prototype = new Super();
Sub.prototype.sayAge = function(){
    console.log(this.age);
};

var instance1 = new Sub("Gavin", 26);
instance1.color.push("blue");
console.log(instance1.color);   // ["red", "blue"]
instance1.sayName();            // "Gavin"
instance1.sayAge();             // 26

var instance2 = new Sub("Camile", 26);
instance2.color.push("yellow");
console.log(instance2.color);   // ["red", "yellow"]
instance2.sayName();            // "Camile"
instance2.sayAge();             // 26

JavaScript中最常用的继承模式!!!

4. 原型式继承

代码语言:javascript
复制
function createObj(o){
    function F(){}
    F.prototype = o;    // 对o的一种浅复制
    return new F();
}

该方法等价于ECMAScript5中Object.create()方法只传入第一个参数。引用类型值的属性会共享相应的值。

5. 寄生式继承

在原型式继承基础上,继续改造

代码语言:javascript
复制
function createAnother(original){
    var clone = createObj(original);
    clone.sayHi = function(){
        console.log("Hi");
    };
    return clone;
}
var person = {
    name: "LIGANG",
    age: 26
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();

对象方法不能被复用!

下一篇
举报
领券