本编参考:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html 系列,总结如下
一.几个重要关键字
1.this —— 指当前的对象,若在全局范围,则指当前页面对象window,如果是函数中使用this,则指的是调用这个函数的对象。firebug一下demo:
function sayHi() {
console.debug(this);
}
var obj = {
sayHi: sayHi,
sayHi2: function() {
return function() {
console.debug(this);
}
}
};
sayHi(); // 结果为window对象
obj.sayHi(); // 结果为obj对象
obj.sayHi2()(); // 结果为window对象
解析:在javascript当中,一切皆为对象,function也是,所以可以看到全局函数sayHi可以像变量一般pass给obj的sayHi属性。三个函数调用的,前两个不用多解析,看第三个,其实等价于下面写法:
var f = obj.sayHi2();
f();
sayHi2函数返回一个函数对象,赋予变量f,f就是个函数了,然后 f 被window调用执行,如此就这么回事了。
再看一个demo:
function TestThis () {
this.oham = 'oham';
console.debug(this);
}
TestThis(); // window对象
new TestThis(); // TestThis 对象实例本身
对于TestThis();的结果不奇特,而new TestThis();之神奇在于new,其内部机制在下就不懂了...只可以看出,通过new XXX();这样调用,XXX方法中的this就指向XXX构造出的对象实例本身了。
2.apply与call
接着上面的例子,介绍apply 与 call,它们均是全局函数,归Function所有(Function属于javascript内部对象,不是Function的对象是没有这两个方法的)。 接上文的demo:
...
obj.sayHi2()(); //结果为window对象
obj.sayHi2().apply(obj); // 结果为obj对象
obj.sayHi2().call(obj); // 结果为obj对象
apply 与 call 的 作用是改变函数中this的指向,即使函数看起来好像被谁调用一样,其作用域也是那个谁的。
apply 与 call 的区别,仅仅是参数定义不同,请看如下demo:
function ml(me, mm) {
console.log(this);
if( this === window ) {
console.log(me + ',' + mm + ' not possible in public');
}else {
console.log(me + ',' + mm + ' so happy');
}
}
var hotel = {};
ml('me', 'mm');
ml.apply(hotel, ['me', 'mm']);
ml.call(hotel, 'me', 'mm');
请实践一下,代码,便知知道,apply的定义的参数列表是个数组,call是参数列(估计是function call(obj, params...))。
**至此深觉有必要介绍javascript中闭包的概念。闭包,指的是语法上表示包含不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。
demo(以下摘自w3cschool):
var sMessage = "hello world";
function sayHelloWorld() {
alert(sMessage);
}
sayHelloWorld();
在上面这段代码中,脚本被载入内存后,并没有为函数 sayHelloWorld() 计算变量 sMessage 的值。该函数捕获 sMessage 的值只是为了以后的使用,也就是说,解释程序知道在调用该函数时要检查 sMessage 的值。sMessage 将在函数调用 sayHelloWorld() 时(最后一行)被赋值,显示消息 "hello world"。
再看看自己的一个demo:
var outSide = 'madom';
var room = {
mm : 'mm',
watch: function(me) {
return function () {
if(this.outSide) { //尝试
console.debug(me + ' can watch ' + this.outSide);
}else {
console.debug(me + ' can\'t watch outSide, but never mind.');
}
if(this.mm) { //尝试
console.debug(me + ' can watch ' + this.mm);
}else {
console.debug(me + ' can\'t watch mm, not happy');
}
}
},
touch: function (me) {
if(this.outSide) { //尝试
console.debug(me + ' can touch ' + this.outSide + ', but won\'t do that');
}else {
console.debug(me + ' can\'t touch outSide, but never mind.');
}
if(this.mm) { //尝试
console.debug(me + ' can touch ' + this.mm + ', so cool...');
}else {
console.debug(me + ' can\'t touch mm, not happy');
}
}
};
var watchFunc = room.watch('I');
//全局调用
watchFunc();
console.log('-------------------------------------');
//room范围调用
watchFunc.apply(room);
console.log('============================================');
//room范围调用
room.touch('I');
console.log('----------------------------------------');
//全局调用
room.touch.apply(this, ['I']);
结果为:
"I can watch madom"
"I can't watch mm, not happy"
"-------------------------------------"
"I can't watch outSide, but never mind."
"I can watch mm"
"============================================"
"I can't touch outSide, but never mind."
"I can touch mm, so cool..."
"----------------------------------------"
"I can touch madom, but won't do that"
"I can't touch mm, not happy"
这个结果好解析,this为谁调用就是谁,接着请尝试把demo中标有//尝试的if 语句中的"this."删除,
情况会如下:
"I can watch madom"
"Uncaught ReferenceError: mm is not defined (line 15)"
执行到watch函数的if(mm) 这句出错了,无论apply部apply结果都一样,原因具体我就不清楚了,这里只是证实了,对于一个变量如name, name 并不是等价于this.name(跟java大不同) ,JS的解释程序不会为你做这个,当函数被调用的时候,它只会先从当前函数范围去找,若无命中变量,则直接到全局范围去找。可以把mm部分的代码去掉,只留outSide的,结果是无论apply与否,结果都能访问的变量outSide。
3.prototype——对该对象的对象原型的引用。对于所有的对象,它默认返回 Object 对象的一个实例(w3c定义)。
prototype本质上还是一个JavaScript对象,个人觉得上述定义不太妥当,实际上prototype感觉更像对象的一个模板,而非实例, 并且每个函数都有一个默认的prototype属性。 以demo为证:
var room = {
mm: 'mm',
watch: function () {
}
};
console.log(room.prototype); // undefined
console.log(room.valueOf()); // room object
现在修改demo如下:
var outSide = 'madom';
function Room () {
this.mm = 'mm';
}
console.log(Room.prototype); //Room{}
console.log(Room.mm); // undefined
console.log(new Room().prototype); //undefined
console.log(new Room().mm); // mm
console.log(new Room().valueOf()); // Room {mm: "mm"}
console.log(Room.valueOf()); // function Room(){this.mm = 'mm';}
解析,当声明 function Room的时候,Room便是javascript的固有对象Function,Function 有自己的一个空的prototype,同时也说明Room.mm为什么是undefined,因为Room此时仅仅是个Function对象,javascript的Function对象当中必定没有mm的定义。
对于后new Room()的两条语句的结果,因为new Room()之后,javascript解析器会执行Room函数本身,返回一个具体的对象实例,该对象包含了Room() 方法体内定义的所有东西。这里new Room() 返回的对象实例跟var room = {...}的区别,请看demo:
function Room () {
this.mm = 'mm';
}
var room = {
mm : 'mm'
};
console.log(new Room().valueOf()); // Room {mm: "mm"}
console.log(room.valueOf()); // Object {mm: "mm"}
可见,Room 是一个类型为Room的实例, 而room是一个Object的实例。当然此时两者的prototype都为空,以上的demo我拿mm属性来测试,是为了说明prototype本质也是个对象,可以像个普通的对象属性那般玩,只是javascript会对名为prototype的属性特别对待,那prototype到底为何用?demo:
function Room () {
this.mm = 'mm';
}
Room.prototype.watch = function(){}; // Room 本身为Function,有个空的prototype属性,js解析器执行new 操作的时候会特别对待prototype属性
var room = {
mm : 'mm'
};
room.prototype.watch = function() {}; //报错 room 本身为Object 类型对象,不具备prototype属性的定义
console.log(new Room()); // Room {mm: "mm", watch: function}
从上面的结果可以看出,prototype 对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype 对象的任何属性和方法都被传递给那个类的所有实例;对于Object,充其量只是个普通的属性。
这样的意义是,此时Room相当于一个构造函数(constructor),我们在构造函数内定义属性与方法,当某天我们需要构建一种对象BabyRoom,包含Room的所有特性,此时,我们只需把Room的prototype 赋予 BabyRoom Function 就可以了,而不必重新另行定义(这是本篇后话)于是使用BabyRoom构造出的对象除了有自身特性外,还包含Room对象的prototype中的一切。
4. constructor——指创建当前对象的构造函数
上述Room就是一个constructor,demo:
function Room () {
this.mm = 'mm';
}
Room.prototype.watch = function(){};
var room = {
mm : 'mm'
};
console.log(new Room().constructor == Room); // true
console.log(room.constructor == Object); // true
当constructor 与 prototype 相遇, demo:
function Room () {
this.mm = 'mm';
}
Room.prototype.watch = function(){};
console.log(new Room().constructor === Room); // Room对象的实例的构造为Room无疑
console.log(Room.prototype.constructor === Room);// 构造函数Room的prototype的构造函数是自己本身
console.log(new Room().constructor.prototype.constructor === Room); /合并上两句得此结论
现在修改demo如下:
function Room () {
this.mm = 'mm';
}
Room.prototype = { //改了这里
watch: function() {}
};
console.log(new Room().constructor === Room); //false , Room的对象实例的构造不再是Room了
console.log(Room.prototype.constructor === Room); // 不再是Room
console.log(new Room().constructor.prototype.constructor === Room); // false
console.log(new Room().constructor === Object); //true
console.log(Room.prototype.constructor === Object); //true
console.log(new Room().constructor.prototype.constructor === Object); //true
原因是修改之后的Room的prototype的声明形式等同于:
Room.prototype = new Object({
watch: function() {}
});
如此看来,prototype的构造当然是Object了,这就造成用Room构造的对象实例的构造也成了Object了。这样引起的问题倒没什么,只是prototype作为一个对象模板,应用在实现继承的时候,其构造却不对应类构造函数本身,这有点说不过去。
修正的办法,网上如是说:
Room.prototype.constructor = Room; // 个人猜测是子类对象调用instanceOf 时能够有正确的结果
demo:
function Room () {
this.mm = 'mm';
}
Room.prototype = {
watch: function() {}
};
Room.prototype.constructor = Room; // 重新覆盖Room.prototype.constructor
console.log(new Room().constructor === Room); //true
console.log(Room.prototype.constructor === Room); //true
console.log(new Room().constructor.prototype.constructor === Room); //true
console.log(new Room().constructor === Object); //false
console.log(Room.prototype.constructor === Object); //false
console.log(new Room().constructor.prototype.constructor === Object); //false
console.log(new Room() instanceof Room ); //无论覆盖Room.prototype.constructor与否, 结果都为true
二、javascript的继承实现
原始版:按照上述关键字的陈述,估计不难给出下面实现:
//定义一个父类 Huamn
function Human(name, gender) {
this.name = name;
this.gender = gender;
this.skills = new Array('talk', 'walk', 'sleep');
}
Human.prototype = {
ml: function () {
if(this.gender == 'male'){
console.log(this.name + ' is feeling hign');
}else if(this.gender == 'female' ){
console.log(this.name + ' is screaming');
}else{
console.log('-_-!...');
}
}
};
//继承 父类Huamn,定义一个Superman类
function Superman(name, gender) {
this.name = name;
this.gender = gender;
}
//为了把prototype中的一切以及Huamn的一些属性都继承过来(如skills),因而不是Superman.prototype = Human.prototype
// 那为什么不都在prototype把属性定义完?这里做个mark,回头给你看看
Superman.prototype = new Human();
Superman.prototype.constructor = Superman;
Superman.prototype.fly = function () {
console.log(this.name + ' is flying');
}
var sMM = new Superman('mm', 'female');
sMM.ml(); //调用了Human的ml方法
sMM.fly(); //调用子类Superman的方法
先说明为什么不都在prototype把属性定义完,看看下面修改后的demo:
//定义一个父类 Huamn
function Human(name, gender) {
this.name = name;
this.gender = gender;
//this.skills = new Array('talk', 'walk', 'sleep');
}
Human.prototype = {
ml: function () {
if(this.gender == 'male'){
console.log(this.name + ' is feeling high');
}else if(this.gender == 'female' ){
console.log(this.name + ' is screaming');
}else{
console.log('-_-!...');
}
},
skills: new Array('talk', 'walk', 'sleep') // 在prototype中定义skills
};
//继承 父类Huamn,定义一个Superman类
function Superman(name, gender) {
this.name = name;
this.gender = gender;
}
Superman.prototype = new Human();
Superman.prototype.constructor = Superman;
Superman.prototype.fly = function () {
console.log(this.name + ' is flying');
}
var sMe = new Superman('me', 'male');
var hMM = new Human('mm', 'female');
console.log(sMe.skills); // "talk", "walk", "sleep"
console.log(hMM.skills); // "talk", "walk", "sleep"
sMe.skills.push('fly');
console.log(sMe.skills); // "talk", "walk", "sleep", "fly"
console.log(hMM.skills); //"talk", "walk", "sleep", "fly" ,这就不对了
这里只对sMe实例的skills push了一个'fly',但hMM的skills却同时多了'fly',这样是不对的。所谓对象模板,即所有的实例都共享这一模板,而且本身又是个对象,于是就所以然了。
这里的原始版的继承实现不得不指出:
- Superman.prototype = new Human(); 实属不妥,我构建Superman的类,却先实例化一个Huamn对象,如此而然,当new Superman()的时候,实例sMe里会有两套this.name ,this.gender——因为Superman.prototype = new Human(),之后,等价于:
function Superman(name, gender){
this.name = name;
this.gender = gender;
prototype = function Human() {
this.name = name;
this.gender = gender;
this.skills = new Array('talk', 'walk', 'sleep');
...
}
}
所以可以预言,即便Human的skills不放在prototype中,对于两个不同的Superman实例仍会出现demo2 的问题,demo:
//定义一个父类 Huamn
function Human(name, gender) {
this.name = name;
this.gender = gender;
this.skills = new Array('talk', 'walk', 'sleep');
}
Human.prototype = {
ml: function () {
if(this.gender == 'male'){
console.log(this.name + ' is feeling hign');
}else if(this.gender == 'female' ){
console.log(this.name + ' is screaming');
}else{
console.log('-_-!...');
}
}
};
//继承 父类Huamn,定义一个Superman类
function Superman(name, gender) {
this.name = name;
this.gender = gender;
}
Superman.prototype = new Human();
Superman.prototype.constructor = Superman;
Superman.prototype.fly = function () {
console.log(this.name + ' is flying');
}
var sMe = new Superman('me', 'male');
var hMM1 = new Human('mm1', 'female');
var hMM2 = new Superman('mm2', 'female');
console.log(sMe); // "talk", "walk", "sleep"
console.log(hMM1.skills); // "talk", "walk", "sleep"
console.log(hMM2.skills); // "talk", "walk", "sleep"
sMe.skills.push('fly'); //sMe超人的skills才包含fly
console.log(sMe.skills); // "talk", "walk", "sleep", "fly"
console.log(hMM1.skills); //"talk", "walk", "sleep"
console.log(hMM2.skills); //"talk", "walk", "sleep", "fly" ,这就不对了
- 感觉很挫,说是继承,但Superman需要知道太多Human的细节,感觉还不如重新单独搞个Superman,然后拿Human的copy 来的优雅。
改良版:必须解决原始版的Superman.prototype = new Human();问题,然后封装继承的细节:
// 将继承的细节封装到一个全局自定义函数Class
// 用法是: var Human = Class({inti:function(name, gender){...}, ml:function(){...}, ...});
// 即inti是必须要得,初始化属性用的
function Class(parentCls, props) {
if( (typeof parentCls) === 'object' ) { // 创建类构造函数时的情形,而非继承一个类
props = parentCls;
parentCls = null; // 把parentCls为null做成一个flag,使得下面的处理为创建类,而非继承类
}
this._initCls = false;
//无论是构建类还是继承类,Class终究要返回一个类的构造函数
//cFunc将作为最终的返回值
function cFunc() {
if( !_initCls && this.init ) {
if(parentCls)
this.baseprototype = parentCls.prototype; //拿着父类的prototype,有方无便
// _initCls用作继承时的情形,避免init分别被父子类调用两次
// 因为实现继承需要cFunc.prototype = new parentCls();
// 当new YourClass()去构建类对象实例时,无疑会造成上述原始版中提到的属性重复以及prototype定义属性问题,这里设定为当真正new cFunc()的时候才调用init方法
// 注意 无论parentCls 还是YourCls,本质都是这里的有Class返回的cFunc
this.init.apply(this, arguments);
}
}
if( parentCls ){
//从parentCls 继承一切, 所以设_initCls 为false避免下面new parentCls()时执行parentCls的init函数
this._initCls = true;
cFunc.prototype = new parentCls();
cFunc.constructor = cFunc;
//把_initCls设置false,当真正调用new cFunc();的时候才调用init函数
//因为只用那时候才能执行YourCls自己的init函数
this._initCls = false;
}
//配置cFunc函数,把props的一切传入cFunc
for( var name in props ) {
if( props.hasOwnProperty(name) ) {
//覆盖父类parentCls的同名函数
if( parentCls && (typeof props[name]) === 'function'
&& (typeof cFunc.prototype[name]) === 'function' ) {
cFunc.prototype[name] = (function(name, fn) {
//这是闭包用法,返回一个函数
return function() {
this.base = parentCls.prototype[name]; // 存储父类的同名函数,相当于java的super(),供子类调用父类方法用
return fn.apply(this, arguments); // 调用子类自己定义的方法, 这里用return是考虑到有些方法是有返回值得
}
})(name, props[name]);
} else {
cFunc.prototype[name] = props[name];
}
}
}
return cFunc;
}
// ----------------测试:创建一个类Huamn,一个子类Superman--------------------
var Human = Class({
init: function(name, gender){
this.name = name;
this.gender = gender;
this.skills = new Array('talk', 'walk', 'sleep');
},
ml: function() {
if(this.gender == 'male'){
console.log(this.name + ' is feeling high');
}else if(this.gender == 'female' ){
console.log(this.name + ' is screaming');
}else{
console.log('-_-!...');
}
}
});
var Superman = Class(Human, {
pangzi: 'red', //定义一个变量胖次(内裤),默认为红色,次变量将放入prototype中
// 此处没定义init方法,当new Superman()的时候会调用父类的,把name跟gender设置好
ml: function() {
this.base();
console.log('Power up');
},
fly: function() {
console.log('fly up high');
}
});
var sMe = new Superman('me', 'male');
var hMM = new Human('mm', 'female');
var sMe2 = new Superman('me2', 'male');
console.log(sMe);
console.log(hMM);
sMe.skills.push('fly');
console.log(sMe.skills); // ["talk", "walk", "sleep", "fly"]
console.log(sMe2.skills); // ["talk", "walk", "sleep"]
console.log(hMM.skills); // ["talk", "walk", "sleep"]
sMe.pangzi = 'none'; //脱掉
console.log(sMe.pangzi); // none
console.log(sMe2.pangzi); // red
sMe.ml();
hMM.ml();
整理一下思路,1.根据传入的参数判断是创建类还是继承创建类。
2. 定义初始的类构造函数cFunc —— 可以理解为一个未成型的胚胎,里面主要是配置使得真正被调用是调用传进的init方法去初始化属性值
3. 若是继承操作,通过形如Subxxx.prototype = new Superxxx(); 将父类的一切传给子类
4. 配置子类自己定义的属性以及方法。
注意一点,看看上面的内裤(pangzi),变量,没有将其放入init方法去初始化,那么它将藏入prototype中,上文提到,prototype的非方法属性问题,(好比java的一个静态变量),但上面测试的我把它脱了,按道理sMe2的也会脱了才对。。。若现在把pangzi的定义改为如下:
pangzi: {color:'red'},
...
//测试代码改为
sMe.pangzi.color = 'none';
console.log(sMe.pangzi); //none
console.log(sMe2.pangzi); //none
这里我估计原因是javascript当中对于基本数据类型的处理方式,回顾上文的skills数组,也是个Object,也是出问题。具体的在下是不懂了。
改良版有两个问题:
- 引入了全局变量_initCls。
- 请看上面一段代码:
cFunc.prototype[name] = (function(name, fn) {
//这是闭包用法,返回一个函数
return function() {
this.base = parentCls.prototype[name]; // 存储父类的同名函数,相当于java的super(),供子类调用父类方法用
return fn.apply(this, arguments); // 调用子类自己定义的方法, 这里用return是考虑到有些方法是有返回值得
}
})(name, props[name]);
注意this.base,当子类调用父类的同名方法的时候,this指的是具体的子类对象,即,每调用一下父类同名方法,this.base就指向父类的同名方法了,在下不清楚javascript有无并发机制,或者某些js框架有无,若有,则这种写法就不行了,若无,则就调用结果而言,并无大碍,但总觉有点扯蛋。
现在介绍优雅版,解决改良版的两个问题,先看看是如何调用的:
var Human = Class.extends({
init: function(name, gender){
this.name = name;
this.gender = gender;
this.skills = new Array('talk', 'walk', 'sleep');
},
ml: function() {
if(this.gender == 'male'){
console.log(this.name + ' is feeling high');
}else if(this.gender == 'female' ){
console.log(this.name + ' is screaming');
}else{
console.log('-_-!...');
}
}
});
var Superman = Human.extends({
pangzi: 'red',
ml: function() {
this._super();
console.log('Power up');
},
fly: function() {
console.log('fly up high');
}
});
优雅版的实现(John Resig关于JavaScript继承的一个实现,John Resig,jQuery的创始人)
(function() {// js 一加载便执行该方法,即方法中的this引用的是window对象,这样做主要是运用闭包把改良版中的全局变量消除
var _initCls = false;
// fnTest为一正则表达式,用于判断子类中有无对父类的引用
var fnTest;
if( /xyz/.test(function(){ xyz; }) ) { //先看看浏览器对test的支持如何
fnTest = /\b_super\b/;
}else {
alert('Sorry, Web Browser not support.');
}
this.Class = function() {}; //此实现优雅之所在
//这里看看上文的调用demo便知Class指的是父类,所以在调用的extends方法中,this指的是Class,即父类
Class.extends = function (props){
var _super = this.prototype;
_initCls = true;
var prototype = new this();
_initCls = false;
for( var name in props ) {
// 判断当前方法是否对父类的同名方法有覆盖
if ( (typeof props[name]) === 'function'
&& (typeof _super[name]) === 'function'
&& fnTest.test(props[name]) ) {
prototype[name] = (function(name, fn) {
return function() {
//此处先把_super缓存起来,因为这里要执行子类的父类同名方法,
//于是在fn.apply之前,把this._super先指向父类的同名方法,
//如此就可以在子类的方法体内使用this._super()去调用父类的方法
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
//当方法执行完毕,马上把this._super设置回原始值
// 但这样并无完全解决改良版中的问题2,要是js将来引进并发机制,问题依旧,
//只是,比起改良版中的做法,这里就不扯蛋了
this._super = tmp;
return ret;
}
})(name, props[name]);
} else {
prototype[name] = props[name];
}
}
//类的构造函数
function ClsFunc() {
if( !_initCls && this.init )
this.init.apply(this, arguments);
}
ClsFunc.prototype = prototype;
ClsFunc.constructor = ClsFunc;
// 子类自动获取extends方法,arguments.callee指向当前正在执行的函数,即Class.extends
ClsFunc.extends = arguments.callee;
return ClsFunc;
};
})();
//------------------------------测试demo----------------------------------------
var Human = Class.extends({
init: function(name, gender){
this.name = name;
this.gender = gender;
this.skills = new Array('talk', 'walk', 'sleep');
},
ml: function() {
if(this.gender == 'male'){
console.log(this.name + ' is feeling high');
}else if(this.gender == 'female' ){
console.log(this.name + ' is screaming');
}else{
console.log('-_-!...');
}
}
});
var Superman = Human.extends({
pangzi: 'red',
ml: function() {
this._super();
console.log('Power up');
},
fly: function() {
console.log('fly up high');
}
});
var sMe = new Superman('me', 'male');
var hMM = new Human('mm', 'female');
var sMe2 = new Superman('me2', 'male');
console.log(sMe);
console.log(hMM);
sMe.skills.push('fly');
console.log(sMe.skills); // ["talk", "walk", "sleep", "fly"]
console.log(sMe2.skills); // ["talk", "walk", "sleep"]
console.log(hMM.skills); // ["talk", "walk", "sleep"]
sMe.pangzi = 'none'; //脱掉
console.log(sMe.pangzi); // none
console.log(sMe2.pangzi); // red
sMe.ml();
hMM.ml();
关于优雅版如何除掉改良版中的全局变量在此作简述,首先看看下面一段demo:
//这里的test方法的局部变量_init,作用范围为test方法体,包括retFunc,执行retFunc方法的时候,javascript解析器会先从retFunc方法体找_init,找不到,再从初始化retFunc的作用域范围找(即,test(),而非function test(){...})
function test(p) {
var _init = false;
if(p) { //若有参数传入,设_init为true
_init = true;
}
function retFunc() { //运用闭包,使之可以使用_init变量
if( _init == true ) {
console.log('log me true');
}else {
console.log('log me false');
}
}
return retFunc;
}
function objFunc() {
var _inti = false;
function ownRet() {
if( _inti == true ) {
console.log('log me true');
}else {
console.log('log me false');
}
}
return ownRet;
};
var ret = test();
ret(); // log me false, 此时ret的初始化作用域为test(), 域中_inti为false
var ret2 = test('3'); //log me true, 此时ret的初始化作用域为test('3'), 域中_inti为true
ret2();
objFunc.ret = ret2; //log me true,方法亦对象,ret2致死其初始化作用域都为test('3'),干爹可以变,生爹无法变
objFunc.ret();
objFunc()(); //log me false, 此处执行的是objFunc的ownRet,初始化作用域为objFunc()
现在可以回头理解优雅版如何解决改良版的问题1了。
其实可以根据优雅版的实现,对改良版进行稍稍的修改,推出改良版2:
(function () { //js加载即启动,参照优雅版的
this.Class = function(){}; //照搬优雅的核心
var _initCls = false; // 不再是全局变量
Class.extends = function(props){ //将原来的类函数构造逻辑移入extends中
var parentCls = null;
if( this !== Class ) {
parentCls = this;
}
function cFunc() {
if( !_initCls && this.init ) {
if( parentCls )
this.baseprototype = parentCls.prototype;
this.init.apply(this, arguments);
}
}
cFunc.extends = arguments.callee;
if( parentCls ) {
_initCls = true;
cFunc.prototype = new parentCls();
cFunc.prototype.constructor = cFunc;
_initCls = false;
}
for( var name in props ) {
if( props.hasOwnProperty(name) ) {
if( parentCls && (typeof props[name]) === 'function'
&& (typeof cFunc.prototype[name]) === 'function'
&& /\b_super\b/.test(props[name]) ) { //参照优雅版的逻辑
cFunc.prototype[name] = (function(name, fn) {
return function() {
this._super = parentCls.prototype[name]; //参照优雅版的逻辑
var ret = fn.apply(this, arguments);
this._super = null;
return ret;
}
})(name, props[name]);
} else {
cFunc.prototype[name] = props[name];
}
}
}
return cFunc;
};
})();
//------------------------------测试demo----------------------------------------
var Human = Class.extends({
init: function(name, gender){
this.name = name;
this.gender = gender;
this.skills = new Array('talk', 'walk', 'sleep');
},
ml: function() {
if(this.gender == 'male'){
console.log(this.name + ' is feeling high');
}else if(this.gender == 'female' ){
console.log(this.name + ' is screaming');
}else{
console.log('-_-!...');
}
}
});
var Superman = Human.extends({
pangzi: 'red',
ml: function() {
this._super();
console.log('Power up');
},
fly: function() {
console.log('fly up high');
}
});
var sMe = new Superman('me', 'male');
var hMM = new Human('mm', 'female');
var sMe2 = new Superman('me2', 'male');
console.log(sMe);
console.log(hMM);
sMe.skills.push('fly');
console.log(sMe.skills); // ["talk", "walk", "sleep", "fly"]
console.log(sMe2.skills); // ["talk", "walk", "sleep"]
console.log(hMM.skills); // ["talk", "walk", "sleep"]
sMe.pangzi = 'none'; //脱掉
console.log(sMe.pangzi); // none
console.log(sMe2.pangzi); // red
sMe.ml();
hMM.ml();
最后,尝试理解库类级源码对继承的实现——源自Prototypejs
先看调用方式:
var Human = Class.create({
initialize: function(name, gender){
this.name = name;
this.gender = gender;
this.skills = new Array('talk', 'walk', 'sleep');
},
ml: function() {
if(this.gender == 'male'){
console.log(this.name + ' is feeling high');
}else if(this.gender == 'female' ){
console.log(this.name + ' is screaming');
}else{
console.log('-_-!...');
}
}
});
var Superman = Class.create(Human, {
pangzi: 'red',
ml: function($super) { //此处将$super作为父类方法对象引用,然后当成参数传入子类同名方法体内
$super(); // 于是就可以这样调用父类的方法了
console.log('Power up');
},
fly: function() {
console.log('fly up high');
}
});
var sMe = new Superman('me', 'male');
var hMM = new Human('mm', 'female');
var sMe2 = new Superman('me2', 'male');
console.log(sMe);
console.log(hMM);
sMe.skills.push('fly');
console.log(sMe.skills); // ["talk", "walk", "sleep", "fly"]
console.log(sMe2.skills); // ["talk", "walk", "sleep"]
console.log(hMM.skills); // ["talk", "walk", "sleep"]
sMe.pangzi = 'none'; //脱掉
console.log(sMe.pangzi); // none
console.log(sMe2.pangzi); // red
sMe.ml();
hMM.ml();
看看其继承的实现方式:
var Prototype = {
emptyFunction: function() { }
};
// 用于把参数转换为数组形式
function $A(iterable) {
if( !iterable ) return [];
if( iterable.toArray ) return iterable.toArray();
var length = iterable.length || 0;
var results = new Array(length);
while ( length-- )
results[length] = iterable[length];
return results;
}
// 直接将source的own properties复制给destination
Object.extend = function(destination, source) {
for( var property in source )
destination[property] = source[property];
return destination;
};
// 定义一组通用方法,类比java的Object类的equals,hashCode 等方法
Object.extend(Object, {
//获取对象中的属性
keys: function(object) {
var keys = [];
for (var property in object)
keys.push(property);
return keys;
},
//判断对象是否为方法对象
isFunction: function(object) {
return typeof object == "function";
},
// 判断对象undifined
isUndefined: function(object) {
return typeof object == "undefined";
}
});
Object.extend(Function.prototype, {
//此方法运用正则表达式抽取function的参数名,具体:
// 对于regexp: /^[\s\(]*function[^(]*\(([^\)]*)\)/,match出来一个数组,
// 下标为1的就是function(param1, param2, ...) 中的"param1, param2, ...“字符串
// .replace(/\s+/g, ''), 将字符串的空白字符除掉,最后根据","split成数组
argumentNames: function() {
var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g, '').split(',');
return names.length == 1 && !names[0] ? [] : names;
},
// 此方法用于,举例,将方法meFunc通过 meFunc.bind(mmSession);
// 邦定到mmSession去准备执行,与apply类似,不过这里只拿第一个参数作邦定
// 与apply不一样的是,这里将方法对象返回,而不会执行_method
bind: function() {
if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
var _method = this;
var args = $A(arguments);
var object = args.shift();
//返回一个方法对象
return function() {
// 这里为何 args.concat($A(arguments))? 只因此时此处是处于一个方法对象的闭包当中,
// 所以此$A(arguments) 不同于上面的$A(arguments), 这里是当方法对象被执行时的参数
return _method.apply(object, args.concat($A(arguments)));
}
},
// 首先要明确这个方法是约定由function调用,作用是把调用wrap的那个方法
// 经过邦定后将其作为参数传给wrapper方法,其意图是用于重写父类的同名方法时,
// 子类的方法中拥有了父类方法的引用了
wrap: function(wrapper) {
var _method = this;
return function() {
return wrapper.apply(this, [_method.bind(this)].concat($A(arguments)));
}
}
});
// 扩展Array,加入一个first方法,返回第一个元素
Object.extend(Array.prototype, {
first: function() {
return this[0];
}
/*
,...其他方法定义
*/
});
// 定义全局变量Class
var Class = {
// 基本跟前面讲的继承实现一致
create: function() {
var parent = null;
var properties = $A(arguments);
// 判断是否为继承操作
if( Object.isFunction(properties[0] ) )
parent = properties.shift();
function klass() {
this.initialize.apply(this, arguments);
}
Object.extend(klass, Class.Methods);
klass.superclass = parent;
klass.subclasses = [];
// 这里为创建类时不调用父类的构造函数提供了一种新的途径
// 使用一个中间过渡类, 与前面用一个boolean的做法较之,这里优雅多了
if( parent ) {
var subclass = function(){};
subclass.prototype = parent.prototype;
klass.prototype = new subclass();
parent.subclasses.push(klass);
}
for( var i = 0; i < properties.length; i++ )
// 注意此处的addMethods
klass.addMethods(properties[i]);
if( !klass.prototype.initialize )
klass.prototype.initialize = Prototype.emptyFunction;
klass.prototype.constructor = klass;
return klass;
}
};
Class.Methods = {
// 此方法用于将source中的方法取出,然后放入子类的prototype中,
// 另外,实现父类的同名方法重写
addMethods: function (source) {
var ancestor = this.superclass && this.superclass.prototype;
var properties = Object.keys(source);
// 对于IE8 ... for ( var property in source ) 是遍历不出toString的,
// 所以此code是针对IE8的
if( !Object.keys({toString: true}).length )
properties.push('toString', 'valueOf');
//遍历属性
for( var i=0, length=properties.length; i<length; i++ ) {
var property = properties[i]; // 属性名
var value = source[property]; // 属性值
// 判断若有对父类方法的重写, 则使得参数中的 $super 指向父类的同名方法
if( ancestor && Object.isFunction(value) && value.argumentNames().first() == '$super' ) {
// 这是子类的方法
var method = value;
// 分析此句:运用闭包,获取父类同名方法对象ancestor[m], 将其包裹与一个方法对象返回,
// 目的是使得父类同名方法对象ancestor[m]在子类范围“this”中执行
// 返回的方法对象调用wrap,目的是将经过包裹的父类方法对象传给子类方法method
// 按上分析,这里用 value = ancestor[property].wrap(method);
value = (function(m) {
return function() { return ancestor[m].apply(this, arguments) };
})(property).wrap(method);
// 此二句code目的是将vauleOf和toString还原,
// 因为上面调用了wrap,看看wrap方法实现,其返回一个包裹方法对象,
// 所以此时的valueOf与toString是描述包裹方法的,而不是method,
// 此时绑定一下就是使其变回描述method
value.valueOf = method.valueOf.bind(method);
value.toString = method.toString.bind(method);
}
this.prototype[property] = value;
}
return this;
}
/*
,...还有其他的方法定义
*/
};
若理解上述,试着参照prototypeJS的继承实现方式重写我们自己的实现方式(使用改良版进行改造):
// 这个方法是盗用Prototypejs中的定义
function argumentNames(fn) {
var names = fn.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g, '').split(',');
return names.length == 1 && !names[0] ? [] : names;
}
function Class(parentCls, props) {
if( (typeof parentCls) === 'object' ) {
props = parentCls;
parentCls = null;
}
function cFunc() {
if(parentCls)
this.baseprototype = parentCls.prototype;
this.initialize.apply(this, arguments);
}
// 盗用prototypejs的优雅写法,以消去全局变量
if( parentCls ) {
var subclass = function(){};
subclass.prototype = parentCls.prototype;
cFunc.prototype = new subclass();
cFunc.prototype.constructor = cFunc;
}
//配置cFunc函数,把props的一切传入cFunc
for( var name in props ) {
if( props.hasOwnProperty(name) ) {
//覆盖父类parentCls的同名函数
if( parentCls && (typeof props[name]) === 'function'
&& argumentNames(props[name])[0] === "$super" ) {
cFunc.prototype[name] = (function(name, fn) {
return function() {
var method = this;
$super = function() {
return parentCls.prototype[name].apply(method, arguments);
}
// 此处无深意,只是技巧地,像Java中调用静态方法一样,调用concat将$super
// 整合如方法参数中
return fn.apply(this, Array.prototype.concat.apply($super, arguments));
}
})(name, props[name]);
} else {
cFunc.prototype[name] = props[name];
}
}
}
return cFunc;
}
测试demo就不写了,不能再让mm scream 了。。。
在下承认确实费了吃奶的劲去弄懂上述,总结出把握js的闭包运用是关键。玩弄透彻javascript的继承,对一些js框架如dojo,extJs,以及正在学的Angular,即使当中很多跟本篇不搭干,想必也不会觉其太诡异,太晦涩。
最后介绍一个很帅的在线前端测试工具:http://jsfiddle.net/
分享到:
相关推荐
4. **类(Class)**:ES6引入了类语法糖,使得JavaScript的面向对象更加符合传统面向对象语言的写法,但实际上,这些“类”仍然基于原型实现。 ```javascript class Person { constructor(name) { this.name = ...
5. 类(Class)语法:ES6引入的类语法糖,提供更接近传统面向对象语言的写法。 6. 封装与模块化:如何使用闭包和立即执行函数表达式(IIFE)来实现封装,以及AMD、CommonJS和ES6模块系统。 7. 继承模式:浅谈原型...
《JavaScript内核系列》和《JavaScript面向对象基础》这两本书籍是深入理解JavaScript语言核心机制和面向对象编程的重要参考资料。JavaScript作为一种广泛应用于Web开发的脚本语言,其内核和面向对象特性对于开发者...
ES6引入了类的概念,虽然JavaScript的类本质上仍然是函数,但它提供了一种更符合传统面向对象语法的写法。例如: ```javascript class Person { constructor(name) { this.name = name; } sayName() { ...
每个JavaScript对象都有一个`__proto__`属性,指向其构造函数的原型对象。原型对象又有一个`__proto__`属性,形成一个链,这就是原型链。通过原型链,对象可以访问到构造函数原型上的属性和方法。例如: ```...
10. **类与继承**:ES6引入的类是基于原型的语法糖,它提供了更接近传统面向对象编程的写法。类支持继承,通过`extends`关键字,一个类可以继承另一个类的属性和方法。 以上只是JavaScript对象涉及的一部分知识点,...
总的来说,通过点表示法和方括号表示法,我们可以方便地读取和设置JavaScript对象的属性。在实际编程中,根据需求选择合适的方式,是提高代码效率和可维护性的关键。希望本文的分析能帮助你深化对JavaScript面向对象...
4. 类与继承:在ES6中,引入了`class`语法糖,虽然在JavaScript引擎底层仍然是基于原型的实现,但提供了更接近传统面向对象语言的写法。例如,我们可以创建一个`EnemyPlane`类继承自`Plane`,添加敌机特有的行为。 ...
2. **原型**:每个JavaScript对象都有一个内部原型(__proto__),它链接到另一个对象,这个对象可以提供继承的属性和方法。`Person.prototype`可以添加共享的方法: ```javascript Person.prototype.greet = ...
ES6引入了类的语法糖,提供了一种更接近传统面向对象语言的写法。实际上,JavaScript的类仍然是基于原型的,类的实例化过程与使用构造函数和`prototype`相同。 9. **静态方法和属性**: 类可以拥有静态方法和属性...
这份"JavaScript语法手册&JavaScript程序范例"旨在帮助你深入理解JavaScript的核心概念,并通过实例加深对语法的理解。 一、JavaScript基础语法 1. 变量声明:JavaScript使用`var`、`let`和`const`进行变量声明。`...
JavaScript对象的属性分为数据属性和访问器属性两种: 1. 数据属性由以下特性组成:[[Writable]](可写)、[[Value]](值)、[[Enumerable]](可枚举)、[[Configurable]](可配置)。 2. 访问器属性由以下特性组成...
尽管JavaScript没有真正的类,但ES6引入了Class语法,提供了更接近传统面向对象语言的写法,其实质仍然是基于原型的: ```javascript class Person { constructor(name, age) { this.name = name; this.age = ...
ES6引入了类语法,使得JavaScript的面向对象编程更加接近传统类语言。类实际上是一个特殊的函数,但它们的写法更加符合面向对象的思维。类有构造函数(`constructor`)、方法等。例如: ```javascript class ...
JavaScript 2015语法,也被称为ECMAScript 6(ES6),是JavaScript的一个重大更新版本。这一版本在2015年获得正式采纳,引入了一系列新特性和语法改进,以帮助开发者编写更清晰、更模块化、更高效的代码。 ### ...
3. 类(ES6):JavaScript的类语法是基于原型的,但提供了更接近传统面向对象语言的语法糖。 五、异步编程 1. 回调函数:处理异步操作的常见方式,但可能导致回调地狱问题。 2. Promise:ES6引入的Promise对象...
javascript 的基础语法 面向对象的实现 设计模式实现 模块化开 javascript 常见的疑问 jQuery NodeJs html5 Javascript based 1.对象 JavaScript 引用 2.JavaScript this 3.JavaScript 闭包 4.JavaScript 事件 ...
理解原型和构造函数是掌握JavaScript面向对象编程的关键。 数组是另一种重要的数据结构,JavaScript提供了丰富的数组方法,如push、pop、shift、unshift、slice、splice等,用于操作和管理数组元素。此外,ES6引入...
构造函数用于创建具有特定属性和方法的对象,原型链则允许对象共享属性和方法,而ES6引入的类语法则提供了一种更接近传统面向对象语言的写法。 在"完整的项目案例"中,我们可能会看到一个基于当当网的模拟电商系统...
ES6引入的Class语法提供了更面向对象的写法,但实质上仍然是基于原型的。 在学习过程中,了解并实践JavaScript的调试技巧也很重要,如使用浏览器的开发者工具,设置断点、查看变量值、跟踪调用栈等。 总的来说,...