首页 > 代码库 > JavaScript面向对象精要(二)

JavaScript面向对象精要(二)

四、构造函数和原型对象

1. 构造函数

构造函数就是用new创建对象时调用的函数。使用构造函数的好处在于所有用同一个构造函数创建的对象都具有同样的属性和方法。

function Person(){}
var p1 = new Person();
console.log(p1.constructor === Person); // true
console.log(p1 instanceof Person);      // true

可以使用构造函数来检查一个对象的类型,但还是建议使用instanceof来检查对象类型。因为构造函数属性可以被覆盖,并不一定完全准确。
示例:构造函数返回对象

function Foo(){
    this.name = "foo";
    return {name: "hhh"};
}
var f = new Foo();
f.name;         // hhh
f.constructor;  // Object

示例:构造函数返回原始类型

function Too(){
    this.name = "too";
    return "hhh";
}
var t = new Too();
t.name;         // too
t.constructor;  // Too

构造函数中显示调用return:

  • 如果返回的值是一个对象,它会替代新创建的对象实例返回;
  • 如果返回的值是一个原始类型,它会被忽略,新创建的对象实例会被返回。

2. 原型对象

请参照:【详解prototype与proto区别 】

3. 改变原型对象

[[Prototype]]属性只是包含一个指向原型对象的指针,并不是一个副本;任何对原型对象的改变都立刻反映到所有引用它的对象实例上。
示例:扩展原型对象

function Person(){}
var p = new Person();
p.sayHi();  //  p.sayHi is not a function(…)
Person.prototype.sayHi = function(){
    console.log("hi");
};
p.sayHi();  // "hi"

注意:在一个对象上使用Object.seal()Object.freeze()时,完全是在操作对象的自有属性,可以通过在原型上添加属性来扩展这些对象实例。
示例:冻结对象

function Person(name){}
var p = new Person();
Object.freeze(p);
p.name = "ligang";
Person.prototype.sayHi = function(){
    console.log("hi");
};
console.log(p.name);    // undefined
p.sayHi();  // "hi"

关于对象,请查看:【面向对象的程序设计】、【JavaScript高级技巧-防篡改对象】

五、继承

JavaScript内建的继承方法被称为原型对象链,又称为原型对象继承。当一个对象的[[Prototype]]设置为另一个对象时,就在这两个对象之间创建了一条原型对象链。

1. Object.prototype

所有对象都继承自Object.prototype

方法 说明
hasOwnProperty() 检查是否存在一个给定名字的自有属性
propertyIsEnumerable() 检查一个自有属性是否可枚举
isPrototypeOf() 检查一个对象是否是另一个对象的原型对象
valueOf() 返回一个对象的值表达
toString() 返回一个对象的字符串表达

上述5种方法经由继承出现在所有对象中。
(1)valueOf()
valueOf()默认返回对象实例本身,可以定义自己的valueOf()方法,定义的时候没有改变操作符的行为,仅仅定义了操作符默认行为所使用的值。
(2)toString()
一旦valueOf()返回的是一个引用而不是原始值的时候,就会回退调用toString()
示例:

var obj1 = {
    valueOf: function(){
        return "valueOf";
    },
    toString: function(){
        return "toString";
    }
}
var obj2 = {
    valueOf: function(){
        return {name: "哈哈"};
    },
    toString: function(){
        return "toString";
    }
}
obj1 + "";  // "valueOf"
obj2 + "";  // "toString"

2. 修改Object.prototype

Object.prototype添加方法,默认是可枚举的,意味着可以出现在for-in循环中。Douglas Crockford(JavaScript之父)推荐在for-in循环中始终使用hasOwnProperty()

var empty = {};
Object.prototype.myName = "LIGANG";
for(var prop in empty){
    console.log(prop);      // 会输出:myName
}
for(var prop in empty){
    if(empty.hasOwnProperty(prop)){
        console.log(prop);  // 无任何内容输出
    }
}

所以,在进行for-in操作时,最好的方式就是增加hasOwnProperty()判断;与此时同,不要修改Object.prototype

3. 对象继承

