基本结构

jQuery的基本结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(function (window, undefiend) {
var jQuery = function () {
return new jQuery.fn.init()
}
jQuery.prototype = {
constructor: jQuery
}

jQuery.fn.init.prototype = jQuery.prototype
window.jQuery = window.$ = jQuery
})(window);
/* 其中fn指代的是原型,因此以上结构可以转换成以下结构 */
(function (window, undefiend) {
var jQuery = function () {
return new kjQuery.prototype.init();
};
jQuery.prototype = {
constructor: jQuery,
};

jQuery.prototype.init.prototype = jQuery.prototype;

window.jQuery = window.$ = jQuery;
})(window);
  1. jQuery的本质是一个闭包

    为了避免多个框架的冲突

  2. jQuery如何让外界访问内部定义的局部变量

    1
    windows.xx = xxx
  3. jQuery为什么要给自己传递一个window参数

    • 为了方便后期压缩代码
    • 为了提升查找的效率
  4. jQuery为什么要给自己传递一个undefiend参数

    • 为了方便后期压缩代码
    • IE9以下的浏览器undefined可以被修改,为了保证内部使用的undefined不被修改,所以需要接收一个正确的undefined

入口函数测试

  1. 传入 '' null undefined NaN 0 false

    返回空的jQuery对象

    1
    2
    3
    4
    5
    6
    7
    console.log($())
    console.log($(''))
    console.log($(null))
    console.log($(undefined))
    console.log($(NaN))
    console.log($(0))
    console.log($(false))

    image-20200621153915653

  2. 字符串

    • 代码片段

      会将创建好的DOM元素存储到jQuery对象中返回

      1
      console.log($('<p>1</p><p>2</p><p>3</p>'));

      image-20200621155130041

    • 选择器

      会将找到的所有元素存储到jQuery对象中返回

      1
      console.log($('li'));

      image-20200621155220061

  3. 数组

    会将数组中存储的元素依次存储到jQuery对象中立返回

    1
    2
    3
    4
    var arr = [1, 2, 3, 4, 5, 6];
    console.log($(arr));
    var likeArr = { 0: "lnj", 1: "33", 2: "male", length: 3 };
    console.log($(likeArr));

    image-20200621155259905

  4. 其他类型(对象、DOM元素、基本数据类型等)

    会将传入的数据存储到jQuery对象中返回

    1
    2
    3
    4
    5
    6
    7
    8
    function Person() { }

    console.log($(new Person()));

    console.log($(document.createElement('div')));

    console.log($(123));
    console.log($(true));

    image-20200621155438227

入口函数-代码实现

接收参数的实现:在创建时接收一个参数,并且传递给init即可。这样init就可以接收这个参数。

1
2
3
var kjQuery = function (selector) {
return new kjQuery.prototype.init(selector);
};

接下来的方法只需要在init中定义即可。

