代码改变世界

浅析Javascript面向对象程序设计之对象

2018-01-15 00:50  jym94  阅读(139)  评论(0)    收藏  举报

什么是对象

众所周知,Javascript是一门面向对象的编程语言。本人最近看书小有心得,特此总结一下关于javascript面向对象编程的几个知识点。
ECMA-262把对象定义为一组无序属性的集合,其属性可以包含基本值、对象或函数。

var person = new Object();
person.name = "Jym";
person.sayName = function(){
    alert(this.name);
}

以上代码定义了一个最基本的对象,并添加了一个name属性和一个sayName方法。当然,我们的对象还可以使用另一种方法来定义。

var person = {
    name : "Jym";
    sayName : function() {
        alert(this.name);
    }
}

甚至,我们还可以用第三种方法来定义一个对象。

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

需要特别注意的是,首先Object.defineProperty是ECMAScript5中的方法,所以各位需要支持IE9以下版本浏览器的同学请自动忽略它(为你们默哀三秒)。
其次,Object.defineProperty定义的数据类型默认的configurable,enumerable,writable特性都是false。(给不知道这三种特性的同学讲一下,configurable表示属性能否被删除,能否被修改。enumerable表示属性能否通过for-in循环返回。writable表示属性的值能否修改。)
嘿嘿,你要是觉得不爽,可以手动改掉它们。

Object.defineProperty(person, "name", {
    value : "Jym",
    configurable : true
})

Object.defineProperty还可以用来定义对象的访问器属性。访问器属性不包含数据值,但是包含一对getter和setter函数。读取访问器属性调用getter函数,写入访问其属性调用setter函数。有一点需要注意的是,访问器属性只能通过Object.defineProperty来定义。

var book = {
    _year : 2004,  //下划线表示属性只能通过对象方法来访问
    edition : 1
}

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

getter和setter不一定要同时指定。只指定getter意味属性是不能写的,嗯,接下来请自觉举一反三。

如果想要通过Object.defineProperty给对象定义多个属性,这也是可以的。

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

ECMAScript5中给了一个Object.getOwnPropertyDescriptor()方法用来取得给定属性的描述符。这个方法接收两个参数,分别是属性所在的对象以及要读取其描述符的属性名称。

var desc = Object.getOwnPropertyDescriptor(book , "year");
alert(desc.value);  //undefined
alert(typeof desc.get);  //"function"

创建对象的几种模式

接下来,我们具体分析一下创建对象的几种模式的优劣性。

工厂模式

function CreatePerson(name, age) {
    var o = new object();
    o.name = name;
    o.age = age;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}

工厂模式的劣势是无法识别一个对象的类型。于是,机智的猿们想到了另一个模式。

构造函数模式

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function() {
        alert(this.name);
    };
}
var preson1 = new Person("Jym",23);

在上面这个例子中,Person是一个构造函数。js中的构造函数一般以大写字母开头,非构造函数一般以小写字母开头。
在创建person1这个实例时,必须使用new操作符。同时,用这种方法调用构造函数会经历以下四个步骤。

  • 创建一个新对象
  • 将构造函数的作用于赋给新对象(因此this就指向了这个新对象)
  • 执行构造函数中的代码
  • 返回新对象

实例person1中有一个constructor属性,该属性指向Person。

person1.constructor == Person;  //tue  

嘿嘿,接下来我们可以来检测一下这个对象的类型了。

person1 instanceof Object  //true
person1 instanceof Person  //true

nice!

那么这种模式就是完美无缺的吗?显然是不可能的。

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

相当于

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = new Function("alert(this.name)");
}

可以看到,以这种方法创建实例函数,每个实例函数都创建了一个完成同样任务的function,这是相当没有必要的。所以我们可以换一个方式来解决这个问题。

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

这样一来,就避免了每个实例都需要去创建sayName方法。实例中包含的是一个指向sayName函数的指针。但是问题又来了,如果对象需要定义很多个方法,那我们就得去定义很多个全局函数,这既繁琐又破坏了对象的封装性。(封装,即把属性和方法封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。)
为了解决这个问题,我们只能祭出传说中的终极杀招,“原型模式”!
欲知后事如何,且看下回《浅析Javascript面向对象程序设计之原型》