共计 9177 个字符,预计需要花费 23 分钟才能阅读完成。
0 / 闭包作用域练习题
闭包 的作用:① 爱护 ② 保留
let x = 5; | |
function fn(x){return function (y) {console.log(y + (++x)); | |
} | |
} | |
let f = fn(6); | |
f(7); | |
fn(8)(9); | |
f(10); | |
console.log(x); |
△ 后果是?
(1)++i
和 i++
的区别
let i = 1; | |
i++; | |
console.log(i); | |
i=1; | |
i+=1; // i=i+1; | |
console.log(i); |
△ 后果是?
let i = '1'; | |
i++; | |
console.log(i); | |
i='1'; | |
i+=1; // i=i+1; | |
console.log(i); |
△ 后果是?
i++/++i
肯定是数学运算,+N
也是把 N 变为数字类型的值
++i
和 i++
的区别:
[相同点]:都是本身根底上累加 1
[不同点]:计算和累加的程序
++i
本身先累加 1,再依据累加后的后果进行运算
i++
先依据原始值进行运算,运算完之后再本身累加 1
let i = 1; | |
console.log(5 + i++); | |
//=> 或者 5+(i++) | |
// ① 先算 5+i | |
// ② 再 i++: i=2 | |
console.log(i); //=> 2 | |
i = 1; | |
console.log(5 + (++i)); | |
// ① 先算 ++i : i=2 | |
// ② 5+i = 7 |
△ 区别
let i = 2; | |
console.log(2 + (++i) - (i++) + 3 - (i--) + (i--)); | |
console.log(i); |
△ 本人算吧
(2)图解
△ 图 1.1_留神:函数创立和函数执行
fn(8) ——>0x000001(8)
执行完后,返回值 0x100002
紧接着会被执行,那么 EC(FN2)
这个公有的执行上下文长期不被开释,当它用完当前才会被开释掉
△ 图 1.2_留神:函数创立和函数执行
GC:浏览器的垃圾回收机制,<u> 内存治理 </u>
①【谷歌】:查找援用
浏览器的渲染引擎会在闲暇的时候(定期一个工夫),顺次遍历所有的内存:栈、堆
[堆内存]:以后堆内存如果被占用了(指针关联地址了)则不能开释掉;如果没有任何的事物占用这个堆,则浏览器会主动把这个堆内存开释掉
[栈内存]:以后执行上下文中是否有内容(个别是堆内存)被此上下文以外的事物所占用,如果被占用则无奈开释(闭包),如果没有被占用则开释掉。其中,EC(G) 是在加载页面时候创立,只有在敞开页面时候才会开释掉
②【IE】:援用计数
每一个内存中都有一个数字 N,记录被占用的次数
如果以后内存被占用一次,则内存中的 N 会累加一次,反之勾销占用,N 会累减:直到 N 变为 0,则开释内存
此计划,常常会导致内存透露
【思考题】总结内存透露的状况
③ 手动优化
null
是不占用空间的,把占用的事物手动赋值为 null,能够实现内存的手动优化
fn = null
开释 0x000001
f = null
开释 0x100001 后,EC(FN1) 也会被开释掉
(3)重构函数
let a=0, | |
b=0; | |
function A(a){A=function(b){alert(a+b++); | |
}; | |
alert(a++); | |
} | |
A(1); | |
A(2); |
△ 函数重构
let a = 1, | |
b = 2; | |
// 等价于 | |
let a = 1; | |
let b = 2; | |
//============== | |
let a = b =1; | |
// 等价于 | |
b = 1; | |
let a = 1; |
△ 赋值
△ 图 1.3_函数重构图解
1 / this 的 5 种根底状况
this
是函数的执行主体,不等价于执行上下文 / 作用域。
this
就是谁把函数执行了
分分明函数执行主体 THIS,依照 5 点法则来说:
1、事件绑定
2、一般函数执行
3、构造函数执行
4、箭头函数执行
5、基于 call/apply/bind 强制扭转 this
此处先说前两点,前面三点与面向对象相干的
在浏览器中运行 JS 代码,非函数中的 THIS 个别都是 window
"use strict;" | |
console.log(this); //=> window |
△ 非函数中的 this
咱们钻研 this 个别都是函数中的 this
还有个非凡状况:ES6+ 中“块级上下文”中的 this,是其所在执行上下文中的 this【了解为:块级上下文中没有本人的 this】
(1)事件绑定
document.body.onclick = function (){console.log(this); | |
}; | |
document.body.addEventListener('click', function (){console.log(this); | |
}); |
△ 事件绑定
1、不论是 DOM0 还是 DOM2 级事件绑定,给元素 E 的某个事件行为绑定办法,当事件触发办法执行时,办法中的 this 是以后元素 E 自身
2、非凡状况:
(1)IE6~8 中,基于attachEvent
实现 DOM2 事件绑定,事件触发执行,办法中的 this 不是元素自身,大部分状况都是 window
(2)基于 call/apply/bind
强制扭转了函数中的 this,咱们也是以强制扭转为主
(2)一般函数执行
① 一般函数
函数执行,看函数后面是否有“点”:
1、有“点”,“点”后面是谁 this 就是谁
2、没有“点”,this 是 window。JS 严格模式下是:undefined
function fn(){console.log(this); | |
} | |
var obj = { | |
name:'晚霞的光影笔记', | |
id:'zhaoxiajingjing', | |
fn:fn | |
}; | |
fn(); //=> 没有点:this=>window | |
obj.fn(); //=> 有点:this=>obj |
△ 一般函数的 this
xxx.__proto__.fn()
:办法执行,后面有“点”,this 是:xxx.__proto__
② 自执行函数
自执行函数执行,外面的 this 个别是 window 非严格模式 /undefined 严格模式
(function (){console.log(this); | |
})(); |
△ 自执行函数
var obj = {num:(function (){ | |
//=> 把自执行函数执行的返回值,赋值给 obj.num | |
//=> 这外面的 this 个别是 window/undefined | |
return 10; | |
})()}; |
△ 自执行函数
③ 回调函数
回调函数:把一个函数作为值,传递给另一个函数,在另一个函数中把传过来的函数调用
回调函数执行中的 this 个别也是 window/undefined,除非做过非凡解决
setTimeout(function (){console.log(this); //=> 个别是 window/undefined | |
}, 1000); | |
[10].forEach(function (){console.log(this); //=> 个别是 window/undefined | |
}); | |
let obj = {id:'zhaoxiajingjing'}; | |
[10].forEach(function (){console.log(this); //=> obj | |
}, obj); //=> 指定回调函数中的 this |
△ 回调函数
④ 括号表达式中的 this 很变态
function fn(){console.log(this); | |
} | |
var obj = { | |
name:'晚霞的光影笔记', | |
id:'zhaoxiajingjing', | |
fn:fn | |
}; | |
fn(); //=> 没有点:this=>window | |
obj.fn(); //=> 有点:this=>obj | |
(obj.fn)(); //=> this:obj 小括号中只有一项,不算是括号表达式 | |
(fn, 10, obj.fn); //=> 括号表达式:有多项只取最初一项 | |
(fn, 10, obj.fn)(); //=>this:window |
△ 括号表达式
括号表达式中:小括号中有多项,只取最初一项,如果把其执行,不管之前 this 是谁,当初根本都会变为 window
(3)题目
var x = 3, | |
obj = {x: 5}; | |
obj.fn = (function () { | |
this.x *= ++x; | |
return function (y) {this.x *= (++x)+y; | |
console.log(x); | |
} | |
})(); | |
var fn = obj.fn; | |
obj.fn(6); | |
fn(4); | |
console.log(obj.x, x); |
△ 找 THIS
a+=b-c
=> a = a+(b-c)
a*=b-c
=> a = a* (b-c)
△ 图 1.4_图解
△ 图 1.5_计算过程
2 / JS 高阶编程技巧
JS 高阶编程技巧:利用闭包的机制,实现进去的一些高阶编程的形式
1、模块化思维
2、惰性函数
3、柯理化函数
4、compose 组合函数
5、高阶组件 React 中的
6、……
(1)模块化思维
单例 -> AMD(require.js)->CMD(sea.js)-> CommonJS(Node)->ES6Module
// 实现天气板块 | |
var time = '2020-11-01'; | |
function queryData(){// ...CODE} | |
function changeCity(){// ...CODE} | |
// 实现征询板块 | |
var time = '2020-11-1'; | |
function changeCity(){// ...CODE} |
△ 很久以前的没有模块化思维之前
在没有模块化思维之前,团队合作开发或者代码量较多的状况下,会导致全局变量净化【变量抵触】
团队之前开发,合并到一起的代码,变量命名抵触了,让谁改都不适合,那怎么办呢?
① 闭包
闭包:爱护
临时基于闭包的“爱护作用”避免全局变量净化
然而,每个版块的代码都是公有的,无奈互相调用
// 实现天气板块 | |
(function fn() { | |
var time = '2020-11-01'; | |
function queryData() {} | |
function changeCity() {} | |
})(); | |
(function fn() { | |
// 实现征询板块 | |
var time = '2020-11-1'; | |
function changeCity() {} | |
})(); |
△ 基于闭包的爱护作用
② 某一种计划
把须要供他人调用的 API 办法,挂在到全局上
然而也不能写太多,还是会引起全局变量净化
// 实现天气板块 | |
(function fn() { | |
var time = '2020-11-01'; | |
function queryData() {} | |
function changeCity() {} | |
window.queryData=queryData; | |
window.changeCity=changeCity; | |
})(); | |
(function fn() { | |
// 实现征询板块 | |
var time = '2020-11-1'; | |
function changeCity() {} | |
window.changeCity=changeCity; | |
})(); |
△ 挂在 window 上
③ 再一种计划
对象的特点:每一个对象都是一个独自的堆内存空间每一个对象也是独自的一个实例:Object 的实例,这样即便多个对象中的成员名字雷同,也互不影响
仿照其余后盾语言,obj1/obj2
不仅仅是对象名,更被称为【<u> 命名空间 </u>】给堆内存空间起个名字
let obj1 = { | |
name:'晚霞的光影笔记', | |
id:'zhaoxiajingjing', | |
show:function(){} | |
}; | |
let obj2 = { | |
name:'zxjj', | |
show:function (){} | |
}; |
△ 对象
每个对象都是一个独自的实例,用来治理本人的公有信息,即便名字雷同,也互不影响:【<u>JS 中的单例设计模式 </u>】
④ 进阶一下
实现公有性和互相调用
// 实现天气板块 | |
var weatherModule = (function fn() { | |
var time = '2020-11-01'; | |
function queryData() {} | |
function changeCity() {} | |
return { | |
queryData:queryData, | |
changeCity:changeCity | |
}; | |
})(); | |
var infoModule = (function fn() { | |
// 实现征询板块 | |
var time = '2020-11-1'; | |
function changeCity() {} | |
// 能够调用其余模块的办法 | |
weatherModule.queryData(); | |
return {changeCity:changeCity} | |
})(); |
△ 单例模式
(2)惰性函数
惰性函数,肯定要抓住精华:惰性 => 懒
window.getComputedStyle(document.body)
获取以后元素通过浏览器计算的款式,返回款式对象
在 IE6~8 中,不兼容这个写法,须要应用 元素.currentStyle
来获取
① 一开始这样写
function getCss(element, attr){if('getComputedStyle' in window){return window.getComputedStyle(element)[attr]; | |
} | |
return element.currentStyle[attr]; | |
} | |
var body = document.body; | |
console.log(getCss(body, 'height')); | |
console.log(getCss(body, 'margin')); | |
console.log(getCss(body, 'backgroundColor')); |
△ 获取款式
当浏览器关上后,在第一次调用 getCss
办法时,就检测了兼容性了,那么,在第二次、第三次调用时是不是就没必要再去检测了
【优化思维】:第一次执行 getCss
咱们就晓得是否兼容了,第二次及当前再次调用 getCss
时,则不想再解决兼容的容错解决了,这就是“<u> 惰性思维 </u>”【就是“懒”,干一次能够搞定的,相对不去做第二次了】
② 优化一下
也能实现,但不是谨严意义上的惰性思维
var flag = 'getComputedStyle' in window; | |
function getCss(element, attr){if(flag){return window.getComputedStyle(element)[attr]; | |
} | |
return element.currentStyle[attr]; | |
} | |
var body = document.body; | |
console.log(getCss(body, 'height')); | |
console.log(getCss(body, 'margin')); | |
console.log(getCss(body, 'backgroundColor')); |
△ 优化一下
③ 惰性思维
惰性是啥?就是 懒
懒是啥?能坐着不站着,能躺着不坐着,能少干活就少干活
function getCss(element, attr){ | |
//=> 第一次执行,依据是否兼容,实现函数的重构 | |
if('getComputedStyle' in window){getCss = function (element, attr){return window.getComputedStyle(element)[attr]; | |
}; | |
} else {getCss = function (element, attr){return element.currentStyle[attr]; | |
}; | |
} | |
// 为了保障第一次 也能够获取信息,须要把重构后的函数执行一次 | |
return getCss(element, attr); | |
} | |
var body = document.body; | |
console.log(getCss(body, 'height')); | |
console.log(getCss(body, 'margin')); | |
console.log(getCss(body, 'backgroundColor')); |
△ 惰性函数 + 重构函数
(3)柯理化函数
函数柯理化:事后解决思维
造成一个不被开释的 闭包 ,把一些信息 存储 起来,当前基于 作用域链 拜访到当时存储的信息,而后进行相干解决。所有合乎这种模式的(闭包利用)都称为 <u> 柯理化函数 </u>
//=> x 是事后存储的值 | |
function curring(x){} | |
var sum = curring(10); | |
console.log(sum(20)); //=> 10+20 | |
console.log(sum(20,30)); //=> 10+20+30 |
△ 请实现柯理化函数
① 把类数组转换为数组:
let arg = {0:10, 1:20, length:2}; | |
let arr = [...arg]; | |
arr = Array.from(arg); | |
arr = [].slice.call(arg); |
△ 类数组转为数组
② 数组求和
数组求和
1、for 循环 /forEach
2、eval([10,20,30].join('+'))
3、[10,20,30].reduce()
命令式编程:[关注怎么做] 本人编写代码,管控运行的步骤和逻辑【本人灵便掌控执行步骤】
函数式编程:[关注后果] 具体实现的步骤曾经被封装称为办法,咱们只须要调用办法即可,无需关注怎么实现的【益处:使用方便,代码量减少。弊病:本人无奈灵便掌控执行步骤】
③ 数组的 reduce 办法
API:https://developer.mozilla.org…
arr.reduce(callback()[, initialValue])
callback(accumulator, currentValue[, index[, array]])
let arr = [10,20,30,40]; | |
let res = arr.reduce(function (result, item, index){console.log(result, item, index); | |
}); |
△ reduce
△ 图 1.6_reduce 执行
1、initialValue 初始值不传递,result 默认初始值是数组的第一项,reduce 是从数字第二项开始遍历的
2、每遍历数组中的一项,回调函数被触发执行一次
① result 存储的是上一次回调函数返回的后果。除了第一次是初始值或者数字第一项
② item 以后遍历这一项
③ index 以后遍历这一项的索引
let arr = [10,20,30,40]; | |
let res = arr.reduce(function (result, item, index){console.log(result, item, index); | |
return item + result; | |
}); | |
console.log(res); //=> 100 |
△ arr.reduce
数组的 reduce 办法:在遍历数组过程中,能够累积上一次解决的后果,基于上次解决的后果持续遍历解决
let arr = [10,20,30,40]; | |
let res = arr.reduce(function (result, item, index){console.log(result, item, index); | |
}, 0 ); |
△ reduce 传递初始值了
arr.reduce 传递了 initialValue 了,则 result 的第一次后果就是初始值,item 从数组第一项开始遍历
△ 图 1.7_reduce 执行
本人实现 reduce
Array.prototype.reduce = function reduce(callback, initialValue){ | |
let self = this, | |
i = 0; //=> THIS:arr | |
if(typeof callback !== 'function') throw new TypeError('callback must be a function'); | |
if(typeof initialValue === "undefined"){initialValue = self[0]; | |
i = 1; | |
} | |
// 迭代数组每一项 | |
for(; i < self.length; i++){var item = self[i], | |
index = i; | |
initialValue = callback(initialValue, item, index, self); | |
} | |
return initialValue; | |
}; |
△ 本人手写 reduce
④ 柯理化函数
function curring(x){return (...args)=>{ | |
//=> 把事后存储的 x,放到数组的结尾 | |
args.unshift(x); | |
return args.reduce((res,item)=>(res+item), 0); | |
}; | |
} | |
var sum = curring(10); | |
console.log(sum(20)); //=> 10+20 | |
console.log(sum(20,30)); //=> 10+20+30 |
△ 柯理化函数
(4)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 函数的编写
② 答题
function compose(...funcs){return function(x){ | |
let result, | |
len=funcs.length; | |
if(len===0){ | |
// 一个函数传递,那就把参数间接返回 | |
result=x; | |
}else if(len===1){ | |
// 只传递了一个函数 | |
result=funcs[0](x); | |
}else{ | |
// funcs 参数执行程序从右到左 | |
result=funcs.reduceRight((result, item) => {if(typeof item !== 'function') return result; | |
return item(result); | |
},x); | |
} | |
return result; | |
} | |
} |
△ 实现 compose 组合函数
3 / 思考题
1、内存透露
2、严格模式和非严格模式区别 API:https://developer.mozilla.org…
3、剖析 redux 中的 compose 办法的实现逻辑
function compose(...funcs) {if (funcs.length === 0) {return arg => arg} | |
if (funcs.length === 1) {return funcs[0] | |
} | |
return funcs.reduce((a, b) => (...args) => a(b(...args))) | |
} |
△ redux 中的 compose 实现
– end –