JavaScript 面向对象的程序设计

理解 JS 的对象

在 JS 中,对象被定义为——“无序属性的集合,其属性可以包含基本值、对象或函数。“ 我们可以将其想象成为一个散列表,无非就是一组名值对,其中可以是数据或函数。所以,可以采用如下字面量来创建对象:

var person = {
    name: "xiaobai",
    age: 22,

    sayName: function(){
        alert(this.name);
    }
}

属性类型

在 JS 中有两种属性:数据属性和访问器属性。JS在定义描述属性各种特征的特性,这些特性是为了是想 JS 引擎用的,因此在 JS 中不能直接访问它们。表示时,一般将其放在两对方括号中,例如 [[Enumerable]];

1.数据属性

数据属性包含一个数据值的位置。在这个位置能读取和写入值。有如下4个特性。

  1. [[Configurable]]: 表示表示能否通过 delete 删除属性,从而定义新的属性,能否修改属性的特性,或者能否吧属性修改为访问器属性。默认值为 true。
  2. [[Enumberable]]: 表示能够通过 for-in 循环访问属性。默认值为 true。
  3. [[Writable]]: 表示能否修改属性的值。默认值为 true。
  4. [[Value]]: 包含这个属性的数据值。默认值为 undefined。

有修改属性默认的特性,必须使用 Object.defineProperty() 方法。其接受三个参数,属性所在的对象、属性的名字和一个描述符对象。其中描述符对象的属性必须是: configurable、enumerable、writable 和 value。设置其中一个或多个值,可以修改其相应的特性。如下:

var person = {};
Object.defineProperty(person, "name" {
    writeable: false,
    value: "Xiaobai"
});

alert(person.name); // "Xiaobai"
person.name = "xiaohei";
alert(person.name); // "Xiaobai"

如果以上代码在非严格模式下会被忽略,严格模式下则会抛出错误。值得一提的是,如果一旦将 configurable 设置为 false 后,就不能再次改变其特性,再次调用 Object.defineProperty(), 则会跑出错误。另外,在调用该方法时,如果不指定,configurable、enumerable和writable 特性的默认值都是 false。

2. 访问器属性

访问器属性不包含数值,但是包含一对儿 getter 和 setter 函数(都不是必须的)。在读取访问器属性时,会调用 getter 函数。在写入访问器属性时,则会调用 setter 函数。其有如下4个特性。

  1. [[Configurable]]: 表示表示能否通过 delete 删除属性,从而定义新的属性,能否修改属性的特性,或者能否吧属性修改为数据属性。默认值为 true。
  2. [[Enumberable]]: 表示能够通过 for-in 循环访问属性。默认值为 true。

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

var book = {
    _year: 2004,
    edition: 1
};

Object.defineProperty(book, "year", {
    get: function(){
        return this._year;
    },
    set: function(newValue){
        if(newValue > 2004){
            this._year = newValue;
            this.edition += newValue - 2004;
        }
    }
});

book.year = 2005;
alert(book.edition); // 2

定义多个属性

Object.defineProperties() 的方法可以通过描述符一下添加多个属性。例如:

var book = {};

