Da'sBlog

js-Class.js学习 源码分析

学习

以下学习需要面向对象基础知识和prototype继承了解

什么是继承

ClassJs提供了完整的继承机制。

为什么要继承

因为父类的功能是不完善的,随着项目的迭代,性能功能上明显不足。

这个时候多个项目栏目可能依赖这个父类,导致你不能修改。

即使修改也会导致项目错乱,一改全部都改。

所以这个时候为了能达到效率和灵活,我们需要用面向对象继承机制。

父类是不完善的,子类通过继承父类的所有,并对不足的予以修订。

所以继承的任务有两个

1. 子类获取父类所有功能    

2. 子类对父类不足的进行修改

继承还有两个特性即单根性和传递性

1. 单根性 (子类只有一个父类)
2. 传递性    (即子类可以作为父类不断传递下去)

教程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//首先我们通过Class.extend创造基类person,
var Person = Class.extend({
init: function(isDancing){
this.dancing = isDancing;
},
dance: function(){
return this.dancing;
}
});
var Ninja = Person.extend({
init: function(){
this._super( false );
},
dance: function(){
// Call the inherited version of dance()
return this._super();
},
swingSword: function(){
return true;
}
});
var p = new Person(true);
p.dance(); // => true
var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true

首先用Class.extend创建基类Person并传入prototype

1
2
3
4
5
6
7
8
9
{
//prototype
init: function(isDancing){
this.dancing = isDancing;
},
dance: function(){
return this.dancing;
}
}

因为js没有纯正的面向对象机制,所谓的继承是由prototype来实现。

当执行完Class.extend并传入prototype会返回一个构造函数给Person。这个函数的构造体是我们的Class的构造函数,prototype是我们传入的值。所以返回的构造函数,打印出来会是这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.dir(Person)
function Class()
arguments:null
caller:null
extend:function(prop)
inject:function(prop)
length:0
name:"Class"
prototype:Class
constructor:function Class()
dance:function()
init:function(isDancing)
__proto__:Object
__proto__:function()
<function scope>

当我们运行

1
2
var p = new Person(true);
p.dance(); // => true

把true传入Person那么初始化函数init自动执行,dance所指向的就变成true。
这个时候用dance打印当前域的dance就变成了true

这个时候我们运行

1
2
3
4
5
6
7
8
9
10
11
12
var Ninja = Person.extend({
init: function(){
this._super( false );
},
dance: function(){
// Call the inherited version of dance()
return this._super();
},
swingSword: function(){
return true;
}
});

用于继承Person这个类

这里的init里面的this._super(false)指向的是父类也就是Person内原型里面的init。
子类init和父类init是重名 ,这个时候子类会把对父类传递值得修改,传给父级。即等于
this._super( false ) == (this.dancing = false; )

dance同理

swingSword因为父类没有,所以它来源于自己,直接添加到prototype。

所以运行后的Ninja的构造函数是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.dir(Person)
function Class()
arguments:null
caller:null
extend:function(prop)
inject:function(prop)
length:0
name:"Class"
prototype:Class
constructor:function Class()
dance:function()
init:function(isDancing)
swingSwordfunction()
__proto__:Class
__proto__function:()
<function scope>

这个时候运行

1
2
3
var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true

发现我们的dance变成false 是因为我们修改了原来的类,同时添加了swingSword扩充了父类。

从而实现了继承的特性。

代码分析

ClassJs用于对象的继承,源代码比较小巧90行左右,非常精简。

  1. fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
    test是为了判断字符串是否匹配。所以大部分浏览器对函数内部的数据如果是函数会执行toString保证运行。
    例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var a = {}
    undefined
    /a/.test(a)
    false
    /a/.test('a')
    true
    /a/.test(function(){a})
    true

    fnTest这里的目的就是为了判断当前浏览器是否支持函数toString,如果支持返回/\b_super\b/,否则返回 /.*/

  2. 首先都在(function(){})()这个自执行里面,防止对全局污染。

  3. 然后用 this.Class = function() {};创建构造函数Class.这里的this.Class的this是指向的window

  4. 然后是在Class类上添加extend方法,该方法用于实现继承,传入一个prop对象,返回Class的原型+prop对象。下面对Class.extend里面代码分析

    1. var _super = this.prototype; 这里 _super就是Class的原型。用于保存原型。
    2. 1
      2
      3
      initializing = true;
      var prototype = new this();
      initializing = false;

      这三段用于初始化,把实例化的Class赋值给prototype

  5. 这是一段for循环用于把prop的属性复制给prototype

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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];
    }

我们分析下这一段
prototype[name] = typeof prop[name] == “function” && typeof _super[name] == “function” && fnTest.test(prop[name]) ? (function(name, fn){})(name, prop[name]):prop[name]

1. typeof prop[name] == "function" && typeof _super[name] == "function" 用于构造函数中的相应name是不是函数,传递过来的prop中的name是不是也是函数。
2. fnTest.test(prop[name]) 同样也是用于判断传来的prop是否有_super。
3. 如果上面两个都是true那么就把
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
var Class = {}
var result = {}
Class.extend =function(prop){
var _super = this.prototype;
for(var name in prop){
result[name] = /\b_super\b/.test(prop[name])?(function(name, fn) {
return function() {
var tmp = this._super; //把super存入tmp缓存防止覆盖
this._super = _super[name]; //把super方法复制给super,this._super已经变成父类name函数
//这里我们要运行fn即prop[name],然后 return this._super指向当前。然后把return this._super();返回值赋值给result即ret
// dance: function(){
//
// return this._super();
// },
// 例如父级是
// init: function(isDancing){
// this.dancing = isDancing;
// },
// 子是
// init: function(){
// this._super( false );
// },
// 子实例化的时候会传入false,然后返回ret。。。。简单点就是如果是同名的那就继承。
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]):prop[name] //如果函数体里面有super那么就继承来至父级别。否则直接进行赋值
}
return
}
var Person = Class.extend({
init: function(isDancing){
this.dancing = isDancing;
},
dance: function(){
return this.dancing;
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
(function() {
var 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;
// EXTENSION BY Draw2D.org to inject methods into an existing class to provide plugins or
// bugfixes for further releases
//
Class.inject = function(prop) {
var proto = this.prototype;
var parent = {};
for (var name in prop) {
if (typeof(prop[name]) == "function" && typeof(proto[name]) == "function" && fnTest.test(prop[name])) {
parent[name] = proto[name];
proto[name] = (function(name, fn) {
return function() {
var tmp = this.parent;
this.parent = parent[name];
var ret = fn.apply(this, arguments);
this.parent = tmp;
return ret;
};
})(name, prop[name]);
} else {
proto[name] = prop[name];
}
}
};
return Class;
};
})();
坚持原创技术分享,您的支持将鼓励我继续创作!