工具函数实现细节和原理 (二)

412 阅读4分钟

1、手写 call

  • Symbol 在 Es5 的对象属性名中都是字符串,当一对象的属性名出现重复时,后者往往会覆盖前者. 若使用 Symbol 就能够保证每个属性的名字都是独一无二的,相当于生成一个唯一的标识 ID,这样就从根本上防止属性名的冲突
Function.prototype.myCall = function (context, ...args) {// 解构context 与arguments
  if (typeof this !== 'function') {// this 必须是函数
    throw new TypeError('must be a function');
  }
  if (!context) {// 没有context,或者传递的是 null undefined,则重置为window
    context = window;
  }
   const fn = Symbol(); // 指定唯一属性,防止 delete 删除错误
   context[fn] = this; // 将 this 添加到 context的属性上
   const result = context[fn](...args); // 直接调用context 的 fn
   delete context[fn]; // 删除掉context新增的symbol属性
  return result;// 返回返回值
};
// 用法
const bar = { a: 1 };
function foo(b) {
  console.log(this.a, b);
  return this.a + b;
}
console.log(foo.myCall(bar, 2));

2、手写 apply

Function.prototype.myApply = function (context, args=[]) {// 解构context 与arguments
  if (typeof this !== 'function') {// this 必须是函数
    throw new TypeError('must be a function');
  }
  if (!context) {// 没有context,或者传递的是 null undefined,则重置为window
    context = window;
  }
  context.fn = this; //  将 this 添加到 context的属性上
  const result = context.fn(...args); // 直接调用context 的 fn
  delete context.fn; // 删除掉context新增的s
  return result;// 返回返回值
};
// 用法
const bar = { a: 1 };
function foo(b) {
  console.log(this.a, b);
  return this.a + b;
}
console.log(foo.myApply(bar, [2]));

3、手写 bind

Function.prototype.myBind = function(context,...args) {
    const fn = this;
    if(typeof fn !== "function"){
        throw new TypeError("it must be a function")
    }
    if(!context){
       context = window;
    }
    return function (...otherArgs) {
        return fn.apply(context,[...args,...otherArgs]);
    }
}
// 用法
const bar = {
    a:1,
};
function foo(b,c,d) {
  console.log(this.a,b,c,d) // 1 2 3 4
  return this.a+b+c+d;
}
const fn = foo.myBind(bar,2)
console.log(fn(3,4))

4、实现防抖

  • 概念 触发事件后,在 n 秒内函数只执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
function debounce(func, delay){
    let timer;
    return function() {
        console.log('debounce: return: ',arguments);
        clearTimeout(timer)
        timer = setTimeout(()=>{
            func(arguments)
        },delay)
    }
}

 const aa = debounce((...args)=>{
    console.log('debounce: 执行了',...args)
},3000);
aa('aa','bb');

5、实现节流

  • 概念 连续触发事件,但是在 n 秒中,只执行一次
function throttleOne (func, delay){
    let preTime =0;
    return function(){
        // Date.now()要快于new.Date().getTime(),都是获取时间戳的
        const nowTime = Date.now()
        if(nowTime-preTime>delay){
           func(arguments)
            preTime=nowTime;
        }
    }
}
function throttleTwo (func, delay){
    let isStart = true;
    return function(){
       if(isStart){
        isStart=false
        setTimeout(() => {
            func(arguments)
            isStart = true
        }, delay);
       }
    }
}
// 用法
const bb = throttleTwo((...args)=>{
    console.log('throttle: 执行了',...args)
},5000);
bb('aa','bb');

6、手写 promise.all

const isPromise = value => typeof value.then === 'function';
Promise.all = function(promises) { // 全部成功才成功
    return new Promise((resolve, reject) => {
        // 异步 :并发 (使用for循环迭代执行) 和 串行(借助回调 第一个完成后调用第二个)
        // 遍历数组 依次拿到执行结果
        let arr = [];
        let index = 0;
        const processData = (key, data) => {
            arr[key] = data; // 不能使用数组的长度来计算
            if(++index === promises.length){
                resolve(arr);
            }
        }
        for (let i = 0; i < promises.length; i++) {
            let result = promises[i];
            if (isPromise(result)) {
                result.then((data) => {
                    processData(i, data)
                }, reject)
            } else {
                processData(i, result)
            }
        }
    });
}
let p1 = new Promise((resolve, reject) => {
  reject('成功了')
})

let p2 = new Promise((resolve, reject) => {
  resolve('success')
})

Promise.all([p1,p2]).then((res)=>{
  console.log('-result-',res)
})

7、手写 promise.race

const isPromise = value => typeof value.then === 'function';
Promise.race = function(promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            let result = promises[i];
            if(isPromise(result)){
                result.then(resolve,reject)
            }else{
                resolve(result);
            }
        }
    });
}
// p1 和p2 同上6
Promise.all([p1,p2]).then((res)=>{
  console.log('result: ',res)
})

8、手写 继承

 //Object.create的理解
const arr = [{"1":1},2,3,5];
// Object.create 创建了一个新对象,这个新对象的 原型是arr
const arr3 = Object.create(arr)

