Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript深入理解之手写代码 #3

Open
plane-hjh opened this issue Apr 28, 2020 · 0 comments
Open

JavaScript深入理解之手写代码 #3

plane-hjh opened this issue Apr 28, 2020 · 0 comments

Comments

@plane-hjh
Copy link
Owner

plane-hjh commented Apr 28, 2020

在面试中,面试官多多少少都会要求面试者手写几道手写代码方法,这时候,不仅要求面试者能够熟练使用这些方法,更是要求面试者掌握这些方法的原理。

  • 实现一个 new 操作符

  • 实现一个 instanceof

  • 实现一个浅拷贝或者深拷贝

  • 实现 call 和 apply

  • 实现 防抖 和 节流

  • 实现一个 promise

  • 实现 ajax 原理

  • 实现 jsonp

  • 实现 JSON.stringify

  • 实现 JSON.parse

  • 实现 flat 扁平化

  • 实现类的继承

  • 实现双向绑定

  • 实现函数柯里化

实现一个 new 操作符

要创建一个实例,那么必须要使用 new 操作符。使用 new 操作符内部会经历4个过程

  1. 创建一个新的对象

  2. 将构造函数的作用域赋给了新对象

  3. 执行构造函数中的代码

  4. 返回新的对象***(注意:如果构造函数中有返回对象类型的值,那么我们就直接返回这个对象。否则返回新的对象)***

function createNew(fn) {
    // 1. 创建一个新的对象
    let obj = new Object()

    // 2. 将构造函数的作用域赋给了新对象
    if(fn.prototype) {
        obj.__proto__ = fn.prototype
    }

    // 3. 执行构造函数中的代码
    const result = fn.apply(obj, [...arguments].slice(1))

    // 4. 返回新的对象
    return typeof result === 'object' ? result : obj
}

实现一个 instanceof

instanceof 可以正确的判断对象的类型。因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype

// letf 对象
// right 类型
const instanceof = (left, right) => {
    // 类型的原型
    let prototype = right.prototype

    // 对象的原型
    let proto = left.__proto__

    while(true) {
        if(proto === null) return false

        if(proto === prototype) return true

        proto = proto.__proto__
    }

    return false
}

实现一个浅拷贝或者深拷贝

在使用对象的时候,我们经常会碰到这样的一个问题

const a = {
    name: 'xiaohong'
}

const b = a

a.name = 'xiaoming'
console.log(b)  // {name: 'xiaoming'}

可能会有朋友会问,咦,我们不是没有修改对象 b 的属性吗,怎么属性 name 变了。其实这里面涉及到引用类型的问题。对象是属于引用类型,把对象赋值给另外一个变量,实际上是把对象的指针指向的内存地址共享给了变量。对象和变量共享同一份内存地址。那么 a 的属性改变了,那么 b 的也会跟着变化的。这时候为了避免这种情况发生,我们可以使用 浅拷贝 来进行操作

  • Object.assign
const a = {
    name: 'xiaohong'
}

const b = Object.assign({}, a)

a.name = 'xiaoming'
console.log(b)  // {name: 'xiaohong'}
  • 对象展开运算符
const a = {
    name: 'xiaohong'
}

const b = {...a}

a.name = 'xiaoming'
console.log(b)  // {name: 'xiaohong'}

注意,浅拷贝 避免对象内部的属性不是引用类型的情况,那么如果对象的内部属性仍然是引用类型的情况时,浅拷贝 这时候只会对对象最外层生效了

const a = {
    name: 'xiaohong',
    obj: {
        age: 10
    }
}

const b = Object.assign({}, a)

a.obj.age = 20
console.log(b)  // {name: 'xiaohong',obj:{age: 20}}

浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到刚开始的话题了,两者享有相同的引用。要解决这个问题,我们需要引入 深拷贝 的概念了。

一般来说,使用 JSON.parse(JSON.stringify()) 已经满足了

var newObj = JSON.parse( JSON.stringify( someObj ) );

但是这种方法存在局限性

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

所以,我们可以利用递归来解决这种情况

function deepCopy(obj){
    //判断是否是简单数据类型,
    if(typeof obj == "object"){
        //复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for(let i in obj){
            result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
        }
    }else {
        //简单数据类型 直接 == 赋值
        var result = obj;
    }
    return result;
}

实现 call 和 apply

call 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。与 apply 方法类似,只有一个区别,就是 call 方法接受的是一个参数列表,而 apply 方法接受的是一个包含多个参数的数组。

Fuction.prototype.myCall = (content) => {
    content = content || window

    content.fn = this

    let args = [...arguments].slice(1)
    let res = content.fn(...args)

    delete content.fn

    return res
}

同理,apply 的实现只是传入的参数是一个数组而已

Fuction.prototype.myApply = (content) => {
    content = content || window

    content.fn = this

    let res
    if(argument[1]) {
        res = content.fn(...arguments[1])
    } else {
        res = content.fn()
    }

    delete content.fn

    return res
}

实现 防抖 和 节流

防抖 指的是防止用户用户过多操作,而带来不必要的性能耗费,把操作改成最后一次执行,只要用户有操作,事件行为也会一直被推迟。例如输入框

const debounce = function (wait, fn) {
    let timer

    return (...args) => {
        // 如果事件一直在触发,那么就把上一次事件的定时器清除
        if(timer) {
            clearTimeOut(timer)
        }

        timer = setTimeout(() => {
            fn.apply(this, args)
        }, wait)
    }
}

