JavaScript笔记:面向对象的程序设计

本文深入讲解ECMAScript中的对象概念,探讨属性类型及其特性,并详细介绍了多种创建对象的方法及各自的优缺点。此外,还全面解析了JavaScript中的继承机制,包括原型链、借用构造函数、组合继承等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ECMA-262 把对象定义为:无序属性的集合,其属性可以包括基本类型值、对象或者函数。
我们可以把ECMAScript的对象看作是一个散列表:无非就是一组名值对,其中值可以是各种数据和函数。
每个对象都是基于一个引用类型创建的。

1、理解对象

var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
    alert(this.name);
};
// 字面量语法
var person = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function(){
        alert(this.name);
} };

1、属性类型

ECMAScript中有两种属性,数据属性和访问器属性。

1、数据属性
数据属性有四个描述其行为的属性。
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性改为访问器属性。像前面的例子中那样直接在对象上定义的属性,默认值为true。
[[Enumerable]]:能否通过for in循环返回属性。像前面的例子中那样直接在对象上定义的属性,默认值为true。
[[Writable]]:表示能否修改属性的值。像前面的例子中那样直接在对象上定义的属性,默认值为true。
[[Value]]:包含这个属性的值。这个特性默认的值为undefined。

要更改属性默认的特性,可以使用Object.defineProperty()方法。

var person = {};
Object.defineProperty(person, "name", {
    writable: false,
    value: "Nicholas"
});
alert(person.name); //"Nicholas" 
person.name = "Greg"; 
alert(person.name); //"Nicholas"

在非严格模式下,赋值操作会被忽略;在严格模式下,赋值操作会报错。

var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Nicholas"
});
alert(person.name); //"Nicholas" 
delete person.name; 
alert(person.name); //"Nicholas"

注意,一旦把属性定义为不可配置的,那么就不能再将它改为可配置的了,否则,会抛出错误。

var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Nicholas"
});
// 抛出错误
Object.defineProperty(person, "name", {
    configurable: true,
    value: "Nicholas"
});

2、访问器属性

访问器属性不包含数值,它们包含一对getter和setter函数。读取访问器属性时,会调用getter函数,这个函数负责返回有效的值;写入访问器属性时,会调用setter函数并传入新值,这个函数负责如何处理数据。
访问器属性有如下四个特性:
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性改为访问器属性。像前面的例子中那样直接在对象上定义的属性,默认值为true。
[[Enumerable]]:能否通过for in循环返回属性。像前面的例子中那样直接在对象上定义的属性,默认值为true。
[[Get]]:读取属性时调用的函数,默认为undefined。
[[Set]]:写入属性时调用的函数,默认为undefined。
访问器属性不能直接定义,必须使用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

写法2:

var book = {
    _year: 2004,
    edition: 1 
};
// 定义访问器的旧有方法
book.__defineGetter__("year", function(){
    return this._year;
});
book.__defineSetter__("year", 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;
            }
        }
    }
)};

读取属性的特性

仍旧以上面的book对象为例。

var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value); //2004
alert(descriptor.configurable); //false
alert(typeof descriptor.get); //"undefined"
var descriptor = Object.getOwnPropertyDescriptor(book, "year"); alert(descriptor.value); //undefined alert(descriptor.enumerable); //false
alert(typeof descriptor.get); //"function"

2、创建对象

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

这种方式解决了创建多个相似对象的问题,但是没有解决对象识别的问题,于是,新的模式出现了。

2、构造函数模式

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }; 
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");


alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
alert(person1 instanceof Object);  //true
alert(person1 instanceof Person);  //true
alert(person2 instanceof Object);  //true
alert(person2 instanceof Person);  //true

1、将构造函数当作函数

构造函数和普通函数唯一的区别就是调用它们的方式不同。任何函数,如果通过new来调用,那它就是构造函数;反之,如果不用new来调用,那就和普通函数没什么区别。

//当作构造函数
var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas"
//普通函数
Person("Greg", 27, "Doctor"); //添加到window 
window.sayName(); //"Greg"
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse"); 
o.sayName(); //"Kristen"

2、构造函数的问题

问题:每个方法都要在每个实例上重新创建一遍。
解决方法:将函数定义到构造函数之外。

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

这样定义其实也存在问题:将函数放在了全局作用域,使得封装性下降。

3、原型模式

function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
person1.sayName();   //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName);  //true

1、理解原型

alert(Person.prototype.isPrototypeOf(person1));  //true
alert(Person.prototype.isPrototypeOf(person2));  //true

alert(Object.getPrototypeOf(person1) == Person.prototype); //true alert(Object.getPrototypeOf(person1).name); //"Nicholas"

// 覆盖
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer"; 12 Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg"
alert(person2.name); //"Nicholas"
alert(person1.hasOwnProperty("name")); //true
alert(person2.hasOwnProperty("name")); //false

delete person1.name;
alert(person1.name);//"Nicholas"
alert(person1.hasOwnProperty("name")); //false

2、原型与in操作符

in单独操作时,in操作符会在通过对象能访问到属性时返回true。

function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name"));  //false
alert("name" in person1);  //true
person1.name = "Greg";
alert(person1.name); //"Greg"
alert(person1.hasOwnProperty("name")); //true 
alert("name" in person1); //true
alert(person2.name); //"Nicholas"
alert(person2.hasOwnProperty("name")); //false 
alert("name" in person2); //true
delete person1.name;
alert(person1.name); //"Nicholas"
alert(person1.hasOwnProperty("name")); //false 
alert("name" in person1); //true