1
2
3
4
5
6
kjQuery.prototype = {
constructor: kjQuery,
init: function (selector) {

},
};
  1. 传入 '' null undefined NaN 0 false

    1
    2
    3
    if (!selector) {
    return this;
    }
  2. 处理函数

    判断是否为函数,如果是则将传入的参数作为ready的回调函数传入。

    1
    2
    3
    isFunction: function (selector) {
    return typeof selector === "function";
    },
    1
    2
    3
    4
    // 2. 方法处理
    else if (kjQuery.isFunction(selector)) {
    kjQuery.ready(selector);
    }

    ready函数中需要判断页面是否加载,由于IE不支持addEvem=ntListener方法添加事件,因此判断页面是否加载只能使用readyState判断页面加载状态。当页面加载完成后在进行添加事件的操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ready: function (fn) {
    // 判断DOM是否加载完毕
    if (document.readyState == "complete") {
    fn();
    // 判断是否含有addEventListener方法
    } else if (document.addEventListener) {
    document.addEventListener("DOMContentLoaded", function () {
    fn();
    });
    } else {
    // IE兼容
    document.attachEvent("onreadystatechange", function () {
    if ((document.readyState = "complete")) {
    fn();
    }
    });
    }
    },
  3. 字符串

    在这里需要判断是否是字符串并且需要去掉字符串两端的空格。但判断是否为字符串后边可能也会用到,因此定义一个静态方法,通过类名调用。方便后边的操作

    1
    2
    3
    kjQuery.isString = function (str) {
    return typeof str === "string";
    };

    去掉两端的空格在传入时去掉即可。同样定义一个静态方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 0 去除字符串两端的空格
    selector = kjQuery.trim(selector);
    kjQuery.trim = function (str) {
    if (!kjQuery.isString(str)) {
    return str;
    }
    if (str.trim) {
    return str.trim();
    } else {
    // IE兼容处理方案
    return str.replace(/^\s+|\s+$/g, "");
    }
    };
    • HTML代码

      判断是否为HTML与判断字符串一样,后边也可能会用到,因此定义一个静态方法。

      1
      2
      3
      4
      5
      6
      7
      kjQuery.isHTML = function (str) {
      return (
      str.charAt(0) == "<" &&
      str.charAt(str.length - 1) == ">" &&
      str.length >= 3
      );
      };
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      else if (kjQuery.isString(selector)) {
      // 2.1 判断是否是代码片段
      if (kjQuery.isHTML(selector)) {
      // 1. 根据代码片段创建所有的元素
      var temp = document.createElement("div");
      temp.innerHTML = selector;
      // 2. 创建好的一级元素添加到对象
      for (var i = 0; i < temp.children.length; i++) {
      this[i] = temp.children[i];
      }
      // 3. 给jQuery对象添加length属性
      this.length = temp.children.length;
      // 4. 返回加工好的this(jQuery)
      return this;
      }
      // 2.2 判断是否是选择器
      }

      其中第二三步可以修改为如下写法:

      1
      [].push.apply(this, temp.children);

      通过apply改变push的this,这样就不会push到数组里,而是push到this(此时为kjQuery对象)中。

    • 选择器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 2.2 判断是否是选择器
      else {
      // 1. 根据传入的选择器找到对应的元素
      var res = document.querySelectorAll(selector);
      // 2. 将找到的元素添加到kjQuery
      [].push.apply(this, res);
      // 3. 返回加工好的this
      return this;
      }
  4. 数组

    注意:但凡将自定义数组转换为真数组或伪数组都先转换为真数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    else if (
    typeof selector === "object" &&
    "length" in selector &&
    selector != window
    ) {
    // 3.1 真数组
    if ({}.toString.apply(selector) == "[object Array]") {
    [].push.apply(this, selector);
    return this;
    }
    // 3.2 伪数组
    else {
    // 将自定义的伪数组转换成真数组
    var arr = [].slice.call(selector);
    [].push.apply(this, arr);
    return this;
    }
    }

    以上代码可优化为如下:

    新增静态方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    kjQuery.isObject = function (selector) {
    return typeof selector === "object";
    };
    kjQuery.isWindow = function (selector) {
    return selector === window;
    };
    kjQuery.isArray = function (selector) {
    if (
    kjQuery.isObject(selector) &&
    !kjQuery.isWindow(selector) &&
    "length" in selector
    ) {
    return true;
    }
    return false;
    };
    1
    2
    3
    4
    5
    else if (kjQuery.isArray(selector)) {
    var arr = [].slice.call(selector);
    [].push.apply(this, arr);
    return this;
    }
  5. 其他类型(对象、DOM元素、基本数据类型等)

    1
    2
    3
    4
    5
    else {
    this[0] = selector;
    this.length = 1;
    return this;
    }

真伪数组转换

  1. 真数组转换为伪数组

    1
    2
    var obj = {},arr=[];
    [].push.apply(obj,arr);
  2. 伪数组转换为真数组

    1
    2
    var obj = {};
    var arr = [].slice.call(obj)

入口函数-extend方法

通过extend方法来为对象或类添加方法。

  1. 调用extend方法,传入一个对象。

    1
    2
    3
    4
    5
    6
    function kjQuery() { }
    kjQuery.extend({
    isTest: function () {
    console.log('test');
    }
    })
  2. extend方法的实现中,遍历传入的对象,并将值添加到类身上作为类的方法。

    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
    /* 为类和对象添加方法 */
    kjQuery.extend = kjQuery.prototype.extend = function (obj) {
    for (var key in obj) {
    this[key] = obj[key];
    }
    };
    /*
    /* 为类添加方法 */
    kjQuery.extend = function (obj) {
    // this指向kjQuery
    console.log(this);
    for (var key in obj) {
    // kjQuery[key] = obj[key]
    this[key] = obj[key]
    }
    }
    /* 为对象添加方法 */
    kjQuery.extend.prototype = function (obj) {
    // this指向实例化对象
    console.log(this);
    for (var key in obj) {
    // kjQuery[key] = obj[key]
    this[key] = obj[key]
    }
    }
    */
  3. 通过类名调用方法

    1
    kjQuery.isTest()

    image-20200621191222466

  4. 通过对象调用方法

    1
    2
    3
    4
    5
    6
    7
    var k = new kjQuery();
    k.extend({
    isDemo: function () {
    console.log('demo')
    }
    })
    k.isDemo()

    image-20200621193649802

因此可以将之前定义的静态方法改为如下写法:

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
kjQuery.extend = kjQuery.prototype.extend = function (obj) {
for (var key in obj) {
this[key] = obj[key];
}
};
kjQuery.extend({
isString: function (str) {
return typeof str === "string";
},
isHTML: function (str) {
return (
str.charAt(0) == "<" &&
str.charAt(str.length - 1) == ">" &&
str.length >= 3
);
},
trim: function (str) {
if (!kjQuery.isString(str)) {
return str;
}
if (str.trim) {
return str.trim();
} else {
// IE兼容处理方案
return str.replace(/^\s+|\s+$/g, "");
}
},
isObject: function (selector) {
return typeof selector === "object";
},
isWindow: function (selector) {
return selector === window;
},
isArray: function (selector) {
if (
kjQuery.isObject(selector) &&
!kjQuery.isWindow(selector) &&
"length" in selector
) {
return true;
}
return false;
},
isFunction: function (selector) {
return typeof selector === "function";
},
ready: function (fn) {
// 判断DOM是否加载完毕
if (document.readyState == "complete") {
fn();
// 判断是否含有addEventListener方法
} else if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", function () {
fn();
});
} else {
// IE兼容
document.attachEvent("onreadystatechange", function () {
if ((document.readyState = "complete")) {
fn();
}
});
}
},
});

完整代码

至此入口函数部分编写已经完成,下边是工具库的全部代码。

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
(function (window, undefiend) {
var kjQuery = function (selector) {
return new kjQuery.prototype.init(selector);
};
kjQuery.prototype = {
constructor: kjQuery,
init: function (selector) {
/*


代码片段:会将创建好的DOM元素存储到jQuery对象中返回
选择器: 会将找到的所有元素存储到jQuery对象中返回
3.数组:
会将数组中存储的元素依次存储到jQuery对象中立返回
4.除上述类型以外的:
会将传入的数据存储到jQuery对象中返回
*/
// 0 去除字符串两端的空格
selector = kjQuery.trim(selector);
// 1.传入 '' null undefined NaN 0 false, 返回空的jQuery对象
if (!selector) {
return this;
}
// 2. 方法处理
else if (kjQuery.isFunction(selector)) {
kjQuery.ready(selector);
}
// 3.字符串:
else if (kjQuery.isString(selector)) {
// 2.1 判断是否是代码片段
if (kjQuery.isHTML(selector)) {
// 1. 根据代码片段创建所有的元素
var temp = document.createElement("div");
temp.innerHTML = selector;
// // 2. 创建好的一级元素添加到对象
// for (var i = 0; i < temp.children.length; i++) {
// this[i] = temp.children[i];
// }
// // 3. 给jQuery对象添加length属性
// this.length = temp.children.length;
[].push.apply(this, temp.children);

// this是kjQuery
// 4. 返回加工好的this(jQuery)
}
// 2.2 判断是否是选择器
else {
// 1. 根据传入的选择器找到对应的元素
var res = document.querySelectorAll(selector);
// 2. 将找到的元素添加到kjQuery
[].push.apply(this, res);
// 3. 返回加工好的this
}
}
// 3 数组
else if (kjQuery.isArray(selector)) {
/*
// 3.1 真数组
if ({}.toString.apply(selector) == "[object Array]") {
[].push.apply(this, selector);
return this;
}
// 3.2 伪数组
else {
// 将自定义的伪数组转换成真数组
var arr = [].slice.call(selector);
[].push.apply(this, arr);
return this;
// 但凡将自定义数组转换为真数组或伪数组都先转换为真数组
}
*/
var arr = [].slice.call(selector);
[].push.apply(this, arr);
}
// 4. 其他类型
else {
this[0] = selector;
this.length = 1;
}
return this;
},
};
kjQuery.extend = kjQuery.prototype.extend = function (obj) {
for (var key in obj) {
this[key] = obj[key];
}
};
kjQuery.extend({
isString: function (str) {
return typeof str === "string";
},
isHTML: function (str) {
return (
str.charAt(0) == "<" &&
str.charAt(str.length - 1) == ">" &&
str.length >= 3
);
},
trim: function (str) {
if (!kjQuery.isString(str)) {
return str;
}
if (str.trim) {
return str.trim();
} else {
// IE兼容处理方案
return str.replace(/^\s+|\s+$/g, "");
}
},
isObject: function (selector) {
return typeof selector === "object";
},
isWindow: function (selector) {
return selector === window;
},
isArray: function (selector) {
if (
kjQuery.isObject(selector) &&
!kjQuery.isWindow(selector) &&
"length" in selector
) {
return true;
}
return false;
},
isFunction: function (selector) {
return typeof selector === "function";
},
ready: function (fn) {
// 判断DOM是否加载完毕
if (document.readyState == "complete") {
fn();
// 判断是否含有addEventListener方法
} else if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", function () {
fn();
});
} else {
// IE兼容
document.attachEvent("onreadystatechange", function () {
if ((document.readyState = "complete")) {
fn();
}
});
}
},
});
kjQuery.prototype.init.prototype = kjQuery.prototype;

window.kjQuery = window.$ = kjQuery;
})(window);