JavaScript进阶必会的手写功能

1,922 阅读6分钟

JavaScript进阶的必要性

无论是学习react还是vue,它们都是js的应用框架。剥去他们的壳子看到的始终是js,所以作为一个前端大厨必须要熟练掌握好js这个大勺,才能烧出一顿好菜

无论是自我提升还是应付面试以下这些手写功能是每一个前端程序员必须掌握的

1. 手写apply、call、bind

  每个Function对象都存在apply()、call()、bind() 方法,其作用都是可以在特定的作用域
  中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。
apply、call、bind 的异同
  1. 三者都可以改变this的指向,第一个参数都是this,如果指向是null或者undefined则指向window
  2.  apply的参数是数组,call是列表,而bind可以多次传入
  3.  apply和call是改变this的指向之后直接运行函数,而bind则是返回绑定之后的函数
apply实现的参考代码
/**
 * 手写apply
 */
window.name='gy' // 全局变量 
let obj={
  name:'ckx'
}

var func=function(b,c){
  console.log(`this=`, this)
  console.log(this.name,b,c)
  return 1
}

func('24','hz')   //  gy 24 hz

func.apply(obj,['24','hz']) // ckx 24 hz

let newObj={
  name:'xmx',
  age:24
}
Function.prototype.myApply=function(base,args){
  // 1. 如果指向是null 或者undefined 则指向window
  base=base || window
  // 2. 根据this是谁调用就指向谁的原理,将this指向的函数 赋值给base对象的一个属性
  base.fn=this
  // 3.执行函数,调用base.fn时,fn中的函数指向 base对象
  let result=base.fn(...args)
  // 4. 删除base的fn属性
  delete base.fn
  // 5. 返回result 结果
  return  result
}

func.myApply(newObj,['55','yw']) // xmx 55 yw
apply代码执行效果

image.png

call 实现的参考代码
/**
 * 手写call
 */
window.name='gy' // 全局变量 
let obj={
  name:'ckx'
}

var func=function(b,c){
  console.log(`this=`, this)
  console.log(this.name,b,c)
  return 1
}

func('24','hz')   //  gy 24 hz

func.call(obj,'24','hz') // ckx 24 hz

let newObj={
  name:'xmx',
  age:24
}
// call 和apply需要注意参数的格式即可
Function.prototype.myCall=function(base,...args){
  // 1. 如果指向是null 或者undefined 则指向window
  base=base || window
  // 2. 根据this是谁调用就指向谁的原理,将this指向的函数 赋值给base对象的一个属性
  base.fn=this
  // 3.执行函数,调用base.fn时,fn中的函数指向 base对象
  let result=base.fn(...args)
  // 4. 删除base的fn属性
  delete base.fn
  // 5. 返回result 结果
  return  result
}

func.myCall(newObj,'55','yw') // xmx 55 yw
call代码执行效果

image.png

bind实现的参考代码
/**
 * 手写bind
 */
window.name = "gy"; // 全局变量
let obj = {
  name: "ckx",
};

var func = function (b, c,d) {
  console.log(`this=`, this);
  console.log(this.name, b, c,d);
  return 1;
};

func("24", "hz",26); //  gy 24 hz 26

let funcRes = func.bind(obj, "24", "hz");
funcRes(24);  // ckx 24 hz 24

let newObj = {
  name: "xmx",
  age: 24,
};
// 注意bind 返回的时绑定的函数以及可以多次传递参数
Function.prototype.myBind = function (base, ...args1) {
  return (...args2) => {
    // 1. 如果指向是null 或者undefined 则指向window
    base = base || window;
    // 2. 根据this是谁调用就指向谁的原理,将this指向的函数 赋值给base对象的一个属性
    base.fn = this;
    // 3.执行函数,调用base.fn时,fn中的函数指向 base对象
    let result = base.fn(...args1,...args2);
    // 4. 删除base的fn属性
    delete base.fn;
    // 5. 返回result 结果
    return result;
  };
};
let myfuncRes=func.myBind(newObj, "55", "yw")
myfuncRes(24) // xmx 55 yw 24
bind代码执行效果

image.png

2. 手写new

new关键字执行时做了哪些
1. 创建了一个新对象
2. 将这个新对象与构造函数用原型链链接起来
3. 将构造函数的this指向新的对象,执行构造函数的代码赋值
4. 如果构造函数没有返回一个对象就返回新创建的对象否则返回构造函数返回的对象
手写new参考代码
/***
 * 手写new关键字执行
 */

function Person(name,age) {
  this.name = name;
}
Person.prototype.getName = function () {
  return this.name;
};
let a = new Person('gy');
console.log(a);
console.log(a.getName());


const myNew = (Func, ...args) => {
  let newObj = {};
  newObj.__proto__=Func.prototype
  let result=Func.apply(newObj,args)
  return  typeof result == Object ? result: newObj
};
let b = myNew(Person,'gy1')
console.log(b);
console.log(b.getName());
代码执行结果参考图

result.png

原型链示意图

lian.webp

