专栏首页Super 前端面向对象的程序设计

面向对象的程序设计

下述内容主要讲述了《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(属性所在的对象, 属性, 描述符对象) 示例:数据属性

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"

示例:访问器属性

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. 工厂模式

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

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. 构造函数模式

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属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型创建的所有实例共享的属性和方法。

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操作符无论该属性存在于实例中还是原型中。 示例:判断属性存在于原型中还是对象中

/* 方式一:函数封装 */
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 原型

更简单的原型语法:

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. 组合使用构造函数模式和原型模式

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

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. 动态原型模式

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

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. 寄生构造函数模式

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

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

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

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__)。

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. 借用构造函数

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

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. 组合继承

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

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. 原型式继承

function createObj(o){
    function F(){}
    F.prototype = o;    // 对o的一种浅复制
    return new F();
}

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

5. 寄生式继承

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

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

对象方法不能被复用!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 比较实用的jQuery代码段

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • 编写高质量代码:改善JavaScript程序建议--面向对象编程

    ​ 对象(Object)是没有原型的,只有构造函数拥有原型,而构造类的实例对象能够通过prototype属性访问原型对象。 prototype表示类的原型,...

    奋飛
  • Thinking--函数强制参数

    JavaScript 是弱类型语言,对于函数的参数不会做强制限制,如果需要限制,我们如何处理?

    奋飛
  • 10个JavaScript常见BUG及修复方法

    Fundebug
  • JS 原生面经从初级到高级【近1.5W字】

    1.函数声明有预解析,而且函数声明的优先级高于变量; 2.使用Function构造函数定义函数的方式是一个函数表达式,这种方式会导致解析两次代码,影响性能。第一...

    火狼1
  • JavaScript学习总结(四)——this、原型链、javascript面向对象

    一、this 在JavaScript中this表示:谁调用它,this就是谁。 JavaScript是由对象组成的,一切皆为对象,万物皆为对象。this是一个动...

    张果
  • javaScript 的面向对象程序

    数据属性(数据属性包含一个数据值的位置,这个位置可以读取和写入值,数据属性有4描述)

    用户1197315
  • python学习之旅(十六)

    在“主程序”中,变量'_name_'的值是'_main_',而在导入的模块中,这个值就被设定为模块的名字

    py3study
  • node.js中this指向失效解决

    原因:虽然类默认的方法指向类的实例,但是如果在外部单独使用该方法,this会指向该方法运行时所在的环境,不再指向对象

    雪山飞猪
  • Struts2 Wildcard 和DMI

    关于Struts2 Action中的最基本method配置我就不说,那个比较死板。关于这个method有两个动态调用Action方法的方法:WildCard(...

    the5fire

扫码关注云+社区

领取腾讯云代金券