Call & Apply & Bind实现

call && apply && bind 的作用 call、apply、bind的作用都是改变函数的 this 指向 call:call 的第一个参数就是对函数绑定的 this,为一个对象,后面的参数是函数调用时所需要的参数 apply:apply 只接受两个参数,第一个与 call 相同,第二个参数是为数组,内容是函数调用所需要的参数 bind:bind 与 call作用相同,不过 bind 会返回一个新的函数,第一个参数之后的参数将绑定原函数的参数。 实现 call 先来看看原生call的使用方法 let obj = { a: 1 } function fn(){ console.log(this.a) } fn() // undefined fn.call(obj) // 1 call 是如何将 this 绑定到 obj 上的呢? 我们换一种思路,先看下面的代码 let obj = { a: 1, fn(){ console.log(this.a) } } obj.fn() // 1 这段代码大家都能理解对吧,接下来,让我们打开脑洞。 如果 call 只是将调用函数绑定到了第一个参数身上呢?似乎一切就说得通了 function fn(){ console.log(this.a) } Function.prototype.myCall = function(context){ context = context || window //首先判断是否传入第一个参数,如果没传,那么 this 就默认绑定到 window context.callFn = this let result = context.callFn() delete context.callFn return result } let obj = { a: 1 } fn.myCall(obj) // 1 ok,初步绑定 this 的工作我们已经完成,接下来思考如何将剩下的参数传给调用函数。 ...

September 24, 2019 · 2 min · Zink

Input中如何处理中文输入防抖

防抖 函数防抖和节流,都是控制事件触发频率的方法。具体实现可以看我的这篇文章 input 如何处理中文输入 如果只是单纯的使用 input 事件来监听 input 值的改变,那么每次敲击键盘,输入一个字母都会触发 input 事件,其实我们想要的是获取用户输入的中文再进行后续操作。 我们可以假象一下,输入中文时的字母都是预输入,只有当选择候选词之后,input得到的值才是我们想要的。 现在,向大家介绍两个新的 DOM 事件:compositionstart、compositionend compositionstart:compositionstart 事件触发于一段文字的输入之前 compositionend:当文本段落的组成完成或取消时, compositionend 事件将被触发 <input type="text"> let inputElement = document.querySelector('input') let canRun = true inputElement.addEventListener('compositionstart', function(){ console.log('start') canRun = false }) inputElement.addEventListener('compositionend', function(){ console.log('end') canRun = true }) inputElement.addEventListener('input', debounce(fn)) function debounce(fn) { let timer = null; return function() { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, arguments) }, 500) }; } function fn(e){ if(canRun){ console.log(e.target.value) } } 当我们切换到中文输入法输入时开始在 input 中输入时,compositionstart 的回调函数被调用,将 canRun 状态切换为 false,半秒后执行 fn 时,canRun 状态为 false,不进行任何操作,当中文输入结束后,compositionend 的回调函数被调用,将 canRun 变为 true,此时 input 的回调函数调用,就可以执行后续的操作了,其实就是利用了 compositionstart 和 compositionend 来切换状态机的状态 ...

September 23, 2019 · 1 min · Zink

这一次,我一定会搞定 This !

迫于 迫于在写 React 事件处理中的 this 时,写到 this 突然卡壳了,一顿冷静分析之后发现自己对于 this 的理解又忘了一些,于是痛定思痛,重新读了 《You-Dont-Know-JS》中关于 this 的文章。 函数的执行环境 首先上一段代码 function fn(){ console.log(this.a) } let obj = { a: 1, fn: fn } var a = 'window' fn() //window obj.fn() //1 首先我们要明确一点 ,this 不是编写代码时就绑定的,而是运行时绑定的。 比如 fn() 在调用时,所处的环境是 global,所以他的 this 就是 window 而 obj.fn() 在调用时,所处的环境是在对象 obj 内,所以他的 this 指向 obj 对象 当一个函数被调用时,会建立一个称为执行环境的活动记录。这个记录包含函数是从何处(调用栈 —— call-stack)被调用的,函数是 如何 被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的 this 引用。 所以,this是根据调用点(函数如何被调用)来为每次函数调用建立的绑定 调用点的规则 默认绑定: 首先是独立函数调用。在这种倾向下,this 规则是在没有其他规则适用时的默认规则。 function fn(){ console.log(this) } fn() //window fn() 被独立调用,此时的 this 是默认绑定,所以 this 指向了 window 隐含绑定 这种规则是:调用点是否拥有一个对象环境。 ...

