链模式(Operate of Responsibility)
通过在对象方法中将当前对象返回,实现对同一个对象多个方法的链式调用。从而简化对该对象的多个方法的多次调用时,对该对象的多次引用。
深究jQuery(参考版本: 2.2.4版本
)
jQuery的点语法是基于原型继承实现的,并且在每一个原型方法的实现上都返回当前对象this,使当前对象一直处于原型链作用域的顶端。
实现类似于jQuery
的框架语法
<p id="text"></p>
// 实现目标如下:
let text = $('#text');
console.log(text); // jQuery.fn.init [p#text, context: document, selector: '#text']
console.log(text.size()); // 1
text
.css({
padding: '10px',
'line-height': '30px',
'background-color': 'red'
})
.attr('class', 'user-name')
.html('ProsperLee')
.on('click', function () {
console.log('clicked');
});
console.log(text); // jQuery.fn.init [p#text.user-name, context: document, selector: '#text']
原型式继承
let A = function () {
};
A.prototype = {
length: 0,
size: function () {
return this.length;
}
};
let a = new A();
console.log(a); // A{}
console.log(a.__proto__); // {length: 0, size: ƒ}
a.size(); // 0
A.prototype.size(); // 0
A(); // undefined
A().size(); // 【Error】 Uncaught TypeError: Cannot read properties of undefined (reading 'size')
A.size(); // 【Error】 Uncaught TypeError: A.size is not a function
找位助手
此时因为A()
返回的是undefined
,所以使用A().size();
访问不到.于是我们可以借助第三方对象进行访问
let A = function () {
return B;
};
let B = A.prototype = {
length: 0,
size: function () {
return this.length;
}
};
A().size(); // 0
而在jQuery
中,为了减少变量的定义,将想使用的变量作为了A
的一个属性
let A = function () {
return A.fn;
};
A.fn = A.prototype = {
length: 0,
size: function () {
return this.length;
}
};
A().size(); // 0
获取元素
jQuery的目的是为了获取元素,返回的是一组元素簇(元素的聚合对象),但现在返回的却是一个A.fn对象,显然达不到我们的需求;所以,如果A.fn能提供给我们一个获取元素的方法init就好了.
let A = function (selector) {
return A.fn.init(selector);
};
A.fn = A.prototype = {
init: function (selector) {
return document.getElementById(selector);
},
length: 0,
size: function () {
return this.length;
}
};
A('container'); // <div id="container"></div>
A('container').size(); // Error ---> Uncaught TypeError: A(...).size is not a function
两个问题
此时是无法访问size
方法的,因为init
返回的是DOM
元素,故可以使用this
解决
let A = function (selector) {
return A.fn.init(selector);
};
A.fn = A.prototype = {
init: function (selector) {
this.selector = selector;
this[0] = document.getElementById(selector);
this.length = 1; // 重置length
return this;
},
length: 0,
size: function () {
return this.length;
}
};
A('container'); // {0: div#container, length: 1, selector: 'container', init: ƒ, size: ƒ}
A('container').size(); // 1
覆盖问题: box2覆盖了box1
let box1 = A('container1');
let box2 = A('container2');
console.log(box1); // {0: div#container2, length: 1, selector: 'container2', init: ƒ, size: ƒ}
console.log(box2); // {0: div#container2, length: 1, selector: 'container2', init: ƒ, size: ƒ}
覆盖获取
后获取id的DOM元素会将先获取id的DOM元素进行覆盖,是因为每次在A的构造函数中返回的A.fn.init(selector);对象指向同一个对象造成的
解决方法
: 使用new关键字进行处理,让被赋值的变量都是独立的对象
let A = function (selector) {
return new A.fn.init(selector);
};
A.fn = A.prototype = {
init: function (selector) {
this.selector = selector;
this[0] = document.getElementById(selector);
this.length = 1; // 重置length
return this;
},
length: 0,
size: function () {
return this.length;
}
};
let box1 = A('container1');
let box2 = A('container2');
console.log(box1); // {0: div#container1, length: 1, selector: 'container1', init: ƒ, size: ƒ}
console.log(box2); // {0: div#container2, length: 1, selector: 'container2', init: ƒ, size: ƒ}
box1.length; // 1
box1.size(); // Error ---> Uncaught TypeError: box1.size is not a function
方法丢失
因为我们使用new关键字,导致无法在init原型上找到size方法,导致方法丢失,所以访问size方法报错
let A = function (selector) {
return new A.fn.init(selector);
};
A.fn = A.prototype = {
init: function (selector) {
console.log(this === A.fn);
console.log(this === A.prototype);
this.selector = selector;
this[0] = document.getElementById(selector);
this.length = 1; // 重置length
return this;
},
length: 0,
size: function () {
return this.length;
}
};
// 未使用new的
A.fn.init('container'); // true true
// 使用了new的,所以找不到size方法,解决方案请向下看
A('container'); // false false
对比jQuery
对比返回对象的构造器constructor
let A = function (selector) {
return new A.fn.init(selector);
};
A.fn = A.prototype = {
init: function (selector) {
this.selector = selector;
this[0] = document.getElementById(selector);
this.length = 1; // 重置length
return this;
},
length: 0,
size: function () {
return this.length;
}
};
console.log($('#container').constructor.name); // jQuery
console.log(A('container').constructor.name); // init
解决方法: 故需要我们去强化构造器
let A = function (selector) {
return new A.fn.init(selector);
};
A.fn = A.prototype = {
constructor: A, // 强化构造函数,使构造器constructor返回A对象
init: function (selector) {
this.selector = selector;
this[0] = document.getElementById(selector);
this.length = 1; // 重置length
return this;
},
length: 0,
size: function () {
return this.length;
}
};
// 要访问size方法需要添加这一行
A.fn.init.prototype = A.fn; // 单独添加这一行会导致constructor返回的是Object
console.log($('#container').constructor.name); // jQuery
console.log(A('container').constructor.name); // A
A('container').size(); // 1
丰富元素获取
丰富功能,可以获取各种选择器返回的DOM元素
<div id="box0">
<ul id="box1">
<li class="item"></li>
<li></li>
<li class="item"></li>
</ul>
<div class="item"></div>
</div>
let A = function (selector, context) {
return new A.fn.init(selector, context);
};
A.fn = A.prototype = {
constructor: A,
/**
* 初始化
* @param selector 选择器
* @param context 上下文
* @returns {A} 返回A对象
*/
init: function (selector, context) {
this.selector = selector;
this.context = context || document;
let doms = this.context.querySelectorAll(this.selector); // 获取元素集合
this.length = doms.length;
doms.forEach((dom, index) => this[index] = dom);
return this;
},
length: 0,
size: function () {
return this.length;
}
};
A.fn.init.prototype = A.fn;
/**
* init {
* 0: div#box0,
* selector: '#box0',
* context: document,
* length: 1
* }
*/
console.log(A('#box0'));
/**
* init {
* 0: li.item,
* 1: li.item,
* 2: div.item,
* selector: '.item',
* context: document,
* length: 3
* }
*/
console.log(A('.item'));
/**
* init {
* 0: li.item,
* 1: li,
* 2: li.item,
* selector: 'li',
* context: document,
* length: 3
* }
*/
console.log(A('li'));
/**
* init {
* 0: li.item,
* 1: li.item,
* selector: '.item',
* context: ul#box1,
* length: 2
* }
*/
console.log(A('.item', document.getElementById('box1')));
/**
* init {
* selector: 'xxx',
* context: document,
* length: 0
* }
*/
console.log(A('xxx'));
数组与对象
由于JavaScript的弱类型语言,并且数组、对象、函数都被看成是对象的实例,所以JavaScript中并没有一个纯粹的数组类型,而且JavaScript引擎的实现也没有做严格的校验,也是基于对象实现的.
一些浏览器解析引擎在判断对象是否是数组的时候不仅仅判断其有没有length属性,可否通过
[索引值]
方式访问元素,还会判断其是否具有数组方法来确定是否要用数组的形式展现,所以我们只需要在A.fn中添加几个数组常用的方法来增强数组特性就可以解决问题了
let A = function (selector, context) {
return new A.fn.init(selector, context);
};
A.fn = A.prototype = {
constructor: A,
// 增强数组(参见访问者模式)
push: [].push,
sort: [].sort,
splice: [].splice,
/**
* 初始化
* @param selector 选择器
* @param context 上下文
* @returns {A} 返回A对象
*/
init: function (selector, context) {
this.selector = selector;
this.context = context || document;
let doms = this.context.querySelectorAll(this.selector); // 获取元素集合
this.length = doms.length;
doms.forEach((dom, index) => this[index] = dom);
return this;
},
length: 0,
size: function () {
return this.length;
}
};
A.fn.init.prototype = A.fn;
console.log(A('#text')); // init [p#text.user-name, selector: '#text', context: document] ---> length: 1
方法拓展
对内部对象的拓展和对外部对象的拓展
let A = function (selector, context) {
return new A.fn.init(selector, context);
};
A.fn = A.prototype = {
constructor: A,
// 增强数组
push: [].push,
sort: [].sort,
splice: [].splice,
/**
* 初始化
* @param selector 选择器
* @param context 上下文
* @returns {A} 返回A对象
*/
init: function (selector, context) {
this.selector = selector;
this.context = context || document;
let doms = this.context.querySelectorAll(this.selector); // 获取元素集合
this.length = doms.length;
doms.forEach((dom, index) => this[index] = dom);
return this;
},
length: 0,
size: function () {
return this.length;
}
};
A.fn.init.prototype = A.fn;
/**
* 对象拓展 - 对内部对象的拓展和对外部对象的拓展
* @type {A.extend}
*/
A.extend = A.fn.extend = function (...args) {
// 如果只存在一个参数,那么是对内部对象进行拓展;
// 如果是多个参数,那么第一个参数作为需要拓展的对象,为外部对象的拓展;
let target = args.length <= 1 ? this : args[0];
args.forEach(arg => {
for (const k in arg) {
target[k] = arg[k];
}
});
return target;
};
A.extend({
name: 'Tom'}, {
age: 18}); // {name: 'Tom', age: 18}
A.extend(A.fn, {
version: '1.0.0',
getVersion: function () {
return this.version;
}
});
console.log(A('container').getVersion()); // 1.0.0
A.extend({
version: 'v1.0.0',
getVersion: function () {
return this.version;
}
});
console.log(A.getVersion()); // v1.0.0
添加方法并使用
<button class="btn">按钮01</button>
<button class="btn">按钮02</button>
<button class="btn">按钮03</button>
let A = function (selector, context) {
return new A.fn.init(selector, context);
};
A.fn = A.prototype = {
constructor: A,
// 增强数组
push: [].push,
sort: [].sort,
splice: [].splice,
/**
* 初始化
* @param selector 选择器
* @param context 上下文
* @returns {A} 返回A对象
*/
init: function (selector, context) {
this.selector = selector;
this.context = context || document;
let doms = this.context.querySelectorAll(this.selector); // 获取元素集合
this.length = doms.length;
doms.forEach((dom, index) => this[index] = dom);
return this;
},
length: 0,
size: function () {
return this.length;
}
};
A.fn.init.prototype = A.fn;
/**
* 对象拓展 - 对内部对象的拓展和对外部对象的拓展
* @type {A.extend}
*/
A.extend = A.fn.extend = function (...args) {
// 如果只存在一个参数,那么是对内部对象进行拓展;
// 如果是多个参数,那么第一个参数作为需要拓展的对象,为外部对象的拓展;
let target = args.length <= 1 ? this : args[0];
args.forEach(arg => {
for (const k in arg) {
target[k] = arg[k];
}
});
return target;
};
A.extend({
// 将'-'分割线转化为驼峰式,如:'border-color' -> 'borderColor'
camelCase: function (str) {
return str.replace(/-(\w)/g, function (all, letter) {
return letter.toUpperCase();
});
},
});
A.fn.extend({
css: function (obj) {
const len = this.length;
for (let i = 0; i < len; i++) {
for (const k in obj) {
this[i].style[A.camelCase(k)] = obj[k];
}
}
return this;
},
attr: function (key, value) {
const len = this.length;
for (let i = 0; i < len; i++) {
this[i].setAttribute(key, value);
}
return this;
},
html: function (htmlStr) {
const len = this.length;
for (let i = 0; i < len; i++) {
this[i].innerHTML = htmlStr;
}
return this;
},
on: function (type, fn) {
const len = this.length;
for (let i = 0; i < len; i++) {
this[i].addEventListener(type, function (e) {
fn.call(null, e);
}, false);
}
return this;
},
});
let btns = A('.btn');
btns
.css({
padding: '3px 10px',
borderRadius: '3px',
borderStyle: 'solid',
'border-width': '1px',
'border-color': 'red',
color: 'red'
})
.attr('class', 'btn btn-item')
.html('按钮喽~~~')
.on('click', function (e) {
console.log(e.target);
});
/**
* init(3) [
* button.btn.btn-item,
* button.btn.btn-item,
* button.btn.btn-item,
* selector: '.btn',
* context: document,
* length: 3
* ]
*/
console.log(btns);
函数、函数简写、箭头函数
区别
let obj = {
fnA: function () {
// console.log(fnA); // Error ---> Uncaught ReferenceError: fnA is not defined
console.log(this); // obj
},
fnB() {
// console.log(fnB); // Error ---> Uncaught ReferenceError: fnB is not defined
console.log(this); // obj
},
fnC: function fnD() {
// console.log(fnC); // Error ---> Uncaught ReferenceError: fnC is not defined
console.log(fnD); // ƒ fnD() { ... }
console.log(this); // obj
},
fnE: () => {
// console.log(fnE); // Error ---> Uncaught ReferenceError: fnE is not defined
console.log(this); // window
}
};
obj.fnA();
obj.fnB();
obj.fnC();
obj.fnE();