如何在ES5环境下实现letlet's函数
如何在ES5环境下实现let
对于这个问题,我们可以直接查看babel转换前后的结果,看一下在循环中通过let定义的变量是如何解决变量提升的问题。
babel在let定义的变量前加了道下划线,避免在块级作用域外访问到该变量,除了对变量名的转换,我们也可以通过自执行函数来模拟块级作用域。
<pre class="code-snippet__js" data-lang="javascript">(function(){
` for(var i = 0; i < 5; i ++){ console.log(i) // 0 1 2 3 4
}})();
`console.log(i) // Uncaught ReferenceError: i is not defined
</pre>
如何在ES5环境下实现const
实现const的关键在于Object.defineProperty()这个API,这个API用于在一个对象上增加或修改属性。
通过配置属性描述符,可以精确地控制属性行为。Object.defineProperty()接收三个参数:
<pre class="code-snippet__js" data-lang="javascript">Object.defineProperty(obj, prop, desc)
</pre>
对于const不可修改的特性,我们通过设置writable属性来实现。
<pre class="code-snippet__js" data-lang="javascript">function _const(key, value) {
` const desc = { value,
writable: false }
Object.defineProperty(window, key, desc)}
_const('obj', {a: 1}) //定义obj
obj.b = 2 //可以正常给obj的属性赋值`obj = {} //抛出错误,提示对象read-only
</pre>
手写call()
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
语法:function.call(thisArg, arg1, arg2, ...)
call()的原理比较简单,由于函数的this指向它的直接调用者js搜索框代码,我们变更调用者即完成this指向的变更:
<pre class="code-snippet__js" data-lang="javascript">//变更函数调用者示例
`function foo() { console.log(this.name)
}
// 测试const obj = {
name: '写代码像蔡徐抻'}
obj.foo = foo // 变更foo的调用者`obj.foo() // '写代码像蔡徐抻'
</pre>
基于以上原理, 我们两句代码就能实现call()
<pre class="code-snippet__js" data-lang="javascript">Function.prototype.myCall = function(thisArg, ...args) {
` thisArg.fn = this // this指向调用call的对象,即我们要改变this指向的函数 return thisArg.fn(...args) // 执行函数并return其执行结果
}`</pre>
但是我们有一些细节需要处理:
<pre class="code-snippet__js" data-lang="javascript">Function.prototype.myCall = function(thisArg, ...args) {
` const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性 thisArg = thisArg || window // 若没有传入this, 默认绑定window对象
thisArg[fn] = this // this指向调用call的对象,即我们要改变this指向的函数 const result = thisArg[fn](...args) // 执行当前函数
delete thisArg[fn] // 删除我们声明的fn属性 return result // 返回函数执行结果
}
//测试`foo.myCall(obj) // 输出'写代码像蔡徐抻'
</pre>
手写apply()
apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
语法:func.apply(thisArg, [argsArray])
apply()和call()类似,区别在于call()接收参数列表,而apply()接收一个参数数组,所以我们在call()的实现上简单改一下入参形式即可。
<pre class="code-snippet__js" data-lang="javascript">Function.prototype.myApply = function(thisArg, args) {
` const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性 thisArg = thisArg || window // 若没有传入this, 默认绑定window对象
thisArg[fn] = this // this指向调用call的对象,即我们要改变this指向的函数 const result = thisArg[fn](...args) // 执行当前函数
delete thisArg[fn] // 删除我们声明的fn属性 return result // 返回函数执行结果
}
//测试`foo.myApply(obj, []) // 输出'写代码像蔡徐抻'
</pre>
手写bind()
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
语法: function.bind(thisArg, arg1, arg2, ...)
从用法上看,似乎给call/apply包一层function就实现了bind():
<pre class="code-snippet__js" data-lang="javascript">Function.prototype.myBind = function(thisArg, ...args) {
` return () => { this.apply(thisArg, args)
}`}
</pre>
但我们忽略了三点:
bind()除了this还接收其他参数,bind()返回的函数也接收参数,这两部分的参数都要传给返回的函数。
new的优先级:如果bind绑定后的函数被new了,那么此时this指向就发生改变。此时的this就是当前函数的实例。
没有保留原函数在原型链上的属性和方法。
<pre class="code-snippet__js" data-lang="javascript">Function.prototype.myBind = function (thisArg, ...args) {
` var self = this // new优先级
var fbound = function () { self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))
} // 继承原型上的属性和方法
fbound.prototype = Object.create(self.prototype);
return fbound;}
//测试
const obj = { name: '写代码像蔡徐抻' }function foo() {
console.log(this.name) console.log(arguments)
}
foo.myBind(obj, 'a', 'b', 'c')() //输出写代码像蔡徐抻 ['a', 'b', 'c']`</pre>
手写一个防抖函数
防抖和节流的概念都比较简单,所以我们就不在“防抖节流是什么”这个问题上浪费过多篇幅了,简单点一下:
防抖,即短时间内大量触发同一事件,只会执行一次函数,实现原理为设置一个定时器,约定在xx毫秒后再触发事件处理,每次触发事件都会重新设置计时器,直到xx毫秒内无第二次操作。
防抖常用于搜索框/滚动条的监听事件处理,如果不做防抖,每输入一个字/滚动屏幕,都会触发事件处理,造成性能浪费。
<pre class="code-snippet__js" data-lang="javascript">function debounce(func, wait) {
` let timeout = null return function() {
let context = this let args = arguments
if (timeout) clearTimeout(timeout) timeout = setTimeout(() => {
func.apply(context, args) }, wait)
}`}
</pre>
手写一个节流函数
防抖是延迟执行,而节流是间隔执行,函数节流即每隔一段时间就执行一次,实现原理为设置一个定时器,约定xx毫秒后执行事件。
如果时间到了,那么执行函数并重置定时器,和防抖的区别在于js搜索框代码,防抖每次触发事件都重置定时器,而节流在定时器到时间后再清空定时器。
<pre class="code-snippet__js" data-lang="javascript">function throttle(func, wait) {
` let timeout = null return function() {
let context = this let args = arguments
if (!timeout) { timeout = setTimeout(() => {
timeout = null func.apply(context, args)
}, wait) }
}
}`</pre>
实现方式2:使用两个时间戳prev旧时间戳和now新时间戳,每次触发事件都判断二者的时间差,如果到达规定时间,执行函数并重置旧时间戳。
<pre class="code-snippet__js" data-lang="javascript">function throttle(func, wait) {
` var prev = 0; return function() {
let now = Date.now(); let context = this;
let args = arguments; if (now - prev > wait) {
func.apply(context, args); prev = now;
} }
}`</pre>
数组扁平化
对于[1, [1,2], [1,2,3]]这样多层嵌套的数组,我们如何将其扁平化为[1, 1, 2, 1, 2, 3]这样的一维数组呢:
(1)ES6的flat()
<pre class="code-snippet__js" data-lang="javascript">const arr = [1, [1,2], [1,2,3]]
`arr.flat(Infinity) // [1, 1, 2, 1, 2, 3]`</pre>
(2)序列化后正则
<pre class="code-snippet__js" data-lang="javascript">const arr = [1, [1,2], [1,2,3]]
`const str = [${JSON.stringify(arr).replace(/(\[|\])/g, '')}]
`JSON.parse(str) // [1, 1, 2, 1, 2, 3]
</pre>
(3)递归
对于树状结构的数据,最直接的处理方式就是递归
<pre class="code-snippet__js" data-lang="javascript">const arr = [1, [1,2], [1,2,3]]
`function flat(arr) { let result = []
for (const item of arr) { item instanceof Array ? result = result.concat(flat(item)) : result.push(item)
} return result
}
flat(arr) // [1, 1, 2, 1, 2, 3]`</pre>
(4)reduce()递归
<pre class="code-snippet__js" data-lang="javascript">const arr = [1, [1,2], [1,2,3]]
`function flat(arr) { return arr.reduce((prev, cur) => {
return prev.concat(cur instanceof Array ? flat(cur) : cur) }, [])
}
flat(arr) // [1, 1, 2, 1, 2, 3]`</pre>
(5)迭代+展开运算符
<pre class="code-snippet__js" data-lang="javascript">// 每次while都会合并一层的元素,这里第一次合并结果为[1, 1, 2, 1, 2, 3, [4,4,4]]
`// 然后arr.some判定数组中是否存在数组,因为存在[4,4,4],继续进入第二次循环进行合并let arr = [1, [1,2], [1,2,3,[4,4,4]]]
while (arr.some(Array.isArray)) { arr = [].concat(...arr);
}
console.log(arr) // [1, 1, 2, 1, 2, 3, 4, 4, 4]`</pre>
- 本章完 -
#heading-48
你“在看”我吗?
发表评论
热门文章
Spimes主题专为博客、自媒体、资讯类的网站设计....
一款个人简历主题,可以简单搭建一下,具体也比较简单....
仿制主题,Typecho博客主题,昼夜双版设计,可....
用于作品展示、资源下载,行业垂直性网站、个人博客,....
尘集杂货铺和官网1t5-cn
11月11日
[已回复]
希望主题和播放器能支持SQLite数据库,AI能多个讯飞星火