function Range(from,to){ this.from =from; this.to =to; } Range.prototype = { includes:function(x){ return this.from<=x &&this.to >=x; }, foreach:function(f){ for(var x = Math.ceil(this.from);x<=this.to;x++){ f(x); } }, toString:function(){ return "("+this.from+"..."+this.to+")"; } } var r= new Range(1,3); console.log(r.includes(2)); // 能夠借鑑下面這種用法 r.foreach(alert); // 彈出三次 console.log(r.toString());
線上連接javascript
function SuperType() { this.property = true; } SuperType.prototype.superMethod = function () { } function LowType() { SuperType.call(this); this.lowProperty = true; } function inheritPrototype(subType, superType) { var prototype1 = object(superType.prototype); prototype1.constructor = subType; subType.prototype = prototype1; } inheritPrototype(LowType, SuperType); LowType.prototype.lowMethod = function () { }; var lowType = new LowType();
線上連接。線上連接主要講最後一個例子,寄生組合繼承,其實這裏命名,在我後來都沒遇到
還有,若是實例對應的類原型的引用變化了,這個實例指向的原型會變嗎?不會的。html
/* @article https://johnresig.com/blog/simple-javascript-inheritance/ * Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed. */ // Inspired by base2 and Prototype (function () { var initializing = false, fnTest = /xyz/.test(function () { xyz; }) ? /\b_super\b/ : /.*/; // The base Class implementation (does nothing) this.Class = function () { }; // Create a new Class that inherits from this class Class.extend = function (prop) { var _super = this.prototype; // Instantiate a base class (but only create the instance, // don't run the init constructor) initializing = true; var prototype = new this(); initializing = false; // Copy the properties over onto the new prototype for (var name in prop) { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function (name, fn) { return function () { var tmp = this._super; // Add a new ._super() method that is the same method // but on the super-class this._super = _super[name]; // The method only need to be bound temporarily, so we // remove it when we're done executing var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } // The dummy class constructor function Class() { // All construction is actually done in the init method if (!initializing && this.init) this.init.apply(this, arguments); } // Populate our constructed prototype object Class.prototype = prototype; // Enforce the constructor to be what we expect Class.prototype.constructor = Class; // And make this class extendable Class.extend = arguments.callee; return Class; }; })();
線上連接。這個繼承是大牛寫的,是我見過最簡潔寫法了,可是在工做中,一直沒看到有人在子類方法內,要用到父類方法,直接使用this._super
方法,還有init方法construction,也只會調用一次。繼承中構造函數保持在原型鏈中。這個實現不支持繼承靜態方法,但之後若是有需求,我會盡可能用這個方法。java
var Class = (function () { // Class // ----------------- // Thanks to: // - http://mootools.net/docs/core/Class/Class // - http://ejohn.org/blog/simple-javascript-inheritance/ // - https://github.com/ded/klass // - http://documentcloud.github.com/backbone/#Model-extend // - https://github.com/joyent/node/blob/master/lib/util.js // - https://github.com/kissyteam/kissy/blob/master/src/seed/src/kissy.js // The base Class implementation. function Class(o) { // Convert existed function to Class. if (!(this instanceof Class) && isFunction(o)) { return classify(o) } } // Create a new Class. // // var SuperPig = Class.create({ // Extends: Animal, // Implements: Flyable, // initialize: function() { // SuperPig.superclass.initialize.apply(this, arguments) // }, // Statics: { // COLOR: 'red' // } // }) // Class.create = function (parent, properties) { if (!isFunction(parent)) { properties = parent parent = null } properties || (properties = {}) parent || (parent = properties.Extends || Class) properties.Extends = parent // The created class constructor function SubClass() { // Call the parent constructor. parent.apply(this, arguments) // Only call initialize in self constructor. if (this.constructor === SubClass && this.initialize) { this.initialize.apply(this, arguments) } } // Inherit class (static) properties from parent. if (parent !== Class) { mix(SubClass, parent, parent.StaticsWhiteList) } // Add instance properties to the subclass. implement.call(SubClass, properties) // Make subclass extendable. return classify(SubClass) } function implement(properties) { var key, value for (key in properties) { value = properties[key] if (Class.Mutators.hasOwnProperty(key)) { Class.Mutators[key].call(this, value) } else { this.prototype[key] = value } } } // Create a sub Class based on `Class`. Class.extend = function (properties) { properties || (properties = {}) properties.Extends = this return Class.create(properties) } function classify(cls) { cls.extend = Class.extend cls.implement = implement return cls } // Mutators define special properties. Class.Mutators = { 'Extends': function (parent) { var existed = this.prototype var proto = createProto(parent.prototype) // Keep existed properties. mix(proto, existed) // Enforce the constructor to be what we expect. proto.constructor = this // Set the prototype chain to inherit from `parent`. this.prototype = proto // Set a convenience property in case the parent's prototype is // needed later. this.superclass = parent.prototype }, 'Implements': function (items) { isArray(items) || (items = [items]) var proto = this.prototype, item while (item = items.shift()) { mix(proto, item.prototype || item) } }, 'Statics': function (staticProperties) { mix(this, staticProperties) } } // Shared empty constructor function to aid in prototype-chain creation. function Ctor() { } // See: http://jsperf.com/object-create-vs-new-ctor var createProto = Object.__proto__ ? function (proto) { return {__proto__: proto} } : function (proto) { Ctor.prototype = proto return new Ctor() } // Helpers // ------------ function mix(r, s, wl) { // Copy "all" properties including inherited ones. for (var p in s) { if (s.hasOwnProperty(p)) { if (wl && indexOf(wl, p) === -1) continue // 在 iPhone 1 代等設備的 Safari 中,prototype 也會被枚舉出來,需排除 if (p !== 'prototype') { r[p] = s[p] } } } } var toString = Object.prototype.toString var isArray = Array.isArray || function (val) { return toString.call(val) === '[object Array]' } var isFunction = function (val) { return toString.call(val) === '[object Function]' } var indexOf = Array.prototype.indexOf ? function (arr, item) { return arr.indexOf(item) } : function (arr, item) { for (var i = 0, len = arr.length; i < len; i++) { if (arr[i] === item) { return i } } return -1 } return Class })()
線上連接。這個繼承是功能最全面的,包括類實現裏,怎麼實現靜態方法,可是它的語法糖太多了,Extends
、Implements
、Statics
、initialize
,繼承中的構造函數提出來,放在當前類的構造函數中執行,new初始化時的鉤子,是initialize,只會調用一次,繼承中initialize也能夠調用。node
// http://g.alicdn.com/thx/brix-release/1.0.0-beta.9/brix-base/dist/base-debug.js var _ = {} var toString = Object.prototype.toString _.each = function (obj, iterator, context) { if (obj === null || obj === undefined) return obj if (obj.forEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, length = obj.length; i < length; i++) { iterator.call(context, obj[i], i, obj) } } else { for (var prop in obj) { iterator.call(context, obj[prop], prop, obj) } } return obj } _.each('Boolean Number String Function Array Date RegExp Object Error'.split(' '), function (name) { _['is' + name] = function (obj) { return toString.call(obj) == '[object ' + name + ']' } }) _.extend = function () { var target = arguments[0] var index = 1 var length = arguments.length var deep = false var options, name, src, copy, clone if (typeof target === "boolean") { deep = target target = arguments[index] || {} index++ } if (typeof target !== "object" && typeof target !== "function") { target = {} } if (length === 1) { target = this index = 0 } for (; index < length; index++) { options = arguments[index] if (!options) continue for (name in options) { src = target[name] copy = options[name] if (target === copy) continue if (copy === undefined) continue if (deep && (_.isArray(copy) || _.isObject(copy))) { if (_.isArray(copy)) clone = src && _.isArray(src) ? src : [] if (_.isObject(copy)) clone = src && _.isObject(src) ? src : {} target[name] = _.extend(deep, clone, copy) } else { target[name] = copy } } } return target } /* This function is loosely inspired by Backbone.js. http://backbonejs.org */ function extend(protoProps, staticProps) { var parent = this // 構造函數 Initialize constructor var constructor = protoProps && protoProps.hasOwnProperty('constructor') ? protoProps.constructor : // 自定義構造函數 Custom constructor parent // 父類構造函數 Base constructor // 子類 Subclass var child = function () { parent.__x_created_with = child.__x_created_with var instance = constructor.apply(this, arguments) || this // instance.options vs parameter options var options = arguments[0] if (options && !instance.hasOwnProperty('options')) { instance.options = _.extend(true, {}, this.options, options) } // 若是模塊帶有 __x_created_with,則一切初始化行爲都交給第三方;不然調用 .create() 方法。 // If the child module has a property named as __x_created_with, the third-library will be response for the rest of initialization actions. // If not, the child module will call the `.create()`. if (!child.__x_created_with && instance.created && instance.constructor === child) { instance.created.apply(instance, instance.created.length ? [instance.options] : []) // @edit } return instance } // 靜態屬性 Static properties _.extend(child, parent, staticProps) // 原型鏈 Build prototype chain. var Surrogate = function () { // this.constructor = constructor // @remove } Surrogate.prototype = parent.prototype child.prototype = new Surrogate() child.prototype.constructor = child // @add // 原型屬性 Copy prototype properties from the parameter protoProps to the prototype of child if (protoProps) _.extend(child.prototype, protoProps) // Add keyword __super__ child.__super__ = parent.prototype child.extend = extend return child } function Brix() { } Brix.prototype = { created: function () { if (this.init) this.init() if (this.render) this.render() }, constructor: Brix // @add } Brix.extend = extend
是Brix Base裏關於繼承的實現,由於爲了支持框架Brix Loader,因此繼承裏作了特殊處理,好比options
參數,好比__x_created_with
,繼承中的構造函數提出來,放在當前類的構造函數中執行,new時初始化鉤子,是created方法調用init、render,因此初始化語句能夠放在init、render,按正常結果只調用一次,但這裏調用了兩次,重複了。jquery
magix中View如何實現繼承?也是相似brix,但它不包含任何語法糖,new初始化鉤子,是git
View.extend = function(props, statics, ctor) {
第三個參數ctor是初始化鉤子,這裏會屢次調用github
爲了實現相似Person.extend
能生成新類,extend
要向封裝傳入原型上方法,父類,靜態方法,每次執行extend時要定義新類的構造函數。返回的新類也能extend再生成子類。
封裝內,將父類的構造函數和原型拆分。新類的構造函數執行父類的構造函數,新類的原型指向Object.create(父類的原型)。構造函數在實例化時才執行,原型鏈實現,在實現繼承時就執行。
新類的構造函數中執行父類的構造函數,父類的構造函數再執行祖先的構造函數...,直到最頂層。最頂層構造函數可自由實現。在simple-inherit中是封裝外面的Class,在brix中是首次直接賦值了extend方法的類。complex-inherit中,是由Class.create傳入的。
實例化類的時候,要自動執行初始化的操做,正常是在構造函數中實現的,extend封裝裏的構造函數是統一寫死的,沒法自由作一些初始化,要在構造函數裏提供個鉤子,構造函數執行這個鉤子函數,鉤子函數,在定義新類的時候傳入,也就是Person.extend
時傳入。
鉤子通常是定義在原型上的某方法,好比initialize
方法,由於構造函數裏是調父類的構造函數,一直到最頂層基類,也要屢次執行構造函數內鉤子嗎?鉤子是這樣執行的this.initialize.apply(this, arguments)
,this在每次執行時都不變的,也不必屢次執行,只要執行一次便可。而且只執行原型鏈中,第一個有鉤子方法的原型,剩下的都不執行了。
實現原型鏈繼承時,要注意原型的constructor屬性,爲了子類方法能調父類方法,給每一個類加個superclass
屬性,指向父類的原型對象。web
// https://g.alicdn.com/mm/pubplus/0.4.12/app_debug/exts/arale/class.js function mix(r, s, wl) { // Copy "all" properties including inherited ones. for (var p in s) { if (s.hasOwnProperty(p)) { if (wl && indexOf(wl, p) === -1) continue // 在 iPhone 1 代等設備的 Safari 中,prototype 也會被枚舉出來,需排除 if (p !== 'prototype') { r[p] = s[p] } } } }
將後面對象的自有可枚舉屬性,拷貝到第一個對象,並返回第一個對象數組
// http://g.alicdn.com/thx/brix-release/1.0.0-beta.9/underscore/underscore.js // Extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { if (!_.isObject(obj)) return obj; var source, prop; for (var i = 1, length = arguments.length; i < length; i++) { source = arguments[i]; for (prop in source) { if (hasOwnProperty.call(source, prop)) { obj[prop] = source[prop]; } } } return obj; };
支持深度拷貝,深拷貝除了支持對象還包括數組,改變第一個對象,並返回第一個對象app
// http://g.alicdn.com/thx/brix-release/1.0.0-beta.9/jquery/dist/jquery.js jQuery.extend = jQuery.fn.extend = function() { var src, copyIsArray, copy, name, options, clone, target = arguments[ 0 ] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // skip the boolean and the target target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( i === length ) { target = this; i--; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( ( options = arguments[ i ] ) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = jQuery.isArray( copy ) ) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray( src ) ? src : []; } else { clone = src && jQuery.isPlainObject( src ) ? src : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; };
改變並返回第一個參數,下面實現方法中MIX_CIRCULAR_DETECTION標記,感受是爲了調試,看deep了幾回
/* * for example: * @example * var t = {}; * S.mix({x: {y: 2, z: 4}}, {x: {y: 3, a: t}}, {deep: TRUE}) => {x: {y: 3, z: 4, a: {}}}, a !== t * S.mix({x: {y: 2, z: 4}}, {x: {y: 3, a: t}}, {deep: TRUE, overwrite: false}) => {x: {y: 2, z: 4, a: {}}}, a !== t * S.mix({x: {y: 2, z: 4}}, {x: {y: 3, a: t}}, 1) => {x: {y: 3, a: t}} */ mix: function (r, s, ov, wl, deep) { if (typeof ov === 'object') { wl = ov['whitelist']; deep = ov['deep']; ov = ov['overwrite']; } var cache = [], c, i = 0; mixInternal(r, s, ov, wl, deep, cache); while (c = cache[i++]) { delete c[MIX_CIRCULAR_DETECTION]; } return r; } function mixInternal(r, s, ov, wl, deep, cache) { if (!s || !r) { return r; } if (ov === undefined) { ov = TRUE; } var i = 0, p, keys, len; // 記錄循環標誌 s[MIX_CIRCULAR_DETECTION] = r; // 記錄被記錄了循環標誌的對像 cache.push(s); if (wl) { len = wl.length; for (i = 0; i < len; i++) { p = wl[i]; if (p in s) { _mix(p, r, s, ov, wl, deep, cache); } } } else { // mix all properties keys = S.keys(s); len = keys.length; for (i = 0; i < len; i++) { p = keys[i]; if (p != MIX_CIRCULAR_DETECTION) { // no hasOwnProperty judge! _mix(p, r, s, ov, wl, deep, cache); } } } return r; } function _mix(p, r, s, ov, wl, deep, cache) { // 要求覆蓋 // 或者目的不存在 // 或者深度mix if (ov || !(p in r) || deep) { var target = r[p], src = s[p]; // prevent never-end loop if (target === src) { // S.mix({},{x:undefined}) if (target === undefined) { r[p] = target; } return; } // 來源是數組和對象,而且要求深度 mix if (deep && src && (S.isArray(src) || S.isPlainObject(src))) { if (src[MIX_CIRCULAR_DETECTION]) { r[p] = src[MIX_CIRCULAR_DETECTION]; } else { // 目標值爲對象或數組,直接 mix // 不然 新建一個和源值類型同樣的空數組/對象,遞歸 mix var clone = target && (S.isArray(target) || S.isPlainObject(target)) ? target : (S.isArray(src) ? [] : {}); r[p] = clone; mixInternal(clone, src, ov, wl, TRUE, cache); } } else if (src !== undefined && (ov || !(p in r))) { r[p] = src; } } }
合併全部obj到返回新
merge: function (var_args) { var_args = S.makeArray(arguments); var o = {}, i, l = var_args.length; for (i = 0; i < l; i++) { S.mix(o, var_args[i]); } return o; },
只對原型對象進行復制,改變並返回第一個參數
augment: function (r, var_args) { var args = S.makeArray(arguments), len = args.length - 2, i = 1, ov = args[len], wl = args[len + 1]; if (!S.isArray(wl)) { ov = wl; wl = undefined; len++; } if (!S.isBoolean(ov)) { ov = undefined; len++; } for (; i < len; i++) { S.mix(r.prototype, args[i].prototype || args[i], ov, wl); } return r; },
繼承的實現,跟上面封裝的繼承相比,仍是有缺點的,首先新類的構造函數執行時候,父類的構造函數不會自動執行,總以爲superclass的指向的原型,中間多了一層
extend: function (r, s, px, sx) { if (!s || !r) { return r; } var sp = s.prototype, rp; // add prototype chain rp = createObject(sp, r); r.prototype = S.mix(rp, r.prototype); r.superclass = createObject(sp, s); // add prototype overrides if (px) { S.mix(rp, px); } // add object overrides if (sx) { S.mix(r, sx); } return r; }, function Empty() { } function createObject(proto, constructor) { var newProto; if (ObjectCreate) { newProto = ObjectCreate(proto); } else { Empty.prototype = proto; newProto = new Empty(); } newProto.constructor = constructor; return newProto; }
更多例子以及在例子中,我對繼承的摸索http://sprying.github.io/webtest/class/index.html