JavaScript 继承详解(五)


Posted in Javascript onOctober 11, 2016

在本章中,我们将分析John Resig关于JavaScript继承的一个实现 - Simple JavaScript Inheritance。 
John Resig作为jQuery的创始人而声名在外。是《Pro JavaScript Techniques》的作者,而且Resig将会在今年秋天推出一本书《JavaScript Secrets》,非常期待。

调用方式
调用方式非常优雅:
注意:代码中的Class、extend、_super都是自定义的对象,我们会在后面的代码分析中详解。

var Person = Class.extend({
  // init是构造函数
  init: function(name) {
this.name = name;
  },
  getName: function() {
return this.name;
  }
});
// Employee类从Person类继承
var Employee = Person.extend({
  // init是构造函数
  init: function(name, employeeID) {
// 在构造函数中调用父类的构造函数
this._super(name);
this.employeeID = employeeID;
  },
  getEmployeeID: function() {
return this.employeeID;
  },
  getName: function() {
// 调用父类的方法
return "Employee name: " + this._super();
  }
});

var zhang = new Employee("ZhangSan", "1234");
console.log(zhang.getName());  // "Employee name: ZhangSan"

说实话,对于完成本系列文章的目标-继承-而言,真找不到什么缺点。方法一如jQuery一样简洁明了。

代码分析
为了一个漂亮的调用方式,内部实现的确复杂了很多,不过这些也是值得的 - 一个人的思考带给了无数程序员快乐的微笑 - 嘿嘿,有点肉麻。
不过其中的一段代码的确迷惑我一段时间:

fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

我曾在几天前的博客中写过一篇文章专门阐述这个问题,有兴趣可以向前翻一翻。

// 自执行的匿名函数创建一个上下文,避免引入全局变量
(function() {
  // initializing变量用来标示当前是否处于类的创建阶段,
  // - 在类的创建阶段是不能调用原型方法init的
  // - 我们曾在本系列的第三篇文章中详细阐述了这个问题
  // fnTest是一个正则表达式,可能的取值为(/\b_super\b/ 或 /.*/)
  // - 对 /xyz/.test(function() { xyz; }) 的测试是为了检测浏览器是否支持test参数为函数的情况
  // - 不过我对IE7.0,Chrome2.0,FF3.5进行了测试,此测试都返回true。
  // - 所以我想这样对fnTest赋值大部分情况下也是对的:fnTest = /\b_super\b/;
  var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
  // 基类构造函数
  // 这里的this是window,所以这整段代码就向外界开辟了一扇窗户 - window.Class
  this.Class = function() { };
  // 继承方法定义
  Class.extend = function(prop) {
// 这个地方很是迷惑人,还记得我在本系列的第二篇文章中提到的么
// - this具体指向什么不是定义时能决定的,而是要看此函数是怎么被调用的
// - 我们已经知道extend肯定是作为方法调用的,而不是作为构造函数
// - 所以这里this指向的不是Object,而是Function(即是Class),那么this.prototype就是父类的原型对象
// - 注意:_super指向父类的原型对象,我们会在后面的代码中多次碰见这个变量
var _super = this.prototype;
// 通过将子类的原型指向父类的一个实例对象来完成继承
// - 注意:this是基类构造函数(即是Class)
initializing = true;
var prototype = new this();
initializing = false;
// 我觉得这段代码是经过作者优化过的,所以读起来非常生硬,我会在后面详解
for (var name in prop) {
  prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn) {
  return function() {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
  };
})(name, prop[name]) :
prop[name];
}
// 这个地方可以看出,Resig很会伪装哦
// - 使用一个同名的局部变量来覆盖全局变量,很是迷惑人
// - 如果你觉得拗口的话,完全可以使用另外一个名字,比如function F()来代替function Class()
// - 注意:这里的Class不是在最外层定义的那个基类构造函数
function Class() {
  // 在类的实例化时,调用原型方法init
  if (!initializing && this.init)
this.init.apply(this, arguments);
}
// 子类的prototype指向父类的实例(完成继承的关键)
Class.prototype = prototype;
// 修正constructor指向错误
Class.constructor = Class;
// 子类自动获取extend方法,arguments.callee指向当前正在执行的函数
Class.extend = arguments.callee;
return Class;
  };
})();

下面我会对其中的for-in循环进行解读,把自执行的匿名方法用一个局部函数来替换, 这样有利于我们看清真相:

(function() {
      var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
      this.Class = function() { };
      Class.extend = function(prop) {
        var _super = this.prototype;
        initializing = true;
        var prototype = new this();
        initializing = false;

        // 如果父类和子类有同名方法,并且子类中此方法(name)通过_super调用了父类方法
        // - 则重新定义此方法
        function fn(name, fn) {
          return function() {
            // 将实例方法_super保护起来。
            // 个人觉得这个地方没有必要,因为每次调用这样的函数时都会对this._super重新定义。
            var tmp = this._super;
            // 在执行子类的实例方法name时,添加另外一个实例方法_super,此方法指向父类的同名方法
            this._super = _super[name];
            // 执行子类的方法name,注意在方法体内this._super可以调用父类的同名方法
            var ret = fn.apply(this, arguments);
            this._super = tmp;
            
            // 返回执行结果
            return ret;
          };
        }
        // 拷贝prop中的所有属性到子类原型中
        for (var name in prop) {
          // 如果prop和父类中存在同名的函数,并且此函数中使用了_super方法,则对此方法进行特殊处理 - fn
          // 否则将此方法prop[name]直接赋值给子类的原型
          if (typeof prop[name] === "function" &&
              typeof _super[name] === "function" && fnTest.test(prop[name])) {
            prototype[name] = fn(name, prop[name]);
          } else {
            prototype[name] = prop[name];
          }
        }

        function Class() {
          if (!initializing && this.init) {
            this.init.apply(this, arguments);
          }
        }
        Class.prototype = prototype;
        Class.constructor = Class;
        Class.extend = arguments.callee;
        return Class;
      };
    })();

写到这里,大家是否觉得Resig的实现和我们在第三章一步一步实现的jClass很类似。 其实在写这一系列的文章之前,我已经对prototype、mootools、extjs、 jQuery-Simple-Inheritance、Crockford-Classical-Inheritance这些实现有一定的了解,并且大部分都在实际项目中使用过。 在第三章中实现jClass也参考了Resig的实现,在此向Resig表示感谢。
下来我们就把jClass改造成和这里的Class具有相同的行为。

我们的实现
将我们在第三章实现的jClass改造成目前John Resig所写的形式相当简单,只需要修改其中的两三行就行了:

(function() {
      // 当前是否处于创建类的阶段
      var initializing = false;
      jClass = function() { };
      jClass.extend = function(prop) {
        // 如果调用当前函数的对象(这里是函数)不是Class,则是父类
        var baseClass = null;
        if (this !== jClass) {
          baseClass = this;
        }
        // 本次调用所创建的类(构造函数)
        function F() {
          // 如果当前处于实例化类的阶段,则调用init原型函数
          if (!initializing) {
            // 如果父类存在,则实例对象的baseprototype指向父类的原型
            // 这就提供了在实例对象中调用父类方法的途径
            if (baseClass) {
              this._superprototype = baseClass.prototype;
            }
            this.init.apply(this, arguments);
          }
        }
        // 如果此类需要从其它类扩展
        if (baseClass) {
          initializing = true;
          F.prototype = new baseClass();
          F.prototype.constructor = F;
          initializing = false;
        }
        // 新创建的类自动附加extend函数
        F.extend = arguments.callee;

        // 覆盖父类的同名函数
        for (var name in prop) {
          if (prop.hasOwnProperty(name)) {
            // 如果此类继承自父类baseClass并且父类原型中存在同名函数name
            if (baseClass &&
            typeof (prop[name]) === "function" &&
            typeof (F.prototype[name]) === "function" &&
            /\b_super\b/.test(prop[name])) {
              // 重定义函数name - 
              // 首先在函数上下文设置this._super指向父类原型中的同名函数
              // 然后调用函数prop[name],返回函数结果
              // 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,
              // 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。
              // 这是JavaScript框架开发中常用的技巧。
              F.prototype[name] = (function(name, fn) {
                return function() {
                  this._super = baseClass.prototype[name];
                  return fn.apply(this, arguments);
                };
              })(name, prop[name]);
            } else {
              F.prototype[name] = prop[name];
            }
          }
        }
        return F;
      };
    })();
    // 经过改造的jClass
    var Person = jClass.extend({
      init: function(name) {
        this.name = name;
      },
      getName: function(prefix) {
        return prefix + this.name;
      }
    });
    var Employee = Person.extend({
      init: function(name, employeeID) {
        // 调用父类的方法
        this._super(name);
        this.employeeID = employeeID;
      },
      getEmployeeIDName: function() {
        // 注意:我们还可以通过这种方式调用父类中的其他函数
        var name = this._superprototype.getName.call(this, "Employee name: ");
        return name + ", Employee ID: " + this.employeeID;
      },
      getName: function() {
        // 调用父类的方法
        return this._super("Employee name: ");
      }
    });

    var zhang = new Employee("ZhangSan", "1234");
    console.log(zhang.getName());  // "Employee name: ZhangSan"
    console.log(zhang.getEmployeeIDName()); // "Employee name: ZhangSan, Employee ID: 1234"