3. 手写instanceof

    typeof 可以判断基本数据类型 但是null 返回的也是object 不能识别 引用数据类型
    instanceof 可以准确的判断引用数据类型不可以判断 基本数据类型
    instanceof是用于检测构造函数的prototype是否出现某个实例对象的原型链上
参考代码
/**
* 手写instanceof
*/
let obj= { label:'gy' }
let arr= ['hello']

let result = obj instanceof Object
let result1 = arr instanceof Array
let result2 = arr instanceof Object
let result3 = obj instanceof Array

console.log('result=',result )
console.log('result1=',result1 )
console.log('result2=',result2 )
console.log('result3=',result3 )

const myInstanceof = (left,right)=>{
    if(typeof left != 'object' || left == null ) return false
    let proto= Object.getPrototypeOf(left)
    while(true){
        if(proto==null) return false
        if(proto==right.prototype) return true
        proto=Object.getPrototypeOf(proto)
    }
}

const myResult= myInstanceof(obj,Object)
const myResult1= myInstanceof(arr,Array)
const myResult2= myInstanceof(arr,Object)
const myResult3= myInstanceof(obj,Array)

console.log('myRsult=',myResult )
console.log('myResult1=',myResult1 )
console.log('myResult2=',myResult2 )
console.log('myResult3=',myResult3 )
代码执行结果截图

image.png 根据上面的代码打印结果 需要注意的是:万物皆对象,包括数组,如果存在一个变量(arrOrObj)可能为array或者object 如果使用instanceof 去判断,一定需要先判断是否为数组先,否则 arrOrObj instanceof Object 一定为true 就无法区分 array和object

4. 手写防抖和节流

    持续的触发某一事件,延迟n秒后执行回调,在未到n秒再次触发,会从新出发倒计时
    持续的触发某一时间,延迟n秒后执行回调,在未达到n秒再次出发,不会重新计时
两者的使用场景

防抖可能用于无法预知的用户主动行为,如用户输入内容去服务端动态搜索结果。用户打字的速度等是无法预知的,具有非规律性。

节流可能用于一些非用户主动行为或者可预知的用户主动行为,如用户滑动商品橱窗时发送埋点请求、滑动固定的高度是已知的逻辑,具有规律性。

节流防抖也是闭包的应用

手写防抖代码参考
/***
 * 手写防抖
 */
const debounce = (func, delay) => {
  let timer = null;
  return function (...args) {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    timer = setTimeout(() => {
      func(args);
    }, delay);
  };
};
const getfn = (data) => {
  console.log(data);
};
debounce(getfn, 2000)("gy");
手写防抖代码执行结果

image.png

手写节流
/***
 * 手写节流
 * 这里只需要注意和防抖不同的时 不会清除定时器
 */
const throttle = (func, delay) => {
  let flag = false;
  return function (...args) {
    if (flag)  return
    flag=true
    setTimeout(() => {
      func(args);
      flag=false
    }, delay);
  };
};
const getfn = (data) => {
  console.log(data);
};
throttle(getfn, 2000)("gy");

5. 手动实现ajax

    AJAX 的全称为 Asynchronous JavaScript + XML, 最重要的要属 XHR(XMLHttpRequest)
    XMLHttpRequest通过不刷新页面请求特定URL,获取数据。
实现的必备条件
  1. XMLHttpRequest() 是一个构造函数

  2. XMLHttpRequest.onreadystatechange 状态码变化时触发事件(所有浏览器支持)

  3. XMLHttpRequest.readyState 请求的状态码

image.png

  1. XMLHttpRequest.status 响应状态码 返回标准的HTTP 状态码

image.png

  1. 其他的请求响应参数

     XMLHttpRequest.response 这个是整个响应实体
     XMLHttpRequest.responseText 返回 `DOMString`
     XMLHttpRequest.timeout 超时时间
     XMLHttpRequest.upload 上传进度
    
  2. 在看看常用方法 open()

    // method/url 是必须的 xhr.open(method, url, async, user, password); send()

    // body 可选默认为null // 可以是 Blob, BufferSource (en-US), FormData, // URLSearchParams, 或者 USVString 对象. XMLHttpRequest.send(body); setRequestHeader()

    XMLHttpRequest.setRequestHeader(header, value); // 例如 XMLHttpRequest.setRequestHeader ("content-type", "application/x-www-form-urlencoded" );

基于以上API实现 ajax
    1.  构造一个请求 XMLHttpRequest
    2.  初始化一个请求 open
    3.  监听请求 onreadystatechange
    4.  发送该请求 send
/**
* 手写一个ajax
*/
const myAjax =(url,methods,header,success,error)=>{
    // 创建一个请求
    let request=new XMLHttpRequest()
    // 设置请求头
    for (const key in header) {
        request.setRequestHeader(key,header[key])
    }
    // 初始化请求
    request.open(methods,url)
    // 发送请求
    request.send()
    // 监听请求 onreadystatechange
    request.onreadystatechange =function(){
        if(request.readyState==4){
            if(request.status==200){
                success(request.response)
            }else {
                error()
            }
        }
    }
}

JavaScript进阶必会的手写功能(二)

总结

    欢迎提出更好的思路以及其他必备手写功能
    

myself.jpg