// 实现一个组合集成
function Parent(){
   this.name ="father";
   this.age = 11;
}
Parent.prototype.say= function (){
  console.log('老爸讲话了')
}
// 构造函数继承,可以传参,多继承
function Child(color){
  Parent.call(this)
  this.color = color;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
/*
* 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
* Child.prototype = new Parent();
*/
/*
*  Child.prototype = Parent.prototype
*  构造函数会覆盖
*/
const child1 = new Child('黄色');
const child2 = new Child('黄色');
const parent1 = new Parent();
console.log(child1.constructor)
console.log(child2.constructor)
console.log(parent1.constructor)

9、手写 new

  • a.创建一个空的简单 JavaScript 对象(即{});
  • b.链接该对象(即设置该对象的构造函数)到另一个对象 ;( 通俗理解就是新对象隐式原型proto链接到构造函数显式原型 prototype 上。)
  • c.将步骤 1 新创建的对象作为 this 的上下文 ;( 实际是执行了构造函数 并将构造函数作用域指向新对象 )
  • d.如果该函数没有返回对象,则返回 this。( 实际是返回一个空对象, new Object()就是返回一个空对象{} )
function myNew(fn, ...args) {
  let obj = Object.create(fn.prototype);
  let res = fn.call(obj, ...args);
  if (res && (typeof res === "object" || typeof res === "function")) {
    return res;
  }
  return obj;
}
用法如下:
// // function Person(name, age) {
// //   this.name = name;
// //   this.age = age;
// // }
// // Person.prototype.say = function() {
// //   console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();

10、手写-setTimeout 模拟实现 setInterval

function mySetInterval(fun, time) {
  let timer = null;
  // 递归实现
  function setIntervalMe() {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fun();
      setIntervalMe(fun, time);
    }, time);
  }
  setIntervalMe();
  return () => {
    // 清除,取消 重复执行
    clearTimeout(timer);
  };
}
// 用法
const cancel = mySetInterval(() => {
  console.log('aa', new Date());
}, 2000);
setTimeout(() => {
  cancel();
}, 5000);

11、虚拟 dom 转换真实 dom 的简单实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .app {
        height: 500px;
        width: 500px;
        background-color: bisque;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <script>
      const vnode = {
        tag: 'DIV',
        attrs: {
          id: 'app',
          class: 'app'
        },
        children: [
          {
            tag: 'DIV',
            attrs: {
              style: { height: '100px', width: '100px', backgroundColor: 'blue' }
            },
            children: [
              {
                tag: 'A',
                attrs: {
                  style: { color: '#fff' }
                },
                children: [{ value: 'hello word' }]
              }
            ]
          },
          {
            tag: 'SPAN',
            children: [
              { tag: 'A', children: [] },
              { tag: 'A', children: [] }
            ]
          }
        ]
      };
      // 虚拟dom上映射着真实dom
      function _render(vnode) {
        if (typeof vnode.tag !== 'string') {
          return document.createTextNode(vnode.value);
        }
        const dom = document.createElement(vnode.tag);
        // 给元素添加上属性
        if (vnode.attrs) {
          for (let key in vnode.attrs) {
            if (key === 'style') {
              for (let styleName in vnode.attrs[key]) {
                dom.style[styleName] = vnode.attrs[key][styleName];
              }
            } else {
              dom.setAttribute(key, vnode.attrs[key]);
            }
          }
        }
        vnode.children &&
          vnode.children.forEach(child => {
            // 递归创建儿子节点,将儿子节点扔到父节点中
            return dom.appendChild(_render(child));
          });
        // 如果不是标签就是文本
        return dom;
      }
      const nodes = _render(vnode);
      document.getElementById('root').appendChild(nodes);
    </script>
  </body>
</html>

12、真实 dom 转换 虚拟 dom 简单实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root" class="tt" style="height: 100px; width: 100px; background-color: bisque">
      <div title="tt1">hello1</div>
    </div>
    <script>
      class VNode {
        constructor(tag, attrs, value, type) {
          this.tag = tag && tag.toLowerCase();
          this.attrs = attrs;
          this.value = value;
          this.type = type;
          this.children = [];
        }
        appendChild(vnode) {
          this.children.push(vnode);
        }
      }
      function getVNode(node) {
        const nodeType = node.nodeType;
        let _vnode = null;
        if (nodeType === 1) {
          // 是元素
          const nodeName = node.nodeName;
          const attrs = Array.from(node.attributes); // 类数组
          let _attrObj = {};
          attrs &&
            attrs.forEach(item => {
              _attrObj[item.nodeName] = item.nodeValue;
            });
          _vnode = new VNode(nodeName, _attrObj, undefined, nodeType);
          const childNodes = node.childNodes;
          childNodes.forEach(childNode => {
            _vnode.appendChild(getVNode(childNode));
          });
        } else if (nodeType === 3) {
          // 文本节点
          _vnode = new VNode(undefined, undefined, node.nodeValue, nodeType);
        }
        return _vnode;
      }
      const root = document.getElementById('root');
      const vRoot = getVNode(root);
      console.log(vRoot);
      console.log(JSON.stringify(vRoot));
    </script>
  </body>
</html>