Javascript Web Application笔记之MVC和类 - 类的继承 函数调用 匿名函数 作用域 类库介绍

1、基于原型的类继承:

当你读取一个对象的属性时,JavaScript 首先会在本地对象中查找这个属性,如果没有找到,JavaScript 开始在对象的原型中查找,若仍未找到还会继续查找原型的原型,直到查找到Object.prototype。如果找到这个属性,则返回这个值,否则返回undefined。

使用原型实现继承,首先需要定义一个父类的构造函数,然后,将父类的新实例赋值给构造函数的原型。下面使用原型实现继承:

var Animal = function(){};
Animal.prototype.breath = function(){
console.log(‘breath’);
};

var Dog = function(){};
// Dog 继承了Animal
Dog.prototype = new Animal;
Dog.prototype.wag = function(){
console.log(‘wag tail’);
};
var dog = new Dog;
dog.wag(); // 调用子类的方法
dog.breath(); //调用继承的属性

2、给类库添加继承:

var Class = function(parent){
var klass = function(){
this.init.apply(this, arguments);
};
// 改变klass 的原型,通过匿名函数的方式让父类的原型赋值给子类
if (parent) {
var subclass = function() { };
subclass.prototype = parent.prototype;
klass.prototype = new subclass;
};
klass.prototype.init = function(){};
// 定义别名
klass.fn = klass.prototype;
klass.fn.parent = klass;
klass.super = klass.\_proto__; // _super,__proto__
/* include/extend 相关的代码…… */
return klass;
};

如果将parent 传入Class 构造函数,那么所有的子类则必然共享同一个原型。这种创建临时匿名函数的小技巧避免了在继承类的时候创建实例,这里暗示了只有实例的属性才会被继承,而非类的属性。

下面是生成由Animal继承的类,并创建该继承类的实例,调用继承下来的方法:

var Animal = new Class;
Animal.include({
breath: function(){
console.log(‘breath’);
}
});
var Cat = new Class(Animal)
// 用法
var tommy = new Cat;
tommy.breath();

3、函数调用:

和其他对象不同的是,函数是可调用的。函数内上下文,如this 的取值,取决于调用它的位置和方法。

除了直接调用函数之外,还有其他两种方法可以调用函数:apply() 和call()。两者的区别在于传入函数的参数的形式。

关于apply()和call()的详细使用,可以参考这篇文章的1.4函数上下文:

http://www.itzhai.com/javascript-notes-javascript-function.html

3.1、访问原始上下文的方法:

为了访问原始上下文,可以将this 的值存入一个局部变量中,这是一种常见的模式,比如:

var clicky = {
wasClicked: function(){
/* … */
},
addListeners: function(){
var self = this;
$(‘.clicky’).click(function(){
self.wasClicked()
});
}
};
clicky.addListeners();

3.2、对访问原始上下文的改进:

这里提供给一个proxy匿名函数进行调用apply方法:

var proxy = function(func, thisObject){
return(function(){
return func.apply(thisObject, arguments);
});
};
var clicky = {
wasClicked: function(){
/* … */
},
addListeners: function(){
var self = this;
$(‘.clicky’).click(proxy(this.wasClicked, this));
}
};

实际上,jQuery 也包含了实现这个功能的API,你或许已经猜到了,就是jQuery.proxy() :

$(‘.clicky’).click($.proxy(function(){ /* … */ }, this));

3.3、使用apply和call的原因:

使用apply() 和call() 还有其他很有用的原因,比如“委托”。我们可以将一个调用委托给另一个调用,甚至可以修改传入的参数:

var App {
log: function(){
if (typeof console == “undefined”) return;
// 将参数转换为合适的数组
var args = jQuery.makeArray(arguments);
// 插入一个新的参数
args.unshift(“(App)”);
// 委托给console
console.log.apply(console, args);
}
};

4、控制类的作用域:

上面提供的proxy匿名函数是一个非常有用的模式。我们应当将其添加至我们的“类”库中。我们在类和实例中都添加proxy 函数,这样就可以在事件处理程序之外处理函数的时候和下面这段代码所示的场景中保持类的作用域:

var Class = function(parent){
var klass = function(){
this.init.apply(this, arguments);
};
klass.prototype.init = function(){};
klass.fn = klass.prototype;
// 添加一个proxy 函数
klass.proxy = function(func){
var self = this;
return(function(){
return func.apply(self, arguments);
});
}
// 在实例中也添加这个函数
klass.fn.proxy = klass.proxy;
return klass;
};

类库中的proxy函数的使用:

var Button = new Class;
Button.include({
init: function(element){
this.element = jQuery(element);
// 代理了这个click 函数
this.element.click(this.proxy(this.click));
},
click: function(){ /* … */ }
});

如果我们没有使用proxy 将click() 的回调包装起来,它就会基于上下文this.element来调用,而不是Button,这会造成各种问题。在新版本的JavaScript——ECMAScript 5(ES5)中同样加入了bind() 函数用以控制调用的作用域。

bind函数实际上等同于上面的proxy函数,其使用如下:

Button.include({
init: function(element){
this.element = jQuery(element);
// 绑定这个click 函数
this.element.click(this.click.bind(this));
},
click: function(){ /* … */ }
});

但是对于一些老版本的浏览器,不支持bind()方法,如果需要兼容,则需要手动实现bind()函数的兼容性,下面就是一段实现了bind()函数的代码:

if (!Function.prototype.bind) {
Function.prototype.bind = function (obj) {
var slice = [].slice,
args = slice.call(arguments, 1),
self = this,
nop = function () {},
bound = function () {
return self.apply( this instanceof nop ? this : (obj || {}),
args.concat(slice.call(arguments)));
};
nop.prototype = self.prototype;
bound.prototype = new nop();
return bound;
};
}

5、添加私有函数:

我们可以利用JavaScript 匿名函数来创建私有作用域,这些私有作用域只能在内部访问:

var Person = function(){};
(function(){
var findById = function(){ /* … */ };
Person.find = function(id){
if (typeof id == “integer”)
return findById(id);
};
})();

我们将类的属性都包装进一个匿名函数中,然后创建了局部变量(findById),这些局部变量只能在当前作用域中被访问到。Person 变量是在全局作用域中定义的,因此可以在任何地方都能访问到。

定义变量的时候不要丢掉var 运算符,因为如果丢掉var 就会创建全局变量。如果你需要定义全局变量,在全局作用域中定义它或者定义为window 的属性:

(function(exports){
var foo = “bar”;
// 将变量暴露出去
exports.foo = foo;
})(window);
alert(foo);

6、类库:

jQuery 本身并不支持类,但通过插件的方式可以轻易引入类的支持,比如HJS(http://plugins.jquery.com/project/HJS)。HJS 允许你通过给$.Class.create 传入一组属性来定义类:

var Person = $.Class.create({
// 构造函数
initialize: function(name) {
this.name = name;
}
});

可以在创建类的时候传入父类作为参数,这样就实现了类的继承:

var Student = $.Class.create(Person, {
price: function() { /* … */ }
});
var alex = new Student(“Alex”);
alex.pay();

HJS 并不是我们的唯一选择,Spine(http://maccman.github.com/spine)同样实现了类,通过直接在页面中引入spine.js(http://maccman.github.com/spine/spine.js)来使用它。

如果你不想把视野局限于jQuery 的话,那就多关注一下Prototype(http://prototypejs.org/),它包含很多不错的API(http://prototypejs.org/learn/class-inheritance),并且是其他很多类库的灵感来源。

arthinking wechat
欢迎关注itzhai公众号