简短的概括:
1、闭包作用域
2、闭包应用:循环事件绑定的解决方法
3、全面分析let和var的区别
4、JS高阶编程技巧 (闭包进阶应用)
1)应用1:循环事件绑定或者循环操作中对于闭包的应用
2)应用2:基于“闭包”实现早期的“模块化”思想
3)应用3:惰性函数(思想)
4)应用4:柯里化函数 & 重写reduce
5)应用5:compose组合函数
6)应用6:jQuery(JQ)中关于闭包的使用
7)应用7:函数的防抖和节流
闭包作用域
* 核心答案 | 基础知识要夯实
? 例如一:闭包作用域 - 面试题( 面试常问 )
let x = 5;
function fn(x) {
return function (y) {
console.log(y + (++x)); // 结果:14, 18, 18
}
}
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x); // 结果:5
画图分析:( 有图有真相 )
? 例如二:闭包作用域 - 面试题( 面试常问 )
let a = 0,
b = 0;
function A(a) {
A = function (b) {
alert(a + b++);
};
alert(a++);
}
A(1);
A(2);
画图分析:( 有图有真相 )
闭包应用:循环事件绑定的解决方法
* 核心答案 | 基础知识要夯实
? 例子一: 递归导致内存溢出 (函数执行中再次调用自己执行)
// 下面案例是“死递归” Uncaught RangeError: Maximum call stack size exceeded “内存溢出”
function fn(x) {
// console.log(x);
fn(x + 1);
}
fn(1);
? 例子二: 循环事件绑定
* 需求分析:有三个按钮,依次单击按钮出现1,2,3。(深入分析)
HTML结构代码:
我是第1个按钮
我是第2个按钮
我是第3个按钮
js代码:
// 实现不了:我们要清楚原因
var buttons = document.querySelectorAll('button'); //=>NodeList“类数组”集合
for (var i = 0; i buttons[i].onclick = function () {
console.log(`当前点击按钮的索引:${i}`); //结果:3,3,3
};
}
画图分析:( 有图有真相 )
实现不了主要原因:
每次点击触发函数执行,i获取的都是全局的,也就是循环后的结果3。
最终的解决方案:
* 方案一:基于“闭包”的机制完成
核心:每一轮循环都产生一个闭包,“存储对应的索引”;点击事件触发,执行对应的函数,让其上级上下文是闭包即可。
? 例子一:
var buttons = document.querySelectorAll('button');
for (var i = 0; i // 每一轮循环都会形成一个闭包,存储私有变量i的值(当前循环传递的i的值)// + 自执行函数执行,产生一个上下文EC(A) 私有形参变量i=0/1/2// + EC(A)上下文中创建一个小函数,并且让全局buttons中的某一项占用创建的函数
(function (i) {
buttons[i].onclick = function () {
console.log(`当前点击按钮的索引:${i}`);
};
})(i);
}
画图分析:( 有图有真相 )
? 例子二:
var buttons = document.querySelectorAll('button');
for (var i = 0; i buttons[i].onclick = (function (i) {
return function () {
console.log(`当前点击按钮的索引:${i}`);
};
})(i);
}
* 解释如下:( 重要 )
var obj = { // 把自执行函数执行的返回值 (小函数)赋值给fn
fn: (function () { // 闭包
console.log('大函数');
return function () {
console.log('小函数');
}
})()
};
obj.fn(); // 执行的是返回的小函数
? 例子三:
// 基于LET这种玩法也是“闭包”方案
let buttons = document.querySelectorAll('button');
for (let i = 0; i buttons[i].onclick = function () {
console.log(`当前点击按钮的索引:${i}`);
};
}
总结:闭包的方式不仅 浪费了堆内存,也浪费了栈内存。
* 方案二:自定义属性 「性能强于闭包」
var buttons = document.querySelectorAll('button');
for (var i = 0; i // 每一轮循环都给当前按钮(对象)设置一个自定义属性:存储它的索引
buttons[i].myIndex = i;
buttons[i].onclick = function () {
// this -> 当前点击的按钮
console.log(`当前点击按钮的索引:${this.myIndex}`);
};
}
总结:自定义属性的方式虽然 栈内存没有浪费,但堆内存还有。
* 方案三:事件委托 「比之前的性能提高40%-60%」
核心:不论点击BODY中的谁,都会触发BODY的点击事件;ev.target的事件源:具体点击的是谁。
document.body.onclick = function (ev) {
var target = ev.target,
targetTag = target.tagName;
// 点击的是BUTTON按钮
if (targetTag === "BUTTON") {
var index = target.getAttribute('index');
console.log(`当前点击按钮的索引:${index}`);
}
};
总结:事件委托的方式堆内存没有,栈内存也没有,只有一个堆函数。
全面分析let和var的区别
* 核心答案 | 基础知识要夯实
声明方式 | 变量提升 | 作用域 | 初始值 | 重复定义 |
const | 否 | 块级 | 需要 | 不允许 |
let | 否 | 块级 | 不需要 | 不允许 |
var | 是 | 函数级 | 不需要 | 允许 |
* 1、let VS const
1)let声明一个变量,变量存储可以改值;
2)const声明的变量,一但赋值,则不能再和其他的值关联(不允许指针重新指向);
let n = 12;
n = 13;
console.log(n); //13
const obj = {
name: "前端学苑"
};
obj.name = "小贾";
console.log(obj); // {name: "小贾"}
* 2、let VS var
1)var存在变量提升,而let不存在
2)“全局上下文中”,基于var声明的变量,也相当于给GO(全局对象 window)新增一个属性,并且任何一个发生值的改变,另外一个也会跟着变化(映射机制);但是基于let声明的变量,就是全局变量,和GO没有任何的关系;
let n = 12;
console.log(n); //12
console.log(window.n); //undefined
// VO(G): var n=12; <=> GO(window): window.n=12;
var n = 12;
console.log(n); //12
console.log(window.n); //12
window.n = 13;
console.log(n); //13
// 1、没有基于任何关键词声明的,则相当于给window设置一个属性
n = 13; //window.n=13;
// 2、首先看是否为全局变量,如果不是,则再看是否为window的一个属性...
console.log(n); //13
// 3、如果两者都不是,则变量未被定义 Uncaught ReferenceError
console.log(m); // m is not defined
function fn() {
// 私有的上下文
m = 13; //window.m=13 按照作用域链查找机制,当前变量找到全局都没有,则相当于给window设置一个属性
console.log(m); //13
console.log(n); //如果是获取的操作,则直接报错 Uncaught ReferenceError: n is not defined
}
fn();
console.log(m); //13
3) 在相同的上下文中,let不允许重复声明(不论你之前基于何种方式声明,只要声明过,则都不能基于let重复声明了);而var很松散,重复声明也无所谓,反正浏览器也只按照声明一次处理;
4) 暂时性死区「浏览器暂存的BUG」;
console.log(n); //Uncaught ReferenceError: n is not defined
console.log(typeof n); //undefined 基于typeof检测被声明的变量,结果是undefined
5) let/const/function会产生块级私有上下文,而var是不会的。
* 3、上下文 & 作用域:
1)全局上下文;
2)函数执行形成的“私有上下文”;
3)块级作用域(块级私有上下文) 除了 对象/函数... 的大括号之外(例如:判断体、循环体、代码块...)都可能会产生块级上下文。
// n是全局上下文的:代码块不会对他有任何的限制// m是代码块所代表的块级上下文中私有的// debugger; //开启断点调试 =>BUG调试
{
var n = 12;
console.log(n); //12
let m = 13;
console.log(m); //13
}
console.log(n); //12
console.log(m); //Uncaught ReferenceError: m is not defined
// i是全局的
for (var i = 0; i 5; i++) {
console.log(i); //0~4
}
console.log(i); //5
// i不是全局的,上下文的私有的
for (let i = 0; i 5; i++) {
console.log(i); //0~4
}
console.log(i); //Uncaught ReferenceError: i is not defined
* 推荐写法:
// i及循环中用到的i都是在全局下声明的全局变量(循环不会产生块级上下文)
let i = 0;
for (; i 5; i++) {
console.log(i); //0~4
}
console.log(i); //5
JS中最基本的this情况分析
* 核心答案 | 基础知识要夯实
this函数的执行主体 (注:对象后面分析)
1)函数执行主体:谁把函数执行的;
2)函数执行上下文:在哪执行的;
// 全局上下文中的this是window;// 块级上下文中没有自己的this,所用到的this都是所处上级上下文中的this;
console.log(this); //window
{
let n = 12;
console.log(this); //window 上级上下文是全局上下文
}
规律:
1)事件绑定:给当前元素的某个事件行为绑定方法,当事件触发、方法执行,方法中的this是当前元素本身;
2)普通函数执行
1. 函数执行前面是否有“点”,没有“点”,this就是window(或者JS严格模式下是undefined);
2. 有“点”,“点”前面是谁this就是谁;
3.匿名函数(自执行函数/回调函数)如果没有经过特殊的处理,则this一般都是window/undefined,但是如果经过一些特殊处理,一切都以处理后的结果为主;
1、事件绑定
document.body.onclick = function () {
console.log(this); //body
};
document.body.addEventListener('click', function () {
console.log(this); //body
});
// IE6~8 DOM2事件绑定
document.body.attachEvent('onclick', function () {
console.log(this); //window
});
2、普通函数执行
function sum () {
console.log(this); // 结果:undefined
}
说明:创建函数,没有执行。this不知道是谁。
1)有“点”,“点”前面是谁this就是谁
// 开启JS严格模式 => 扩展:严格模式和非严格模式的区别// "use strict";
function fn() {
console.log(this);
}
let obj = {
name: '前端学苑',
fn: fn
};
fn(); //this->window/undefined
obj.fn(); //this->obj
2)匿名函数
(function () {
console.log(this); //this->window/undefined
})();
说明:开启严格模式是undefined
function fn(callback) {
callback(); //this->window/undefined
}
let obj = {
// sum:function(){}
sum() {
console.log(this); // window
}
};
// obj.sum(); //this->obj// 回调函数:把一个函数作为实参值,传递给另外一个函数// => fn(function () {});
fn(obj.sum);
setTimeout(function () {
console.log(this); //window或者undefined
}, 1000);
let obj = {
name: 'xxx'
};
let arr = [10, 20];
arr.forEach(function (item, index) {
console.log(this); // this -> obj
}, obj);
说明:obj 因为触发回调函数执行的时候,forEach内部会把回调函数中的this改变为传递的第二个参数值obj “特殊处理”。
let obj = {
sum() {
console.log(this);
}
};
obj.sum(); //this->obj
(obj.sum)(); //this->obj
(10, obj.sum)(); //this->window
说明:括号表达式,小括号中包含“多项”(如果只有一项,和不加括号没啥本质区别),其结果是只取最后一项;但是这样处理后,this会发生改变,变为window/undefined。
JS高阶编程技巧
* 核心答案 | 基础知识要夯实
JS高阶编程技巧「本质:基于“闭包”的机制完成的」
* 应用1:循环事件绑定或者循环操作中对于闭包的应用
* 面试题 ( 面试常问 )
for (var i = 0; i 3; i++) {
setTimeout(() => { // 设置定时器的时候,这个函数是创建不是执行
console.log(i); // 结果:3, 3, 3
}, (i + 1) * 1000);
}
分析:
setTimeout([function],[interval]):设置一个定时器,等待[interval]这么长的时间后,触发[function]执行。
------i是全局下的变量
i=0 第一轮循环
setTimeout(() => { //设置定时器的时候,这个函数是创建不是执行
console.log(i);
}, 1000);
i=1 第二轮循环
setTimeout(() => {
console.log(i);
}, 2000);
i=2 第三轮循环
setTimeout(() => {
console.log(i);
}, 3000);
i=3 循环结束
------1000ms时间到了
执行 () => {console.log(i);} 这个函数
1)在形成的私有上下文中遇到变量i,发现并不是自己私有的,找上级上下文(全局)下的i;
2)结果都是循环结束后的3;
如何实现结果是:0,1,2 ?
? 例子一:
for (var i = 0; i 3; i++) {
// 每一轮循环,自执行函数执行,都会产生一个私有上下文;并且是把当前这一轮循环,全局变量i的值作为实参,传递给私有上下文中的形参i// + EC(AN1) 形参赋值:i=0// + EC(AN2) 形参赋值:i=1// + EC(AN3) 形参赋值:i=2// -> 每一个形成的私有上下文中,都会创建一个“箭头函数堆”,并且把其赋值给 window.setTimeout ,// 这样等价于,当前上下文中的某些内容,被上下文以外的东西给占用了,形成的上下文不会释放(私有变量i的值也不会被释放) 「闭包」
(function (i) {
setTimeout(() => {
console.log(i); 结果:0,1,2
}, (i + 1) * 1000);
})(i);
}
? 例子二:
// let xxx=proxy(0) -> proxy执行会产生闭包,闭包中私有的形参变量存储传递的实参信息
const proxy = i => {
return () => {
console.log(i);
};
};
for (var i = 0; i 3; i++) {
setTimeout(proxy(i), (i + 1) * 1000);
// 到达时间后,执行的是proxy返回的小函数
}
? 例子三:
for (let i = 0; i 3; i++) {
setTimeout(() => {
console.log(i);
}, (i + 1) * 1000);
}
基于let的循环(let存在“块级作用域”的处理机制)
1)首先浏览器会创建一个父级私有的上下文,控制循环;
2)每一轮循环还会产生一个私有的块级上下文,都有自己的私有变量i,存储当前这一轮循环i的值;
3)EC(BLOCK1) 私有变量i=0;
4)EC(BLOCK2) 私有变量i=1;
5)EC(BLOCK3) 私有变量i=2;
-> 每一个私有块级上下文中,也是创建一个箭头函数,并且被window.setTimeout占用了,也一样不会释放这个块级上下文「闭包」。
和我们自己写代码形成“闭包”的区别:它是浏览器底层实现的,从性能上比我们自己写的要快那么一些。
* 性能对比
for (let i = 0; i 3; i++) {
setTimeout(() => {
// ... 处理很多事情,但是函数中不需要使用i的值
}, (i + 1) * 1000);
}
每一轮循环都会产生一个私有的块级上下文,如果上下文中没有什么东西被外部占用,则本轮循环结束,私有块级上下文也会被释放掉;但是一旦有东西被占用,则会产生闭包,性能上会有所消耗。
let i = 0;
for (; i 3; i++) {
setTimeout(() => {
// ...
}, (i + 1) * 1000);
}
这种写法,在循环的时候就不会产生块级上下文了,性能上比之前要还好一些。
* 应用2:基于“闭包”实现早期的“模块化”思想
1)单例设计模式(模块化概念)
2)AMD -> require.js
3)CMD -> sea.js
4)CommonJS -> Node本身就是基于这种规范实现的
5)ES6Module
解决变量冲突:闭包机制 -> 保护
(function () {
let name = '张三';
let age = 22;
let girlfriend = false;
const skill = function () {};
// 把私有的信息暴露到全局上,这样在其他的上下文中,就可以调用这些信息了;// 不能暴露太多,暴露多了,也会导致冲突;
window.skill = skill;
})();
(function () {
let name = '李四';
let age = 81;
let girlfriend = false;
skill();
})();
解决变量冲突:对象 -> 把描述当前事物特征的内容,全部汇总到一个对象中(存储到同一个堆内存中)
let person1 = {
name: '张三',
age: 22,
girlfriend: false,
skill: function () {}
};
let person2 = {
name: '李四',
age: 81,
girlfriend: false,
};
说明:
1)对象在这里起到一个“分组”的作用。
2)新称呼:person1/person2 被称为命名空间;
此处相当于,把描述同一个事物的属性,存放到相同的命名空间下,以此来进行分组,减少全局变量的污染;
每一个对象都是Object这个类的一个实例,person1和person2是两个完全不同的实例,所以我们也可以把这种方式称之为“单例设计模式” -> 目的也是解决全局变量冲突和污染的;
扩展:
单例设计模式就是“破对象”;
new Xxx —> 构造函数模式;
高级单例设计模式:JS中最早期的模块化开发思想,“模块之间的独立性以及互通性”。
1)把一个复杂或者大型的产品,按照功能特点拆分成一个个的模块;
2)每个模块都是独立的,相互之间的信息互不干扰(有助于团队协作开发);
3)但是对于一些需要供其他模块用到的公共方法,我们是可以实现相互调用的;
? 模块化开发例子:
// 前端组长 -> 公共方法封装
let utils = (function () {
let isWindow = true,
num = 0;
const queryElement = function queryElement(selector) {};
const formatTime = function formatTime(time) {};
// 把需要供外界调用的方法,存储到一个命名空间(对象)中
return {
// queryElement:queryElement
queryElement,
formatTime
};
})();
// 程序猿A -> 搜索
let searchModal = (function () {
let num = 10;
const checkValue = function checkValue() {
utils.formatTime();
};
const submit = function submit() {};
return {
checkValue
};
})();
// 程序媛B -> 换肤
let skinModal = (function () {
let num = 0;
const submit = function submit() {
searchModal.checkValue();
};
return {};
})();
* 应用3:惰性函数(思想)
懒:能够干一次的绝对不会干第二次。
需求:获取元素的样式?
思路:
1)获取元素的样式,使用window.getComputedStyle(element) 属性名获取,但不兼容IE678 * 若不兼容;
2)则使用element.currentStyle(属性名);
说明:属性名 in 对象:检测当前这个属性是否属于这个对象。
代码如下:
function get_css(element, attr) {
if ('getComputedStyle' in window) {
return window.getComputedStyle(element)[attr];
}
return element.currentStyle[attr];
}
var w = get_css(document.body, 'width');
console.log(w);
var h = get_css(document.body, 'height');
console.log(h);
性能上的问题:
在某个浏览器中渲染页面(渲染代码)
1)第一次执行 get_css 需要验证浏览器的兼容性;
2)后期每一次执行 get_css ,浏览器的兼容性检测都会执行一遍 “这个操作是没有必要的”;
* 改造,基于“惰性函数”提高上述的性能
function get_css(element, attr) {
// 第一次执行get_css,根据浏览器的兼容情况,对外部的get_css函数进行重构
if ('getComputedStyle' in window) {
get_css = function (element, attr) {
return window.getComputedStyle(element)[attr];
};
} else {
get_css = function (element, attr) {
return element.currentStyle[attr];
};
}
// 第一次执行也是需要获取到结果的,所以我们把重构的函数执行一次
return get_css(element, attr);
}
var w = get_css(document.body, 'width');
console.log(w);
// 后续再次执行get_css,执行是的是第一次重构后的小方法,无需再次校验兼容性
var h = get_css(document.body, 'height');
console.log(h);
* 应用4:柯里化函数 & 重写reduce
柯里化函数
预处理的思想:预先存储,后续拿来直接使用。
1)执行函数,形成一个闭包,把一些信息(私有变量和值)存储起来「保存作用」;
2)以后其下级上下文中如果需要用到这些值,直接基于作用域链查找机制,拿来直接用即可;
? 柯里化函数例子 (es5) :
function fn() {
// 存储执行fn传递的实参信息
let outerArgs = Array.from(arguments);
return function anonymous() {
// 存储执行小函数传递的实参信息
let innerArgs = Array.from(arguments);
// 存储两次执行函数传递的实参信息
let params = outerArgs.concat(innerArgs);
return params.reduce(function (result, item) {
return result + item;
});
};
}
let res = fn(1, 2)(3);
console.log(res); //=>6 1+2+3
? 柯里化函数例子 (es6) :
const fn = (...outerArgs) => {
return (...innerArgs) => {
return outerArgs.concat(innerArgs).reduce((result, item) => {
return result + item;
});
};
};
let res = fn(1, 2)(3);
console.log(res); //=>6 1+2+3
? 柯里化函数例子 (es6简写方式) :
const fn = (...outerArgs) => (...innerArgs) => outerArgs.concat(innerArgs).reduce((result, item) => result + item);
let res = fn(1, 2)(3);
console.log(res); //=>6 1+2+3
? 柯里化函数例子
function fn(x) {
return function (y) {
return function (z) {
return x + y + z;
}
}
}
let res = fn(10)(20)(30);
console.log(res) // 60
* 数组求和方法:
1、eval方法处理
eval([10, 20, 30].join('+')) // 60
2、循环遍历数组中的每一项,以些实现求和
let arr = [10, 20, 30],
total = 0;
arr.forEach(function(item){
total += item;
})
console.log(total) // 60
数组 reduce
数组中的reduce:依次遍历数组中的每一项,可以把上一轮遍历得到的结果,传递给下一轮,以此实现结果的累计。
1)arr.reduce([function]):会把数组第一项做为初始结果,从数组第二项开始遍历;
2)arr.reduce([function],[value]):第二个传递的参数作为初始结果,从数据第一项开始遍历;
? reduce例子一:
let arr = [10, 20, 30, 40];
let result = arr.reduce(function (result, item, index) {
// 第一轮遍历: result->10「数组中的第一项值」 item->20 index->1// 第二轮遍历: result->30「上一轮遍历,函数返回的结果」 item->30 index->2// 第三轮遍历: result->60 item->40 index->3
console.log(result, item, index);
return result + item;
});
? reduce例子二:
let arr = [10, 20, 30, 40];
let result = arr.reduce(function (result, item, index) {
// 第一轮遍历: result->0 item->10 index->0// 第二轮遍历: result->10 item->20 index->1// ....
console.log(result, item, index);
return result + item;
}, 0);
console.log(result); //100
* 重构reduce-面试题 ( 面试常问 )
function reduce(arr, callback, initValue) {
let result = initValue,
i = 0;
// 没有传递initValue初始值:把数组第一项作为初始值,遍历从数组第二项开始
if (typeof result === "undefined") {
result = arr[0];
i = 1;
}
// 遍历数组中的每一项:每一次遍历都会把callback执行
for (; i result = callback(result, arr[i], i);
}
return result;
}
let arr = [10, 20, 30, 40];
let result = reduce(arr, function (result, item, index) {
return result + item;
});
console.log(result); //结果;100
* 应用5:compose组合函数
命令式编程:注重过程的管控;
函数式编程:注重最后的结果 (大部分需求,函数式更好);
在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。例如:
const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;
div2(mul3(add1(add1(0)))); //=>3
而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
const operate = compose(div2, mul3, add1, add1)
operate(0) //=>相当于div2(mul3(add1(add1(0))))
operate(2) //=>相当于div2(mul3(add1(add1(2))))
简而言之:compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x),请完成 compose函数的编写
const add1 = x => x + 1;
const mul3 = x => x * 3;
const div2 = x => x / 2;
// funcs:按照管道的顺序依次存储着要处理的函数
const compose = (...funcs) => {
return x => {
let len = funcs.length;
if (len === 0) return x;
if (len === 1) return funcs[0](x);
return funcs.reduceRight((result, item) => {
return item(result);
}, x);
};
};
const operate = compose(div2, mul3, add1, add1);
console.log(operate(0)); //3
console.log(operate(2)); //6
console.log(compose()(10)); //10
console.log(compose(div2)(10)); //5
* 简写方式 (需要深入)
const compose = (...funcs) => {
let len = funcs.length;
if (len === 0) return x => x;
if (len === 1) return funcs[0];
return funcs.reduce((a, b) => {
return x => {
return a(b(x));
};
});
};
// [div2, mul3, add1, add1]//第一轮遍历 a=div2 b=mul3 // x=>a(b(x)) 0x000//第二轮遍历 a=0x000 b=add1// x=>a(b(x)) 0x001//第三轮遍历 a=0x001 b=add1// x=>a(b(x)) 0x002//--->最后返回的就是 0x002,把其赋值给 operate// 0x002(0) -> 0x001(add1(0)) // 0x001(1) -> 0x000(add1(1))// 0x000(2) -> div2(mul3(2)) =>3
* 应用6:jQuery(JQ)中关于闭包的使用
1、第一代码块
let params1 = typeof window !== "undefined" ? window : this;
利用JS的暂时性死区:基于typeof检测一个未被声明的变量,不会报错,结果是undefined。
1)浏览器环境下 & APP的webview环境下:默认就有window -> GO;
2)Node环境下:没有window,this->global || 当前模块;
2、第二代码块
let params2 = function (window, noGlobal) {
// 浏览器环境下:window->window noGlobal->undefined// Node环境下:window->this noGlobal->true
var jQuery = function (selector, context) {
// ...
};
// ...// 为了防止我们暴露到全局的jQuery和$,和全局下现有的内容冲突,我们可以提供$等使用权限的转让
var _jQuery = window.jQuery,
_$ = window.$;
jQuery.noConflict = function noConflict(deep) {
if (window.$ === jQuery) {
window.$ = _$;
}
if (deep && window.jQuery === jQuery) {
window.jQuery = _jQuery;
}
return jQuery;
};
// 浏览器环境下,基于window.xxx=xxx的方式,把jQuery暴露到全局// jQuery() 或者 $() -> 都是把内部私有的jQuery执行
if (typeof noGlobal === "undefined") {
window.jQuery = window.$ = jQuery;
}
return jQuery;
};
3、第三代码块 ( 区分环境 )
(function (global, factory) {
// 浏览器环境下:global->window Node环境下:global->当前module// factory:回调函数
"use strict";
if (typeof module === "object" && typeof module.exports === "object") {
// 支持CommonJS模块规范的环境:Node环境// -> params2(this,true)
module.exports = global.document ?
factory(global, true) :
function (w) {
if (!w.document) {
throw new Error("jQuery requires a window with a document");
}
return factory(w);
};
} else {
// 浏览器环境(因为它是不支持CommonJS规范)// -> params2(window)
factory(global);
}
})(params1, params2);
* 不同类库之间对$使用权的冲突
var _jQuery = window.jQuery,
_$ = window.$;
// 当前类库没有导入完之前,把之前全局的$和 jQuery存储起来
jQuery.noConflict = function (deep) {
// 发现有冲突执行这个方法,把$的使用权归还给之前使用它的人
if (window.$ === jQuery) {
window.$ = _$;
}
// 传递deep = true还可以把 jQuery名字的使用仅也转移出去
if (deep && window.jQuery === jQuery) {
// 返回自己的:在外面基于一个别名接受,以后别名代表当前自己的
window.jQuery = _jQuery;
}
// 让现在全局下的$和jQuery都以最新自己导入的为主
return jQuery;
};
* 应用7:函数的防抖和节流
1、函数的防抖(防止老年帕金森)
对于频繁触发某个操作,我们只识别一次(只触发执行一次函数)
function debounce(func, wait = 300, immediate = false) {
let timer = null;
return function anonymous(...params) {
let now = immediate && !timer;
// 每次点击都把之前设置的定时器清除
clearTimeout(timer);
// 重新设置一个新的定时器监听wait时间内是否触发第二次
timer = setTimeout(() => {
// 手动让其回归到初始状态
timer = null;
// wait这么久的等待中,没有触发第二次
!immediate ? func.call(this, ...params) : null;
}, wait);
// 如果是立即执行
now ? func.call(this, ...params) : null;
};
}
参数
1)func[function]:最后要触发执行的函数;
2)wait[number]:“频繁”设定的界限;
3)immediate[boolean]:默认多次操作,我们识别的是最后一次,但是immediate=true,让其识别第一次;
主体思路:
在当前点击完成后,我们等wait这么长的时间,看是否还会触发第二次,如果没有触发第二次,属于非频繁操作,我们直接执行想要执行的函数func;如果触发了第二次,则以前的不算了,从当前这次再开始等待...
应用场景
1)keyup 事件;
2)调整窗口大小;
2、函数节流:
在一段频繁操作中,可以触发多次,但是触发的频率由自己指定。
function throttle(func, wait = 300) {
let timer = null,
previous = 0; // 记录上一次操作的时间
return function anonymous(...params) {
let now = new Date(),
remaining = wait - (now - previous); //记录还差多久达到我们一次触发的频率
if (remaining <= 0) {
// 两次操作的间隔时间已经超过wait了
window.clearTimeout(timer);
timer = null;
previous = now;
func.call(this, ...params);
} else if (!timer) {
// 两次操作的间隔时间还不符合触发的频率
timer = setTimeout(() => {
timer = null;
previous = new Date();
func.call(this, ...params);
}, remaining);
}
};
}
参数
func[function]:最后要触发执行的函数;
wait[number]:触发的频率;
应用场景
1)鼠标不断点击触发;
2)监听滚动事件;
* 简述你对闭包的理解,以及其优缺点?(面试常问)
1、为什么产生闭包
浏览器想要执行代码,都要进栈执行的。在进栈过程中, 由于某些内容被上下文以外的一些事物所占用,则当前上下文不能被出栈释放,根据浏览器的垃圾回收机制,如果被占用不释放,就会保留下来,这种机制就是闭包。
2、闭包的作用
1)保护:保护私有上下文中的“私有变量”和外界互不影响。
2)保存:上下文不被释放,那么上下文中的“私有变量”和“值”都会被保存起来,可以供其下级上下文中使用。
3、闭包弊端
如果大量使用闭包,会导致栈内存太大,页面渲染变慢,性能受到影响,所以真实项目中需要“合理应用闭包”;某些代码会导致栈溢出或者内存泄漏,这些操作都是需要我们注意的;
4、实战中的应用
1)循环事件绑定方法:可以用事件索引,后来修改是let的方式,都是闭包的方式。闭包的方式不仅 浪费了堆内存,也浪费了栈内存。这种方式不太好,所以用的事件委托。
2)惰性函数、柯里化函数、compose组合函数、函数的防抖和节流,jQuery源码也都用了闭包的思路。
3)自己封装组件,模块之间的独立性以及互通性。
总结:所以需要深入了解闭包,对项目有帮助的。
* 扩展:
一、GC:浏览器的垃圾回收机制(内存释放机制)
1、栈内存释放
1)加载页面,形成一个全局的上下文,只有页面关闭后,全局上下文才会被释放。
2)函数执行会形成一个私有的上下文,进栈执行;当函数中代码执行完成,大部分情况下,形成的上下文都会被出栈释放掉,以此优化栈内存大小;
2、堆内存释放
方案一:(例如谷歌):查找引用
浏览器在空闲或者指定时间内,查看所有的堆内存,把没有被任何东西占用的堆内存释放掉;但是占用着的是不被释放的;
方案二:(例如IE):引用计数
创建了堆内存,被占用一次,则浏览器计数+1,取消占用则计数 -1。当记录的数字为零的时候,则内存释放掉;某些情况会导致记数混乱出现“内存泄漏”的现象。
二、事件委托
让利用事件冒泡的原理,让自己的所触发的事件,让他的父元素代替执行。
e.target 事件目标(触发事件流的元素)
事件委托优点:可以为将来元素绑定事件;减少事件注册。