对象继承是最简单的继承类型,需要做的就是指定哪个对象是新对象的[[Prototype]]
创建对象过程中,字面量形式会隐式指定Object.prototype为其[[Prototype]],也可以用Object.create()方式显示指定。

var obj1 = {};
var obj2 = Object.create(Object.prototype, {});

Object.create()方法,第一个参数是需要设置为新对象的[[Prototype]]的对象,第二个参数是一个属性描述对象,格式同Object.defineProperties()
示例:原型对象链(继承)

var person = {
    name: "person",
    sayName: function(){
        console.log(this.name);
    }
};
var p1 = Object.create(person, {
    name: {
        cofigurable: true,
        enumerable: true,
        value: "ligang",
        writable: true
    }
});     

技术分享
末端通常是一个Object.prototype[[prototype]]被置为null。
示例:空对象

var obj1 = {};
var obj2 = Object.create(null);
"toString" in obj1; // true
"toString" in obj2; // false

obj2没有原型对象链的对象。完全是一个没有任何预定义属性的白板,使其成为一个完美的哈希容器。

4. 构造函数继承

构造函数的所有对象实例共享同一个原型对象,所以它们都继承自该对象,但不能用原型对象继承自有属性。

function Person(name){
    this.name = name; // 私有属性
}
Person.prototype.sayName = function(){  // 共用方法
    console.log(this.name);
};
var p1 = new Person("ligang");
var p2 = new Person("camile");

p1,p2都是构造函数Person的实例,其共享Person.prototype原型对象。但其自有属性name不能通过原型对象继承。
总结:自有属性/方法通过构造函数定义,共有属性/方法通过原型对象继承!!!

六、对象模式

虽然JavaScript没有一个正式的私有(局部)属性的概念(ES6中出现了let语法,可以定义局部变量),但是可以创建仅在对象内可以访问的数据或函数。使用模块模式可对外界隐藏数据;也可以使用立即调用函数表达(IIFE)定义仅可被新创建的对象访问的本地变量和函数。

1. 私有成员和特权成员

var obj = (function(){
    // 私有变量
    var author = "ligang";
    return {
        // 公共的方法和属性
        getAuthor: function(){
            return author;
        }
    };
}());
obj.author; // undefined
obj.getAuhtor(); // "ligang"

上述函数仅存在于被调用的瞬间,一旦执行后立即就被销毁了。

// 上述示例的等价写法
var obj = (function(){
    // 私有变量
    var author = "ligang";
    var getAuthor = function(){
        return author;
    };
    return {
        // 公共的方法和属性
        getAuthor: getAuthor
    };
}());

2. 混入

混入将一个属性从一个对象复制到另一个,从而使得接受者在不需要继承提供者的情况下获得其功能。和继承不同,混入令你在创建对象后无法检查属性来源。若你想要获得更强大的功能且需要知道该功能来自哪里,继承是首选!

function mixin(des, src){
    for(var prop in src){
        if(src.hasOwnProperty(prop)){
            des[prop] = src[prop];
        }
    }
    return des;
}

注意上述方式,不是深拷贝!
奇舞团提供了深拷贝方式:https://github.com/75team/mixin.js

var a = {x:{y:1, z:3}};
mixin(a, {x:{y:2}, z:2}, function(a,b){
    try{
        return mixin(a,b, arguments.callee)
    }catch(ex){
        return b
    }
});

等价于 jQuery.extend(true, a, {x:{y:2}, z:2});

3. 作用域安全的构造函数

function Person(name){
    this.name = name;
}
var p1 = new Person("ligang");
var p2 = Person("camile");
console.log(p1 instanceof Person); // true
console.log(p2 instanceof Person); // false

作用域安全的构造函数是用不用new都可以被调用来生成新的对象实例的构造函数。其this在构造函数一开始执行就已经指向自定义类型的实例。当然,你可以根据new的使用与否决定构造函数的行为。

function Person(name){
    if(this instanceof Person){
        this.name = name;
    }else {
        return new Person(name);
    }

}
var p = Person("ligang");
console.log(p instanceof Person); // true

许多内建构造函数,例如Array、RegExp不需要new也可以工作,正是因为它们被设计之初采用了作用域安全的构造函数。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    JavaScript面向对象精要(二)