国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

JavaScript 繼承詳解(六)

2019-11-20 08:46:39
字體:
來源:轉載
供稿:網友

在本章中,我們將分析Prototypejs中關于JavaScript繼承的實現。
Prototypejs是最早的JavaScript類庫,可以說是JavaScript類庫的鼻祖。 我在幾年前接觸的第一個JavaScript類庫就是這位,因此Prototypejs有著廣泛的群眾基礎。

不過當年Prototypejs中的關于繼承的實現相當的簡單,源代碼就寥寥幾行,我們來看下。

早期Prototypejs中繼承的實現
源碼:

var Class = {      // Class.create僅僅返回另外一個函數,此函數執行時將調用原型方法initialize      create: function() {        return function() {          this.initialize.apply(this, arguments);        }      }    };        // 對象的擴展    Object.extend = function(destination, source) {      for (var property in source) {        destination[property] = source[property];      }      return destination;    };

調用方式:

var Person = Class.create();    Person.prototype = {      initialize: function(name) {        this.name = name;      },      getName: function(prefix) {        return prefix + this.name;      }    };    var Employee = Class.create();    Employee.prototype = Object.extend(new Person(), {      initialize: function(name, employeeID) {        this.name = name;        this.employeeID = employeeID;      },      getName: function() {        return "Employee name: " + this.name;      }    });    var zhang = new Employee("ZhangSan", "1234");    console.log(zhang.getName());  // "Employee name: ZhangSan"

很原始的感覺對吧,在子類函數中沒有提供調用父類函數的途徑。

Prototypejs 1.6以后的繼承實現
首先來看下調用方式:

// 通過Class.create創建一個新類    var Person = Class.create({      // initialize是構造函數      initialize: function(name) {        this.name = name;      },      getName: function(prefix) {        return prefix + this.name;      }    });        // Class.create的第一個參數是要繼承的父類    var Employee = Class.create(Person, {      // 通過將子類函數的第一個參數設為$super來引用父類的同名函數      // 比較有創意,不過內部實現應該比較復雜,至少要用一個閉包來設置$super的上下文this指向當前對象      initialize: function($super, name, employeeID) {        $super(name);        this.employeeID = employeeID;      },      getName: function($super) {        return $super("Employee name: ");      }    });    var zhang = new Employee("ZhangSan", "1234");    console.log(zhang.getName());  // "Employee name: ZhangSan"

這里我們將Prototypejs 1.6.0.3中繼承實現單獨取出來, 那些不想引用整個prototype庫而只想使用prototype式繼承的朋友, 可以直接把下面代碼拷貝出來保存為JS文件就行了。

var Prototype = {      emptyFunction: function() { }    };    var Class = {      create: function() {        var parent = null, 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 = [];        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++)          klass.addMethods(properties[i]);        if (!klass.prototype.initialize)          klass.prototype.initialize = Prototype.emptyFunction;        klass.prototype.constructor = klass;        return klass;      }    };    Class.Methods = {      addMethods: function(source) {        var ancestor = this.superclass && this.superclass.prototype;        var properties = Object.keys(source);        if (!Object.keys({ toString: true }).length)          properties.push("toString", "valueOf");        for (var i = 0, length = properties.length; i < length; i++) {          var property = properties[i], value = source[property];          if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") {            var method = value;            value = (function(m) {              return function() { return ancestor[m].apply(this, arguments) };            })(property).wrap(method);            value.valueOf = method.valueOf.bind(method);            value.toString = method.toString.bind(method);          }          this.prototype[property] = value;        }        return this;      }    };    Object.extend = function(destination, source) {      for (var property in source)        destination[property] = source[property];      return destination;    };    function $A(iterable) {      if (!iterable) return [];      if (iterable.toArray) return iterable.toArray();      var length = iterable.length || 0, results = new Array(length);      while (length--) results[length] = iterable[length];      return results;    }    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";      },      isUndefined: function(object) {        return typeof object == "undefined";      }    });    Object.extend(Function.prototype, {      argumentNames: function() {        var names = this.toString().match(/^[/s/(]*function[^(]*/(([^/)]*)/)/)[1].replace(//s+/g, '').split(',');        return names.length == 1 && !names[0] ? [] : names;      },      bind: function() {        if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;        var __method = this, args = $A(arguments), object = args.shift();        return function() {          return __method.apply(object, args.concat($A(arguments)));        }      },      wrap: function(wrapper) {        var __method = this;        return function() {          return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));        }      }    });    Object.extend(Array.prototype, {      first: function() {        return this[0];      }    });

首先,我們需要先解釋下Prototypejs中一些方法的定義。

argumentNames: 獲取函數的參數數組

function init($super, name, employeeID) {        console.log(init.argumentNames().join(",")); // "$super,name,employeeID"        }

bind: 綁定函數的上下文this到一個新的對象(一般是函數的第一個參數)

var name = "window";        var p = {          name: "Lisi",          getName: function() {            return this.name;          }        };        console.log(p.getName());  // "Lisi"        console.log(p.getName.bind(window)()); // "window"

wrap: 把當前調用函數作為包裹器wrapper函數的第一個參數

var name = "window";        var p = {          name: "Lisi",          getName: function() {            return this.name;          }        };        function wrapper(originalFn) {          return "Hello: " + originalFn();        }        console.log(p.getName());  // "Lisi"        console.log(p.getName.bind(window)()); // "window"        console.log(p.getName.wrap(wrapper)()); // "Hello: window"        console.log(p.getName.wrap(wrapper).bind(p)()); // "Hello: Lisi"

有一點繞口,對吧。這里要注意的是wrap和bind調用返回的都是函數,把握住這個原則,就很容易看清本質了。

對這些函數有了一定的認識之后,我們再來解析Prototypejs繼承的核心內容。
這里有兩個重要的定義,一個是Class.extend,另一個是Class.Methods.addMethods。

var Class = {      create: function() {        // 如果第一個參數是函數,則作為父類        var parent = null, properties = $A(arguments);        if (Object.isFunction(properties[0]))          parent = properties.shift();        // 子類構造函數的定義        function klass() {          this.initialize.apply(this, arguments);        }                // 為子類添加原型方法Class.Methods.addMethods        Object.extend(klass, Class.Methods);        // 不僅為當前類保存父類的引用,同時記錄了所有子類的引用        klass.superclass = parent;        klass.subclasses = [];        if (parent) {          // 核心代碼 - 如果父類存在,則實現原型的繼承          // 這里為創建類時不調用父類的構造函數提供了一種新的途徑          // - 使用一個中間過渡類,這和我們以前使用全局initializing變量達到相同的目的,          // - 但是代碼更優雅一點。          var subclass = function() { };          subclass.prototype = parent.prototype;          klass.prototype = new subclass;          parent.subclasses.push(klass);        }        // 核心代碼 - 如果子類擁有父類相同的方法,則特殊處理,將會在后面詳解        for (var i = 0; i < properties.length; i++)          klass.addMethods(properties[i]);        if (!klass.prototype.initialize)          klass.prototype.initialize = Prototype.emptyFunction;                // 修正constructor指向錯誤        klass.prototype.constructor = klass;        return klass;      }    };

再來看addMethods做了哪些事情:

Class.Methods = {      addMethods: function(source) {        // 如果父類存在,ancestor指向父類的原型對象        var ancestor = this.superclass && this.superclass.prototype;        var properties = Object.keys(source);        // Firefox和Chrome返回1,IE8返回0,所以這個地方特殊處理        if (!Object.keys({ toString: true }).length)          properties.push("toString", "valueOf");        // 循環子類原型定義的所有屬性,對于那些和父類重名的函數要重新定義        for (var i = 0, length = properties.length; i < length; i++) {          // property為屬性名,value為屬性體(可能是函數,也可能是對象)          var property = properties[i], value = source[property];          // 如果父類存在,并且當前當前屬性是函數,并且此函數的第一個參數為 $super          if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") {            var method = value;            // 下面三行代碼是精華之所在,大概的意思:            // - 首先創建一個自執行的匿名函數返回另一個函數,此函數用于執行父類的同名函數            // - (因為這是在循環中,我們曾多次指出循環中的函數引用局部變量的問題)            // - 其次把這個自執行的匿名函數的作為method的第一個參數(也就是對應于形參$super)            // 不過,竊以為這個地方作者有點走火入魔,完全沒必要這么復雜,后面我會詳細分析這段代碼。            value = (function(m) {              return function() { return ancestor[m].apply(this, arguments) };            })(property).wrap(method);            value.valueOf = method.valueOf.bind(method);            // 因為我們改變了函數體,所以重新定義函數的toString方法            // 這樣用戶調用函數的toString方法時,返回的是原始的函數定義體            value.toString = method.toString.bind(method);          }          this.prototype[property] = value;        }        return this;      }    };

上面的代碼中我曾有“走火入魔”的說法,并不是對作者的褻瀆, 只是覺得作者對JavaScript中的一個重要準則(通過自執行的匿名函數創建作用域) 運用的有點過頭。

value = (function(m) {      return function() { return ancestor[m].apply(this, arguments) };    })(property).wrap(method);

其實這段代碼和下面的效果一樣:

value = ancestor[property].wrap(method);

我們把wrap函數展開就能看的更清楚了:

value = (function(fn, wrapper) {      var __method = fn;      return function() {        return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));      }    })(ancestor[property], method);

可以看到,我們其實為父類的函數ancestor[property]通過自執行的匿名函數創建了作用域。 而原作者是為property創建的作用域。兩則的最終效果是一致的。

我們對Prototypejs繼承的重實現
分析了這么多,其實也不是很難,就那么多概念,大不了換種表現形式。
下面我們就用前幾章我們自己實現的jClass來實現Prototypejs形式的繼承。

// 注意:這是我們自己實現的類似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 jClass(baseClass, prop) {      // 只接受一個參數的情況 - jClass(prop)      if (typeof (baseClass) === "object") {        prop = baseClass;        baseClass = null;      }      // 本次調用所創建的類(構造函數)      function F() {        // 如果父類存在,則實例對象的baseprototype指向父類的原型        // 這就提供了在實例對象中調用父類方法的途徑        if (baseClass) {          this.baseprototype = baseClass.prototype;        }        this.initialize.apply(this, arguments);      }      // 如果此類需要從其它類擴展      if (baseClass) {        var middleClass = function() {};        middleClass.prototype = baseClass.prototype;        F.prototype = new middleClass();        F.prototype.constructor = F;      }      // 覆蓋父類的同名函數      for (var name in prop) {        if (prop.hasOwnProperty(name)) {          // 如果此類繼承自父類baseClass并且父類原型中存在同名函數name          if (baseClass &&            typeof (prop[name]) === "function" &&            argumentNames(prop[name])[0] === "$super") {            // 重定義子類的原型方法prop[name]            // - 這里面有很多JavaScript方面的技巧,如果閱讀有困難的話,可以參閱我前面關于JavaScript Tips and Tricks的系列文章            // - 比如$super封裝了父類方法的調用,但是調用時的上下文指針要指向當前子類的實例對象            // - 將$super作為方法調用的第一個參數            F.prototype[name] = (function(name, fn) {              return function() {                var that = this;                $super = function() {                  return baseClass.prototype[name].apply(that, arguments);                };                return fn.apply(this, Array.prototype.concat.apply($super, arguments));              };            })(name, prop[name]);                      } else {            F.prototype[name] = prop[name];          }        }      }      return F;    };

調用方式和Prototypejs的調用方式保持一致:

 var Person = jClass({      initialize: function(name) {        this.name = name;      },      getName: function() {        return this.name;      }    });    var Employee = jClass(Person, {      initialize: function($super, name, employeeID) {        $super(name);        this.employeeID = employeeID;      },      getEmployeeID: function() {        return this.employeeID;      },      getName: function($super) {        return "Employee name: " + $super();      }    });    var zhang = new Employee("ZhangSan", "1234");    console.log(zhang.getName());  // "Employee name: ZhangSan"

經過本章的學習,就更加堅定了我們的信心,像Prototypejs形式的繼承我們也能夠輕松搞定。
以后的幾個章節,我們會逐步分析mootools,Extjs等JavaScript類庫中繼承的實現,敬請期待。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 屏山县| 南溪县| 海城市| 南投市| 武安市| 余干县| 枣强县| 长春市| 博客| 合阳县| 麻阳| 上思县| 江西省| 定边县| 吉安县| 锦州市| 临沭县| 丹江口市| 大田县| 新营市| 东辽县| 桦甸市| 胶南市| 萨嘎县| 安国市| 诸暨市| 永吉县| 同德县| 陕西省| 玉屏| 四平市| 华坪县| 铅山县| 红桥区| 台南市| 广丰县| 佛教| 广灵县| 渝北区| 林甸县| 玉环县|