最近看了一点有关js方面的文章,不错的东西记下来,这里主要简单讲解有关JavaScprit继承和面向对象编程方面的东西
首先,清楚两个概念
1、JavaScript是基于对象的,不是基于类的,
2、函数本身也是对象,
3、原型对象,
JavaScript 中的继承可以通过原型链来实现,调用对象上的一个方法,由于方法在 JavaScript 对象中是对另一个函数对象的引用,因此解释器会在对象中查找该属性,如果没有找到,则在其内部对象 prototype 对象上搜索,由于 prototype 对象与对象本身的结构是一样的,因此这个过程会一直回溯到发现该属性,则调用该属性,否则,报告一个错误。关于原型继承,我们不妨看一个小例子:
function Base(){
this .baseFunc = function (){
print ( "base behavior" );
}
}
function Middle(){
this .middleFunc = function (){
print ( "middle behavior" );
}
}
Middle. prototype = new Base();
function Final(){
this .finalFunc = function (){
print ( "final behavior" );
}
}
Final. prototype = new Middle();
function test(){
var obj = new Final();
obj.baseFunc();
obj.middleFunc();
obj.finalFunc();
}
图 原型链的示意图
在 function test 中,我们 new 了一个 Final 对象,然后依次调用 obj.baseFunc ,由于 obj 对象上并无此方法,则按照上边提到的规则,进行回溯,在其原型链上搜索,由于 Final 的原型链上包含 Middle ,而 Middle 上又包含 Base ,因此会执行这个方法,这样就实现了类的继承。
middle behavior
final behavior
引用
引用是一个比较有意思的主题,跟其他的语言不同的是, JavaScript 中的引用始终指向最终的对象,而并非引用本身,我们来看一个例子:
var obj = {}; // 空对象
var ref = obj; // 引用
obj. name = "objectA" ;
print ( ref . name ); //ref 跟着添加了 name 属性
obj = [ "one" , "two" , "three" ]; //obj 指向了另一个对象 ( 数组对象 )
print ( ref . name ); //ref 还指向原来的对象
print (obj. length ); //3
print ( ref . length ); //undefined
运行结果如下:
objectA
3
undefined
obj 只是对一个匿名对象的引用,所以, ref 并非指向它,当 obj 指向另一个数组对象时
可以看到,引用 ref 并未改变,而始终指向这那个后来添加了 name 属性的 " 空 " 对象 ”{}” 。理解这一点对后边的内容有很大的帮助。
new 操作符
有面向对象编程的基础有时会成为一种负担,比如看到 new 的时候, Java 程序员可能会认为这将会调用一个类的构造器构造一个新的对象出来,我们来看一个例子:
function Shape(type){
this .type = type || "rect" ;
this .calc = function (){
return "calc, " + this .type;
}
}
var triangle = new Shape( "triangle" );
print (triangle.calc());
var circle = new Shape( "circle" );
print (circle.calc());
运行结果如下:
calc, circle
Java 程序员可能会觉得 Shape 就是一个类,然后 triangle , circle 即是 Shape 对应的具体对象,而其实 JavaScript 并非如此工作的,罪魁祸首即为此 new 操作符。在 JavaScript 中,通过 new 操作符来作用与一个函数,实质上会发生这样的动作:
首先,创建一个空对象,然后用函数的 apply 方法,将这个空对象传入作为 apply 的第一个参数,及上下文参数。这样函数内部的 this 将会被这个空的对象所替代:
var triangle = new Shape( "triangle" );
// 上一句相当于下面的代码
var triangle = {};
Shape.apply(triangle, [ "triangle" ]);
封装
事实上,我们可以通过 JavaScript 的函数实现封装,封装的好处在于未经授权的客户代码无法访问到我们不公开的数据,我们来看这个例子:
function Person(name){
//private variable
var address = "The Earth" ;
//public method
this .getAddress = function (){
return address;
}
//public variable
this .name = name;
}
//public
Person.prototype.getName = function (){
return this .name;
}
//public
Person.prototype.setName = function (name){
this .name = name;
}
首先声明一个函数,作为模板,用面向对象的术语来讲,就是一个类 。用 var 方式声明的变量仅在类内部可见,所以 address 为一个私有成员,访问 address 的唯一方法是通过我们向外暴露的 getAddress 方法,而 get/setName ,均为原型链上的方法,因此为公开的。我们可以做个测试:
var jack = new Person( "jack" );
print(jack.name);//jack
print(jack.getName());//jack
print(jack.address);//undefined
print(jack.getAddress());//The Earth
直接通过 jack.address 来访问 address 变量会得到 undefined 。我们只能通过 jack.getAddress 来访问。这样, address 这个成员就被封装起来了。
另外需要注意的一点是,我们可以为类添加静态成员,这个过程也很简单,只需要为函数对象添加一个属性即可。比如:
function Person(name){
//private variable
var address = "The Earth" ;
//public method
this .getAddress = function (){
return address;
}
//public variable
this .name = name;
}
Person.TAG = "javascript-core" ;// 静态变量
print(Person.TAG);
也就是说,我们在访问 Person.TAG 时,不需要实例化Person 类。这与传统的面向对象语言如 Java 中的静态变量是一致的。
参考:http://hzjavaeyer.group.iteye.com/group/wiki/2281-JavaScript-Core