日期:2014-05-16  浏览次数:20320 次

JavaScript: The Good Parts 读书笔记(三)

三.继承

  • 概述
    ?? Javascript 是一门弱类型语言,对象的起源是无关紧要的。对于一个对象来说重要的是它能做什么,而不是它从哪里来。Javascript 提供了一套丰富的代码重用模式。它可以模拟那些基于类的模式,同时它也可以支持其他更具表现力的模式. 在JS中可能的继承模式有很多。在这里,我们将研究几种最为直接的模式.
    ? ?在基于类的语言中,对象是类的实例,并且类可以从另一个类继承。而Javascript是一门基于原型的语言,这意味着对象直接从其他对象继承。

  • 伪类模式
    ???Javascript 的原型存在着诸多矛盾。它不让对象直接从其他对象继承,反而插入了一个多余的间接层. 它使用构造器函数产生对象。这种构造器函数就是一个伪类.
    当一个函数对象被创建时,Function 构造器产生的函数对象会运行类似下面的代码:
    this.prototype = {constructor : this};
    ??? 新函数对象被赋予一个 prototype 对象,该对象又包含一个 constructor 属性。而这个constructor属性的值即为该新函数自身. 因为Javascript 语言并没有提供一种方法去确定哪个函数作为构造器。所以每个函数都会得到一个 prototype 对象。 大多数情况下,constructor 属性没什么用,重要的是prototype 对象.
    ?? 这里假设new操作为一个方法,那么当使用new去构造对象时,可能的执行代码如下:
    Function.method("new", function(){
           //创建一个继承自身原型的对象
         var that = Object.beget(this.prototype);
           //调用构造器函数,绑定 this 到新对象上
         var other = this.apply(that,arguments);
           //如果它的返回值不是一个对象,则返回新对象
         return (typeof other === 'object' && other) || that;
    }
    ?由上可知,我们可以定义一个构造器函数,并对其原型进行扩展.
    var Mammal = function(name){ // 由约定可知,构造器函数使用大写开头
    	this.name = name;
    };
    Mammal.prototype.get_name = function(){
    	return this.name;
    };
    Mammal.prototype.says = function(){
    	return this.saying || "";
    };
    
    var myMammal = new Mammal("Herb the Mammal");
    var name = myMammal.get_name();
    document.writeln(name);
    
    var Cat = function(name){
    	this.name = name;
    	this.saying = "meow";
    };
    
    // 替换Cat.prototype,使其"继承" Mannal
    Cat.prototype = new Mammal();
    Cat.prototype.purr = function(n){
    	var i,s = "";
    	for(i = 0 ; i < n ; i ++){
    		if(s){
    			s+="-";
    		}
    		s += "r";
    	}
    	return s;
    };
    // 覆盖原型链中的get_name()
    Cat.prototype.get_name = function(){
    	return this.says() + " " + this.name + " " + this.says();
    };
    
    var myCat = new Cat("Henrietta");
    document.writeln(myCat.says()); // meow
    document.writeln(myCat.purr(5)); // r-r-r-r-r
    document.writeln(myCat.get_name()); // meow Henrietta meow
    
    ??? 伪类模式的本意是模拟OO语言中的继承,向面向对象靠拢,但它看起来显得格格不入。下面可以通过一些辅助方法来隐藏一些丑陋的细节.
    Function.method("inherits",function(Parent){
    	this.prototype = new Parent();
    	return this;
    });
    
    var Dog = function(name){
    	this.name = name;
    	this.saying = "wanwan";
    }.inherits(Mammal).method("purr",function(n){
    	var i,s = "";
    	for(i = 0 ; i < n ; i ++){
    		if(s){
    			s+="-";
    		}
    		s += "w";
    	}
    	return s;
    }).method("get_name",function(){
    	return this.says() + " " + this.name +" " + this.says();
    });
    
    var myDog = new Dog("Kiten");
    document.writeln(myDog.says()); // wanwan
    document.writeln(myDog.purr(5)); // w-w-w-w-w
    document.writeln(myDog.get_name()); // wanwan Kiten wanwan
    
    ?
    ?? 虽然通过隐藏一些繁琐的针对prototype的操作细节,使用伪类继承看起来没那么怪异了.但我们是否真的有所改善呢? 我们现在有了行为像 “类” 的构造器函数,但仔细去看,他们却存在令人惊讶的行为: 没有私有环境,所有属性都是公开的。无法访问父类的方法(super). 更糟糕的是,使用构造器函数存在一个严重的危害。如果你在调用构造器函数时忘了在前面加上new操作符,那么this将会被绑定到全局对象上,这样你既没有扩充新对象,反而破坏了全局变量。而且此时既没有编译时警告,也没有运行时警告。这是一个严重的语言设计错误。为了降低产生这个问题的风险,所有构造器函数都约定为使用首字母大写命名
    ?? “伪类” 形式可以给不熟悉Javascript 的程序员提供便利,但它也已隐藏了该语言的真实本质。借鉴类的表示法可能误导程序员去编写过于深入与复杂的层次结构。而Javascript中却有更好的选择。

  • 函数化模式
    ??? 在一个纯粹的原型模式中,我们会摒弃类,转而专注于对象。基于原型的继承相比基于类的继承在概念上更为简单:一个新对象可以继承一个旧对象的属性。通过构造一个有用的基础对象,接着可以构造更多和那个对象类似的对象。可以完全避免把一个应用拆解成一些列嵌套抽象类的分类过程。
    // 我们先使用字面变量去构造一个有用的对象:
    var myMammal = {
    	name : "Herb the Mammal",
    	get_name : function(){
    		return this.name;
    	},
    	says : function(){
    		return this.saying || "";
    	}
    };
    
    // 接下来我们可以使用 beget 方法构造出更多的实例,之后对实例进行定制。
    var myCat = Object.beget(myMammal);
    myCat.name = "Henrietta";
    myCat.saying = "meow";
    myCat.purr = function(n){
    	var i,s="";
    	for(i = 0 ; i < n ; i ++){
    		if(s){
    			s+="-";
    		}
    		s+="s";
    	}
    	return s;
    };
    
    myCat.get_name = function(){
    	return this.says + " " + this.name + " " + this.says;
    };
    ?这是一种 “差异化继承”. 通过定制该新对象,我们指明了它与其基类对象的区别.

  • 隐私的保护
    ??? 迄今为止,我们所论述的继承模式都存在一个弱点:我们没法保护隐私. 对象的所有属性都是可见的。我们设置保护私有变量和私有函数。其中一个