Object.defineProperties(book, {
    _year: {
        value: 2004
    },

    edition: {
        value: 1
    },

    year: {
        get: function(){
            return this._year;
        },

        set: function(newValue){
            if(newValue > 2004){
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});

以上代码与上一节创建的对象相同。唯一区别是,它们是在同一时间创建的。

读取属性的特性

Object.getOwnPropertyDescriptor() 方法,客气鱼的给属性的描述符。接受两个参数: 属性所在对象和要读取器描述符的属性名称。返回是一个对象,如果是属性访问器,则对象属性包含 configurable、enumerable、get 和 set。如果是数据属性,则这个对象属性包含 configurable、enumerable、writable 和 value。例如:

var book = {};

Object.defineProperties(book, {
    _year: {
        value: 2004
    },

    edition: {
        value: 1
    },

    year: {
        get: function(){
            return this._year;
        },

        set: function(newValue){
            if(newValue > 2004){
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});

var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value);    //2004
alert(descriptor.configurable); // false

创建对象

工厂模式

这是一种广为人知的模式,如下所示:

function createPerson(name, age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayname = function(){
        alert(this.name);
    };

    return o;
}

var person = createPerson("xiaobai", 23);

工厂模式最大的问题在于没有解决对象的识别问题,即知道一个对象的类型。

构造函数

我们也可以自定义构造函数,如下所示:

function Person(name, age){
    this.name = name;
    this.age = age;

    this.sayName = function(){
        alert(this.name);
    };
}

var person = new Person("xiaobai", 23);

在这里我们使用了 new 操作符,以这种方式调用构造函数会经历以下4个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋值给这个对象(因此 this 就指向了这个新对象);
  3. 执行构造函数的代码(为这个对象添加新的属性);
  4. 返回新的对象。

其中,每一个对象有一个 constructor 属性,指向其构造函数。

alert(person.constructor == Person); // true

我们还可以通过 instanceof 操作符检测对象是不是某个类型的实例。

alert(person instanceof Object);    //true
alert(person instanceof Person);    //true

构造函数的缺点在于

hasOwnProperty() 方法可以检测一个属性是否存在于实例中还是在原型中。而 in 操作符在对象能够访问属性时返回 true;

function Person(){
}

Person.prototype.name = "xiaobao";
Person.prototype.age = 22;

person = new Person();

alert(person.hasOwnProperty("name"));   //false 来自原型
alert("name" in person);                //true
person.name = "xiaohei";
alert(person.hasOwnProperty("name"));   //true 来自实例
alert("name" in person);

在使用 for-in 循环时能够返回该对象能够访问的所有的属性,包括不可枚举属性(即将[[Enumerable]]标记的属性)。

Object.keys() 方法接受一个对象作为参数,返回该对象上一个包含所有可枚举属性的字符串数组。

Person.prototype.name = "xiaobao";
Person.prototype.age = 22;

person = new Person();

var keys = Object.keys(Person.prototype);

alert(keys); //"name, age"

keys = Object.keys(person);

alert(keys); //""

要想去的某个实例的所有属性,无论是否可枚举,都可以使用 Object.getOwnPropertyNames();

function Person(){
}

Person.prototype.name = "xiaobao";
Person.prototype.age = 22;

person = new Person();

var keys = Object.getOwnPropertyNames(Person.prototype);

alert(keys); //"constructor,name, age"

keys = Object.getOwnPropertyNames(person);

alert(keys); //""
2. 更简单的原型语法

为了更好的封装原型功能,一个更常见的功能是用一个包含所有对象和方法的字面量来重写整个原型对象。如下所示:

function Person(){
}

Person.prototype = {
    name: "xiaobai",
    age: 22,
    sayName: function(){
        alert(this.name);
    }
}

该方法将 Person.prototype 设置为一个字面量创建的新的对象。最终的结果就是实例的 constructor 不在指向Person。而当我们创建这个对象时,它也自动获得了一个 constructor 属性。我们重写了默认 Prototype 对象,因此 constructor 也比啊我觉得新对象的 constructor 属性(指向 Object 构造函数)。

var person = new Person();

alert(person instanceof Object);        //true
alert(person instanceof Person);        //true
alert(person.constructor == Person);    //false
alert(person.constructor == Object);    //true

这个解决的方法如下:

function Person(){
}

Person.prototype = {
    constructor: Person,
    name: "xiaobai",
    age: 22,
    sayName: function(){
        alert(this.name);
    }
}

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

因为原型主要是实现共享的方法与属性,所以我们还需要构造函数来定义实例属性。如下所示:

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

Person.prototype = {
    constructor: Person,
    sayName: function(){
        alert(this.name);
    }
}

继承

原型链

JS 中原型链是实现继承的主要方法。其做法是让一个原型对象等于另一个类型的实例对象。

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

Person.prototype.sayName = function(){
    alert("My name is " + this.name);
};

function Student(major){
    this.major = major;
};

Student.prototype = new Person("xiaobai", 22);

Student.prototype.sayMajor = function(){
    alert("My major is " + this.major);
};

student = new Student("Computer");

student.sayName();  //"My name is xiaobai"
student.sayMajor(); //"My major is computer"

原型链主要问题在于,引用类型的属性会被所有实例共享,这很明显不是我们希望看到的。我们可以借用构造函数来解决这个问题,如下所示:

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

Person.prototype.sayName = function(){
    alert("My name is " + this.name);
};

function Student(name, age, major){
    Person.call(this, name, age);
    this.major = major;
};

Student.prototype = new Person();

Student.prototype.sayMajor = function(){
    alert("My major is " + this.major);
};

student = new Student("xiaobai", 22, "Computer");

student.sayName();
student.sayMajor();

这种方式被成为组合继承,有时也被叫做伪经典继承,将原型链和借用构造函数组合到一起。它唯一一个小缺点时,原型中还保存与实例继承下来的同名属性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容