这篇文章就接受到这了,下面还有一个系列的文章,大家都可以看下

Javascript 相关文章推荐
浅谈javascript中createElement事件
Dec 05 Javascript
js简单抽奖代码
Jan 16 Javascript
jquery ajax 如何向jsp提交表单数据
Aug 23 Javascript
jquery的ajax提交form表单的两种方法小结(推荐)
May 25 Javascript
BootStrapTable 单选及取值的实现方法
Jan 10 Javascript
JavaScript实现的select点菜功能示例
Jan 16 Javascript
js获取一组日期中最近连续的天数
May 25 Javascript
vue2.0在table中实现全选和反选的示例代码
Nov 04 Javascript
angularJS实现动态添加,删除div方法
Feb 27 Javascript
一百行JS代码实现一个校验工具
Apr 30 Javascript
React 实现车牌键盘的示例代码
Dec 20 Javascript
ES6 Object.assign()的用法及其使用
Jan 18 Javascript
Javascript动画效果(4)
Oct 11 #Javascript
JavaScript中const、var和let区别浅析
Oct 11 #Javascript
对javascript继承的理解
Oct 11 #Javascript
Javascript动画效果(3)
Oct 11 #Javascript
JavaScript实现自动切换图片代码
Oct 11 #Javascript
Javascript动画效果(2)
Oct 11 #Javascript
Javascript动画效果(1)
Oct 11 #Javascript
You might like
PHP下利用shell后台运行PHP脚本,并获取该脚本的Process ID的代码
2011/09/19 PHP
php smtp实现发送邮件功能
2017/06/22 PHP
form.submit()不能提交表单的原因分析
2014/10/23 Javascript
javascript实现验证IP地址等相关信息代码
2015/05/10 Javascript
Jquery检验手机号是否符合规则并根据手机号检测结果将提交按钮设为不同状态
2015/11/26 Javascript
js实现商品抛物线加入购物车特效
2020/11/18 Javascript
DOM操作和jQuery实现选项移动操作的简单实例
2016/06/07 Javascript
移动端刮刮乐的实现方式(js+HTML5)
2017/03/23 Javascript
Bootstrap多级菜单的实现代码
2017/05/23 Javascript
彻底解决 webpack 打包文件体积过大问题
2017/07/07 Javascript
vue router的基本使用和配置教程
2018/11/05 Javascript
js正则表达式简单校验方法
2021/01/03 Javascript
[01:11:10]2014 DOTA2华西杯精英邀请赛 5 24 iG VS VG加赛
2014/05/26 DOTA
[27:39]Ti4 循环赛第二日 LGD vs Fnatic
2014/07/11 DOTA
Python 命令行参数sys.argv
2008/09/06 Python
Tornado Web服务器多进程启动的2个方法
2014/08/04 Python
python继承和抽象类的实现方法
2015/01/14 Python
详解Python中的循环语句的用法
2015/04/09 Python
讲解Python中for循环下的索引变量的作用域
2015/04/15 Python
在Lighttpd服务器中运行Django应用的方法
2015/07/22 Python
Python处理PDF及生成多层PDF实例代码
2017/04/24 Python
用 Python 连接 MySQL 的几种方式详解
2018/04/04 Python
python 进程 进程池 进程间通信实现解析
2019/08/23 Python
Python实现新型冠状病毒传播模型及预测代码实例
2020/02/05 Python
PyCharm 在Windows的有用快捷键详解
2020/04/07 Python
Python Tricks 使用 pywinrm 远程控制 Windows 主机的方法
2020/07/21 Python
详解Python的爬虫框架 Scrapy
2020/08/03 Python
BeautifulSoup获取指定class样式的div的实现
2020/12/07 Python
html5默认气泡修改的代码详解
2020/03/13 HTML / CSS
小天鹅官方商城:LittleSwan
2017/06/16 全球购物
音乐教学案例
2014/01/30 职场文书
2014年度安全生产目标管理责任书
2014/07/25 职场文书
2014年仓库管理员工作总结
2014/11/18 职场文书
初中运动会闭幕词范本3篇
2019/12/09 职场文书
Python采集壁纸并实现炫轮播
2022/04/30 Python
Go 内联优化让程序员爱不释手
2022/06/21 Golang