September 16, 2019 · 2 min · Zink

防抖和节流

什么是防抖?如何实现? 动作绑定事件,动作发生后一定时间后触发事件,在这段时间内,如果该动作又发生,则重新等待一定时间再触发事件。 实现:每次触发事件时都取消之前的延时调用方法。 function debounce(fn) { let timer = null; return function() { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, arguments) }, 500) }; } 什么是节流?如何实现? 动作发生后一段时间后触发事件,在这段时间内,如果动作又发生,则无视该动作,直到事件执行完后,才能重新触发。 实现:每次触发事件时都判断当前是否有等待执行的延时函数,如果有,则无视该次动作。 function throttle(fn) { let canRun = true; return function() { if (!canRun) return; canRun = false; setTimeout(() => { fn.apply(this, arguments); canRun = true; }, 500); }; } 区别 防抖和节流的区别在于: 防抖的思路是:指定事件触发的函数在一定时间后执行,如果在这段时间内再次触发该事件,那么便取消上一次的执行。 节流的思路是:指定事件触发的函数在一定时间后执行,如果在这段时间内再次触发该事件,那么便无视该次事件动作。

August 6, 2019 · 1 min · Zink

JavaScript的深浅拷贝

JavaScript的数据类型 JS中有7种数据类型:String、Number、Boolean、Null、Undefined、对象以及ES6新增的Symbol。 由于目前还未深入了解过Symbol,所以此次不讨论Symbol。 其中6种为基本类型(或叫做简单数据类型)和1种引用类型(复杂数据类型)。 基本类型 String Number Boolean Null Undefined 引用类型 对象 不同数据类型在内存中如何存储? 我们将内存分为栈(Stack)内存和堆(Heap)内存 基本类型以固定大小的空间存在与栈内存中,当基本类型发生值的拷贝时,目标变量会得到原始变量的值的副本。 let a = 1 let b = a //1 引用类型的值是对象,保存在堆内存中,引用类型的变量的值并不是值的本身,而是这个值在堆内存中的地址(也有说法为指针,不过我个人觉得地址容易理解)。当引用类型发生值的拷贝的时候,实际上目标变量得到的是原始变量的值的地址。 let obj1 = { name: "jack" } /* 此时{name: "jack"}这个对象存在于堆内存中,我们假设他的地址是218 */ let obj2 = obj1 //在这个赋值操作中,传递的其实是对象在内存中的地址 浅拷贝 let data = { name: "jack", age: 18, gender: "male" } let copyDate = data copyDate === data //true copyDate.name = "Tom" copyDate.name === data.name //true copyDate === data //true 上面这段代码就是一个浅拷贝的例子,由此可见,浅拷贝就是目标变量得到原始变量的值后, 不管是目标变量还是原是变量,只要值发生变化,那么两个变量都会收到影响。这一点从内存图中就可以看出,因为引用地址相同。 深拷贝 let a = 1 let b = a b = 2 a //1 变量b得到变量a的值,但变量b的值改变,不影响变量a,这就是深拷贝 存在的问题 & 解决方法 有些时候,我们需要深拷贝一个对象或者数组,但=操作符只能实现浅拷贝 对象的深拷贝 //最容易想到的方法:遍历原始对象 let obj = { name: "jack" } let copyObj = {} for(let key in obj){ copyObj[key] = obj[key] } obj === copyObj //false //使用JSON let a = {name: "alex"} let b = JSON.parse(JSON.stringify(a)) //ES6的方法 let source = { name: "Tom", age: 18 } let target = Object.assign({}, source) //Object.assign方法用于将原始对象的所有可枚举属性复制到目标对象上。 target === source //false 数组的深拷贝 ...

July 24, 2019 · 1 min · Zink