在使用for in循环时,返回的是所有能够通过对象访问的,可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中的不可枚举属性([[Enumerable]]标记为false的属性)的实例属性也会在循环中返回。因为根据规定,所有开发人员定义的属性都是可枚举的。

var o = {
    toString : function(){
        return "My Object";
    }
};
for (var prop in o){
    if (prop == "toString"){
        alert("Found toString");
    } 
}

要取得对象上所有的可枚举实例属性,可以使用 Object.keys()方法。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys);       //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys);    //"name,age"

如果要得到所有的实例属性,不论是否可枚举,则可这样调用:

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);    //"constructor,name,age,job,sayName"

3、更简单的原型语法

function Person(){
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

使用这种方式的缺点是,constructor无法确定正确的类型。

var friend = new Person();
alert(friend instanceof Object);//true
alert(friend instanceof Person);//true
alert(friend.constructor == Person);//false
alert(friend.constructor == Object);//true

解决办法:

function Person(){
}
Person.prototype = {
    constructor : Person,
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

4、原型的动态性

我们对原型所做的任何修改都能在实例上立即反应出来。

var friend = new Person();
Person.prototype.sayHi = function(){
    alert("hi");
};
friend.sayHi(); //"hi"

但是,如果是重写整个原型对象,那么情况就不一样了。

function Person(){
}
var friend = new Person();
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
friend.sayName();   //error

5、原生对象的原型

原生的引用类型,也是通过原型模式的方式创建的。

alert(typeof Array.prototype.sort); //"function" alert(typeof String.prototype.substring); //"function"

因此,我们也可以给原生的引用类型增加方法:

String.prototype.startsWith = function (text) {
    return this.indexOf(text) == 0;
};
var msg = "Hello world!";
alert(msg.startsWith("Hello"));   //true

6、原型模式的问题

function Person(){
}
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shelby", "Court"],
    sayName : function () {
        alert(this.name);
} };
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);    //"Shelby,Court,Van"
alert(person2.friends);    //"Shelby,Court,Van"
alert(person1.friends === person2.friends);  //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

这是目前使用最广泛、认同度最高的创建自定义类型的方法。

动态原型模式

有其他OO语言经验开发的人员看到独立的构造函数和原型的时候,可能会感到非常困惑,动态原型模式正是为了解决这个问题,它把所有的信息都封装在了构造函数中。例子:

function Person(name, age, job){
    this.name = name; 
    this.age = age; 
    if (typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            alert(this.name);
        }; 
    }
}

var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

寄生构造函数模式

function Person(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 friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

实例2:创建一个具有额外方法的数组,同时不修改Array原型。

function SpecialArray(){
    var values = new Array();
    values.push.apply(values, arguments);
    values.toPipedString = function(){
        return this.join("|");
    };
    return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"

稳妥构造函数模式

稳妥对象,指的是没有公共属性,而且其方法也不引用this对象。稳妥对象最适合用在一些安全的环境中,防止数据被修改。

function Person(name, age, job){
    var o = new Object();
    o.sayName = function(){
        alert(name);
    };
    return o; 
}

var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

除了调用sayName方法之外,没有别的方式访问其数据成员。

3、继承

许多OO语言都支持两种继承方式:接口继承和实现继承。ECMAScript只支持实现继承,而且继承主要是依靠原型链来实现的。

1、原型链

原型链是实现继承的主要方法。
原型链的基本模式:

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true

确定原型和实例的关系

//方法1
alert(instance instanceof Object);//true
alert(instance instanceof SuperType);//true
alert(instance instanceof SubType);//true

//方法2
alert(Object.prototype.isPrototypeOf(instance));
alert(SuperType.prototype.isPrototypeOf(instance));
alert(SubType.prototype.isPrototypeOf(instance));

谨慎的定义方法

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
//添加方法
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
//重写超类型中的方法
SubType.prototype.getSuperValue = function (){
    return false;
};
var instance = new SubType();
alert(instance.getSuperValue());   //false
function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
//这样写,会使上面这行代码无效
SubType.prototype = {
    getSubValue : function (){
        return this.subproperty;
},
    someOtherMethod : function (){
        return false;
} };
var instance = new SubType();
alert(instance.getSuperValue());   //error!

原型链的问题

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//继承SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black" 
var instance2 = new SubType(); 
alert(instance2.colors); //"red,blue,green,black"

解决方法见下面一节 ->

借用构造函数

在子类的构造函数中调用超类型的构造函数。

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
    //继承SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);    //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors);    //"red,blue,green"

传递参数

function SuperType(name){
    this.name = name;
}
function SubType(){
    //继承SuperType
    SuperType.call(this, "Nicholas");
    this.age = 29;
}
var instance = new SubType();
alert(instance.name);    //"Nicholas";
alert(instance.age);     //29

问题:函数复用无从谈起

组合继承

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    //继承
    SuperType.call(this, name);
    this.age = age;
}
//继承
SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);//"red,blue,green,black"
instance1.sayName();//"Nicholas";
instance1.sayAge();//29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors);//"red,blue,green"
instance2.sayName();//"Greg";
instance2.sayAge();//27

原型式继承

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

从本质上讲,object函数对传入的对象进行了浅复制。

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

规范方法:Object.create()

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

寄生式继承

function createAnother(original){ 
    var clone=object(original);
    clone.sayHi = function(){// 增强对象
        alert("hi");
    };
    return clone;
}


var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

寄生组合式继承

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
}

最理想的继承范式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值