JavaScript继承的特性与实践应用深入详解

继承是代码重用的模式。JavaScript 可以模拟基于类的模式,还支持其它更具表现力的模式。但保持简单通常是最好的策略。

JavaScript 是基于原型的语言,也就是说它可以直接继承其他对象。

1 伪类

JavaScript 的原型不是直接让对象从其他对象继承,而是插入一个多余的间接层:通过构造函数来产生对象。

当一个函数被创建时,Function 构造器产生的函数对象会运行这样类似的代码:

this.prototype = {constructor : this};

新的函数对象新增了一个 prototype 属性,它是一个包含了 constructor 属性且属性值为该新函数的对象。

当采用构造器调用模式,即用 new 去调用一个函数时,它会这样执行:

Function.method('new', function (){ var that = Object.create(this.prototype);//创建一个继承了构造器函数的原型对象的新对象 var other = this.apply(that, arguments);//调用构造器函数,绑定 this 到新对象 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'); console.log(myMammal.get_name());//Herb the mammal

构造另一个伪类来继承 Mammal(定义构造器函数并替换它的 prototype):

var Cat = function (name) { this.name = name; this.saying = 'meow'; }; Cat.prototype = new Mammal();

扩充原型:

Cat.prototype.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }; Cat.prototype.get_name = function () { return this.says() + ' ' + this.name + ' ' + this.says(); }; var myCat = new Cat('Henrietta'); console.log(myCat.says());//meow console.log(myCat.purr(5));//r-r-r-r-r console.log(myCat.get_name());//meow Henrietta meow

我们使用 method 方法定义了 inherits 方法,来隐藏上面这些丑陋的细节:

/** * 为 Function.prototype 新增 method 方法 * @param name 方法名称 * @param func 函数 * @returns {Function} */ Function.prototype.method = function (name, func) { if (!this.prototype[name])//没有该方法时,才添加 this.prototype[name] = func; return this; }; Function.method('inherits', function (Parent) { this.prototype = new Parent(); return this; });

这两个方法都返回 this,这样我们就可以以级联的方式编程啦O(∩_∩)O~

var Cat = function (name) { this.name = name; this.saying = 'meow'; }.inherits(Mammal).method('purr', function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }).method('get_name', function () { return this.says() + ' ' + this.name + ' ' + this.says(); }); var myCat = new Cat('Henrietta'); console.log(myCat.says());//meow console.log(myCat.purr(5));//r-r-r-r-r console.log(myCat.get_name());//meow Henrietta meow

虽然我们有了行为很像“类”的构造器函数,但没有私有环境,所有的属性都是公开的,而且不能访问父类的方法。

如果在调用构造函数时忘记加上 new 前缀,那么 this 就不会被绑定到新对象上,而是被绑定到了全局变量!!!这样我们不但没有扩充新对象,还破坏了全局变量环境。

这是一个严重的语言设计错误!为了降低出现这个问题的概率,所有的构造器函数都约定以首字母大写的形式来命名。这样当我们看到首字母大写的形式的函数,就知道它是构造器函数啦O(∩_∩)O~

当然,更好的策略是根本不使用构造器函数。

2 对象说明符

有时候,构造器需要接受一大堆参数,这很麻烦。所以在编写构造器时,让它接受一个简单的对象说明符会更好:

var myObject = maker({ first: f, middle: m, last: l });

现在这些参数可以按照任意的顺序排列咯,而且构造器还能够聪明地为那些没有传入的参数使用默认值,代码也变得更易阅读啦O(∩_∩)O~

3 原型

基于原型的继承指的是,一个新对象可以继承一个旧对象的属性。首先构造出一个有用的对象,然后就可以构造出更多与那个对象类似的对象。

/** * 原型 */ var myMammal = { name: 'Herb the mammal', get_name: function () { return this.name; }, says: function () { return this.saying || ''; } }; //创建新实例 var myCat = Object.create(myMammal); myCat.name = 'Henrietta'; myCat.saying = 'meow'; myCat.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }; myCat.get_name = function () { return this.says() + ' ' + this.name + ' ' + this.says(); }; console.log(myCat.says());//meow console.log(myCat.purr(5));//r-r-r-r-r console.log(myCat.get_name());//meow Henrietta meow

这里用到了 create 方法来创建新的实例:

Object.create = function (o) { var F = function () { }; F.prototype = o; return new F(); }

4 函数化

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/fd85de868bcaca00e4e3705dcb461685.html