js中有关类、对象的增强函数
javascript中继承的实现
基础实现
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());
寄生组合继承
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();
线上链接。线上链接主要讲最后一个例子,寄生组合继承,其实这里命名,在我后来都没遇到
还有,如果实例对应的类原型的引用变化了,这个实例指向的原型会变吗?不会的。
最优雅的写法
/* @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,也只会调用一次。继承中构造函数保持在原型链中。这个实现不支持继承静态方法,但以后如果有需求,我会尽量用这个方法。
最繁重的写法
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也可以调用。
brix里的实现
// 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,按正常结果只调用一次,但这里调用了两次,重复了。
magix里实现
magix中View如何实现继承?也是类似brix,但它不包含任何语法糖,new初始化钩子,是
View.extend = function(props, statics, ctor) {
第三个参数ctor是初始化钩子,这里会多次调用
总结
为了实现类似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
属性,指向父类的原型对象。
mix
// 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]
}
}
}
}
extend在库里实现
underscore
将后面对象的自有可枚举属性,拷贝到第一个对象,并返回第一个对象
// 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;
};
jquery
支持深度拷贝,深拷贝除了支持对象还包括数组,改变第一个对象,并返回第一个对象
// 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;
};
kissy1.3中有关对象的方法
mix
改变并返回第一个参数,下面实现方法中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;
}
}
}
merge
合并所有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
只对原型对象进行复制,改变并返回第一个参数
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;
},
extend
继承的实现,跟上面封装的继承相比,还是有缺点的,首先新类的构造函数执行时候,父类的构造函数不会自动执行,总觉得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
*******站在巨人的肩膀上