手写防抖,节流
防抖(debounce)
任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行,直白的讲就是:你尽管触发,我总是在你最后一次触发的时间之后的多少秒之后执行一次
防抖是一个高阶函数,使用了闭包
function debounce(fn, delay){
// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
// 定时器
let timer = null;
// 将debounce处理结果当作函数返回
return function(){
// 保留调用时的this上下文
let context = this;
// 保留调用时传入的参数
let args = arguments;
// 每次事件被触发时,都去清除之前的旧定时器
if(timer){
clearTimeout(timer)
}
// 设置新定时器
timer = setTimeout(function(){
fn.apply(context, args)
}, delay)
}
}
总的来说,防抖核心思想就是,传入你要执行的函数fn,以及延迟执行的时间delay,首先设置定时器timer,并返回一个新函数,而在新函数中需要保存调用时的上下文this和传入的参数,并在执行fn前将旧的定时器清除,然后重新设置新的定时器在delay之后执行fn并绑定this上下文。
节流(throttle)
当持续触发事件时,保证一定时间段内只调用一次事件处理函数。 节流是一个高阶函数,使用了闭包
fucntion throttle(fn, interval) {
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
// last 为上一次触发回调的时间
let last = 0;
// 将throttle处理结果当作函数返回
return function () {
// 保留调用时的this上下文
let context = this
// 保留调用时传入的参数
let args = arguments
// 记录本次触发回调的时间
let now = +new Date()
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (now - last >= interval) {
// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
last = now;
fn.apply(context, args);
}
}
}
节流函数 是一个高阶函数,使用闭包保存上一次触发回调的时间last,执行函数fn,时间阀值inerval,在要执行fn时,当前时间与上一次触发事件进行比较,如果时间间隔大于interval(now - last >= interval)执行函数fn.apply(context, args)
深度拷贝
function deepCopy(obj) {
if(typeof obj !== "object"){
return obj
}
const newObj = obj instanceof Array ? [] : {};
for(let key in obj){
if(obj.hasOwnProperty(key)){
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
}
}
return newObj
}
可以实现一个promise吗?
可以实现一个promise的ajax吗?
实现思路简单整理如下:
- 首先我们要知道发起一个网络请求应该是_ajax(url, params)这样子的,即必须有请求地址 请求参数(可选)
- 而promise是可以链式的调用then方法的,那么要想链式调用then方法_ajax的返回值必须是一个promise对象 此时我们大体的架子已经有了
function _ajax(url, params){
const promise = new Promise();
return promise;
}
- 接下来我们去丰富里边的内容:
- new 一个promise,接收一个函数作为参数
- 该函数接收两个参数,resolve和reject,它们是两个函数,由js引擎提供
function _ajax(url, params){
function fn(resolve, reject){
}
const promise = new Promise(fn);
return promise;
}
- 接着创建xhr对象 即new XMLHttpRequest()
- 然后绑定事件onreadystatechange 在xhr的readyState属性发生变化的时候做出处理
- 接着.open()打开连接,同时设置请求方式,请求地址,以及是异步还是同步请求
- 然后可以使用setRequestHeader(k, v)添加http请求头字段
- 然后.send(params)发送请求
function _ajax(url, params){
function fn(resolve, reject){
const xhr = new XMLHttpRequest();
const handleChange = function (){
}
xhr.onreadystatechange = handleChange;
xhr.responseType = "json";
xhr.setRequestHeader("Accept", 'application/json');
xhr.open('POST', url);
xhr.send(params);
}
const promise = new Promise(fn);
return promise;
}
- 总的基本完成了 那么再去详细的完善 事件处理函数onreadystatechange
- readyState状态为:
- 0: 尚未开始初始化,也没有调用open
- 1: 此时调用了open但没有调用send
- 2: 此时调用了send,但是服务器没有给出响应
- 3: 此时正在接收服务器响应,但还没有接收完毕,此处一般不做任何处理
- 4: 此时已经接收完了服务器的响应,可以对数据进行处理
- status状态码为200的时候进行处理
function _ajax(url, params){
params = JSON.stringify(params);
function fn(resolve, reject){
const xhr = new XMLHttpRequest();
const handleChange = function (){
if(this.status === 200 && this.readyState === 4){
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.onreadystatechange = handleChange;
xhr.responseType = "json";
xhr.setRequestHeader("Accept", 'application/json');
xhr.open('POST', url);
xhr.send(params);
}
const promise = new Promise(fn);
return promise;
}
- 至此,我们基于promise的ajax已经完成了
- 完美!
手写一个call???
首先得抓住几个点:即实现call的核心思想
- 将函数设为对象的属性
- 执行&删除这个函数
- 指定this到函数并传入给定参数执行函数
- 如果不传入参数,默认指向为 window
那么按照此套路,我们来实现如下:
// 示例:bar.say._call(foo, 'aaa');辅助理解下方的话
Function.prototype._call = function(context=window){
// 传入的context即是foo,
// 又因为this 总是指向调用它的那个对象,那么调用_call方法时的this就是bar.say,即有了下一行的代码
content.fn = this;
let args = [...arguments].slice(1);
// 执行fn方法
let result = content.fn(...args);
// 删除 fn
delete content.fn
return result
}
手写一个bind ???
中心思想是,并不立即执行, 而是返回一个函数
Function.prototype.myBind = function(context) {
//返回一个绑定this的函数,我们需要在此保存this
let self = this;
// 可以支持柯里化传参,保存参数
let args = [...arguments].slice(1);
return function(){
// 同样因为支持柯里化形式传参我们需要再次获取存储参数
let newArgs = [...arguments];
// 返回函数绑定this,传入两次保存的参数
//考虑返回函数有返回值做了return
return self.apply(context, args.concat(newArgs));
}
}
手写一个数组map方法
首先得知道map方法,接收一个回调函数,该回调函数有三个参数:currentValue(当前元素的值),index(当前元素下标),arr(当前元素所属的数组对象) 且会返回一个新数组。
Array.prototype.__map = function(callback) {
var arr = []
for (var i = 0; i < this.length; i++) {
arr.push(callback.call(this, this[i], i, this))
}
return arr
}
var res = [1, 2, 3].__map(item => {
return item * 2
})
console.log('res: ', res);// res: [ 2, 4, 6 ]
实现一个new
首先得知道new做了三件事:
- 创建了一个新对象并返回
- 将新对象的__proto__属性指向了构造函数的原型对象
- 执行了构造函数中的代码(为新对象添加属性)
function _new(fn, ...arg) {
// 创建新对象将其原型对象指向构造函数的原型对象
const obj = Object.create(fn.prototype);
// 将this指向新创建的对象并指向该构造函数
const ret = fn.apply(obj, arg);
// 如果构造函数的执行结果是Object类型则返回执行结果,否则返回新创建的对象
return ret instanceof Object ? ret : obj;
}
只要你明白了这个new的实际底层执行过程,那么类似于下面的这种类型的面试题百分百不会出错:
栗子:
function Constructor() {
this.a = "123";
// 第一种情况
return false
//第二种情况
return [] 或者 return {} 或者 return () => {}
// 第三种情况
没有return
}
var obj = new Constructor();