javascript的面向对象开发(一)
在我刚来到这个公司实习的时候,看到的javascript代码全部都是内嵌在标签中,一竖排的function,各种$(“element”).html(), $.ajax把页面功能给堆出来。如果不是自己写的根本别想一眼看懂,就算是自己写的过一段时间也糊涂了,可读性几乎为0。而且如果要改别人的代码,更是难度超级大,当时一度看得我想吐。js代码行数超级多,然而仔细一看后发现很多代码实现的是同一个功能或者相似的功能,然而它们却因为属于不同的页面或者业务流程而被完全隔离。。
解决这些问题的方法很简单,就是用javascript的面向对象开发方法。(原来javascript也能写面向对象哦!之前真是什么都不懂)页面上的视图组件,数据模型包括其他控制逻辑等,都可以用面向对象的方式来开发。好处是:
- 提高代码的复用率
- 更抽象,容易理解,可读性高
- 封装对象的内部逻辑,功能仅以接口的形式暴露,易于使用
我们来看一个例子。这里我们实现一个Car类,有setSpeed方法和run方法。
var Car = function(name) {
this.name = name;
this.distance = 0;
};
Car.prototype = {
setSpeed: function(speed) {
this.speed = speed;
},
run: function(time) {
this.distance = this.distance + this.speed * time;
console.log(this.name + " has runned for " + this.distance + " meters.");
}
}
var car = new Car("bmw");
car.setSpeed(10);
car.run(3);
//console: bmw has runned for 30 meters.
在上面的代码中,首先定义的function(name) 其实就是Car类型的构造函数,其中this指向的就是新创建的那个变量。构造函数中为car实例设置了name和distance两个属性。随后的Car.prototype为Car类型定义了setSpeed()和run()两个方法。
这里有两个要点:一个是prototype的含义,一个是this这个东西在js里到底是怎么一回事。
一、prototype
在javascript中,任意一个构造函数(构造函数和普通function的区别就是构造函数体中有对this的操作)都会有一个prototype属性。这个属性指向一个对象,任何由该构造函数创建的实例都会继承这个对象的所有属性。
在上文的例子中,car实例继承了prototype中的setSpeed()和run()两个属性。其实也可以不写定义prototype,而把所有继承关系都在构造函数体中实现,比如:
var Car = function(name) {
this.name = name;
this.distance = 0;
this.setSpeed = function(speed) {
this.speed = speed;
}
this.run = function(time) {
this.distance = this.distance + this.speed * time;
console.log(this.name + " has runned for " + this.distance + " meters.");
}
};
问:然而还是用prototype的模式更加的好,为什么?
答:这样的话,所有实例的setSpeed()和run()方法,其实都是同一个内存地址,指向prototype对象,在创建新的实例的时候不需要重新分配内存,所以能提高运行效率也节省空间。
与prototype相似的还有一个叫做proto的属性。与prototype不同,proto是属于具体的实例的属性。指向创造该实例的构造函数的prototype所指向的对象。 这两者的区别就是一个属于构造函数,一个属于实例。
prototype或__proto__链:
一个对象能够继承别的对象的属性,那么那个被继承的对象的属性也有可能继承自其他对象。和Java一样,一切对象的祖先是原生的Object{}类型。那么这里有一个问题,如果一个类型ClassA有methodA()属性,同时其构造函数的prototype中也有一个名为methodA()的属性,在var a = new ClassA()后,a的methodA到底是指向前者,还是后者呢?答案是前者,因为在寻找属性的时候,javascript永远先从本地的属性中寻找,如果找不到才会再找prototype中有没有该属性。
代码如下:
var Car = function(name) {
this.name = name;
this.distance = 0;
this.run = function(time) {
this.distance = this.distance + this.speed * time;
console.log(this.name + " has runned for " + this.distance + " kilometers.");
}
};
Car.prototype = {
setSpeed: function(speed) {
this.speed = speed;
},
run: function(time) {
this.distance = this.distance + this.speed * time;
console.log(this.name + " has runned for " + this.distance + " meters.");
}
}
var car = new Car("bmw");
car.setSpeed(10);
car.run(3);
//console: bmw has runned for 30 kilometers.
二、this的指向
this的指向是js的一个麻烦的地方,经常会有因为搞错了this的作用域导致程序bug的问题。总的来说,this的指向有这么几条规则。
In JavaScript, as in most object-oriented programming languages, this is a special keyword that is used within methods to refer to the object on which a method is being invoked.
也就是说,this指向当前函数执行时的当前对象。
问:所有的函数都有它所属的对象么?js中的匿名函数呢?
答:事实上,匿名函数也是有他所属的对象的。这个对象就是全局对象--在前端开发中,这个全局对象就是window对象。(在node环境中就不是了)这就是为什么在定义了window.a后,直接访问a也是可以的,反之亦然。
在上面的例子中,构造函数中的this指向就是函数执行时候其所属的对象,也就是新建的实例了。
这里顺带一提:所有用var声明的变量的作用域都是当前作用域。比如在for循环内用var声明的变量外部不可访问,而不使用var声明的变量,其作用域都是全局变量,也就是window。当要使用到一个变量时,首先访问当前作用域,如果没有则一层层向上寻找。
call和apply:
看这样一段代码:
function log() {
console.log(this.name);
}
window.name = "window";
var obj = { name: "obj"}
log();//console: window
log.call(obj);//console: obj
函数的call方法能够强行更改函数中this的指向,其实就是更改了log函数在执行似的当前对象(原本是window,现在被更改成了obj)。
apply和call方法相似,区别是传递参数的格式。
以上这些就是javascript面向对象开发的最最基础。在这里推荐一本书:雅虎大牛Douglas Crockford的《JavaScirpt语言精粹》,电子工业出版社。书如其名,助你写出最精粹的js代码。