防抖节流 本质是不一样的。防抖 是将多次执行变为最后一次执行,节流 是将多次执行变成每隔一段时间执行。

const throttle = function (wait, fn) {
    let preTime = 0

    return (...args) => {
        // 获取当前的时间戳
        let curTime = new Date().getTime()

        // 与上一个时间段的时间戳做对比
        if((curTime - preTime) > wait) {
            preTime = curTime
            fn.apply(this, args)
        }
    }
}

实现一个promise

(1)"promise"是一个对象或者函数,该对象或者函数有一个then方法

(2)"then"是一个对象或者函数,用来定义then方法

(3)"value"是promise状态成功时的值

(4)"reason"是promise状态失败时的值

普通版

function myPromise(constructor) {
    let self = this

    // 一个promise必须有3个状态,pending,fulfilled(resolved)
    self.status = "pending" // 定义状态改变前的初始状态
    self.value = undefined  // 定义状态为resolve的时候的状态
    self.reason = undefined // 定义状态为reject的时候的状态

    function resolve () {
        // 两个pending,保证了状态的改变是不可逆的
        if(selft.status === 'pending') {
            self.value = value
            self.status = "resolved"
        }
    }

    function reject () {
        // 两个pending,保证了状态的改变是不可逆的
        if(selft.status === 'pending') {
            self.reason = reason
            self.status = "rejectd"
        }
    }

    //捕获构造异常
    try{
        constructor(resolve, reject)
    } catch(e) {
        reject(e)
    }
}

// 一个promise必须有一个then方法,then方法接受两个参数
myPromise.prototype.then = function(onFullfilled, onRejected) {
    let self = this

    switch(self.status) {
        case "resolved":
            onFullfilled(self.value)
            break
        case "rejected":
            onRejected(self.value)
            break
        default
    }
}

但是这里 myPromise 无法处理异步的 resolve. 比如:

var p= new myPromise(function(resolve,reject){
    setTimeout(function(){
        resolve(1)
    },1000)
});

p.then(function(x){
    console.log(x)
})

而且 myPromise 也没有实现链式调用,也就是说 then 方法返回的应该是一个 promise

// 为了处理异步resolve,我们修改myPromise的定义,用2个数组onFullfilledArray和onRejectedArray来保存异步的方法。在状态发生改变时,一次遍历执行数组中的方法。
function myPromise(constructor){
    let self=this;
    self.status="pending" //定义状态改变前的初始状态
    self.value=undefined;//定义状态为resolved的时候的状态
    self.reason=undefined;//定义状态为rejected的时候的状态
    self.onFullfilledArray=[];
    self.onRejectedArray=[];
    function resolve(value){
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
          self.onFullfilledArray.forEach(function(f){
                f(self.value);
                //如果状态从pending变为resolved,
                //那么就遍历执行里面的异步方法
          });
       }
    }
    function reject(reason){
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
          self.onRejectedArray.forEach(function(f){
              f(self.reason);
             //如果状态从pending变为rejected, 
             //那么就遍历执行里面的异步方法
          })
       }
    }
    //捕获构造异常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}

// 要通过then方法实现链式调用,那么也就是说then方法每次调用需要返回一个primise,同时在返回promise的构造体里面,增加错误处理部分,我们来改造then方法
myPromise.prototype.then=function(onFullfilled,onRejected){
    let self=this;
    let promise2;
    switch(self.status){
      case "pending":
        promise2=new myPromise(function(resolve,reject){
             self.onFullfilledArray.push(function(){
                try{
                   let temple=onFullfilled(self.value);
                   resolve(temple)
                }catch(e){
                   reject(e) //error catch
                }
             });
             self.onRejectedArray.push(function(){
                 try{
                   let temple=onRejected(self.reason);
                   reject(temple)
                 }catch(e){
                   reject(e)// error catch
                 }
             });
        })
      case "resolved":
        promise2=new myPromise(function(resolve,reject){
            try{
              let temple=onFullfilled(self.value);
              //将上次一then里面的方法传递进下一个Promise的状态
              resolve(temple);
            }catch(e){
              reject(e);//error catch
            }
        })
        break;
      case "rejected":
        promise2=new myPromise(function(resolve,reject){
            try{
               let temple=onRejected(self.reason);
               //将then里面的方法传递到下一个Promise的状态里
               resolve(temple);
            }catch(e){
               reject(e);
            }
        })
        break;
      default:
   }
   return promise2;
}

实现ajax原理

let xhr = new XMLHttpRequest()

xhr.open('get/post', '/api/getSth',  true)

xhr.send(null)

xhr.onreadystatechange = () => {
  if(xhr.readyState === 4) {
    if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
      console.log(xhr.responseText)
    } else {
      console.log('Error:' + xhr.status)
    }
  }
}

实现 jsonp

function objectToQuery(obj) {
  const arr = []

  for(let i in obj) {
    arr.push(encodeURIComponent(i)+ '=' + encodeURIComponent(o[i]))
  }

  return arr.join('&')
}

function jsonp({url, data, callback}) {
  const container = document.getElementsByTagName('head')[0]

  const fnName = `jsonp_${new Date().getTime()}`
  const script = document.createElement('script')
  script.src = `${url}?${objectToQuery(data)}&callback=${fnName}}`
  script.type = 'type/javascript'
  container.appendChild(script)
  
  window[fnName] = function (res) {
    callback && callback(res)

    container.removeChild(script)
    delete window[fnName]
  }

  script.onerror = function() {
    window[fnName] = function () {
      callback && callback('error')

      container.removeChild(script)
      delete window[fnName]
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant