JS手写题汇总

127 阅读13分钟

1 浅复制 Object.assign()

/**
 * Object.assign(obj,obj1,obj2...)
 * 浅复制
 */
Object.myAssign = (target, ...source) =>{
  let res = Object(target); //转换为对象类型的构造函数调用
  source.forEach((each) => {
    if(each!==null){
      for(let key of each){
        if(each.hasOwnProperty(key)){
          res[key] = each[key]
        }
      }
    }
  })
  return res;
}

2 Object.create()

/**
 * Object.create(proto, [propertyObject])
 * 创建新对象并指定对象的原型
 */
/**
 * 
 * @param {*} proto  新对象的原型
 * @param {*} propertyObject 属性描述对象{属性,属性描述符}
 * @returns 
 */
Object.myCreate = (proto, propertyObject = undefined) => {
  if (typeof proto !== 'object' && typeof proto !== 'function') {
    throw new TypeError('Object prototype may only be an object or null')
  }
  if (proto == null) {
    throw TypeError('Cannot convert undefined or null to object')
  }
  const obj = {};
  Object.setPrototypeOf(obj, proto);
  if (propertyObject !== undefined) {
    Object.defineProperties(obj, propertyObject);
  }
  return obj;
}

3 类型判断

1 instanceof

  • A instanceof B
  • instanceof 运算符用来检测constructor.prototype 是否存在于参数 object 的原型链上
  • 判断A是否是B的实例
  • A实例 只存了一个引用 obj.__proto__
  • B 构造函数的原型存储在prototype
  • Object.getPrototypeOf(obj) 等价于 obj.__proto__
  • right.prototype 获取构造函数的 prototype 对象
  • 只有构造函数有prototype这个属性
const myInstanceof = (left ,right) => {
  let proto = Object.getPrototypeOf(left); // 获取对象的原型
  while(proto!==null){
    if(proto == right.prototype){ 
      return true;
    }
    proto = Object.getPrototypeOf(proto); // 获取原型的原型
  }
  return false;
}

测试

class People {
  constructor(){
  }
}
const p = new People()
console.log('myInstanceof',myInstanceof(p, People)) // true

2 typeof

const typeOf = function (item) {
  return Object.prototype.toString.call(item)
           .slice(8,-1).toLowerCase()
}
/**
 如果不切割的话得到的是
[object Array]
[object Object]
[object Date]
 */

总汇

  • typeof
    • 判断基本类型,无法细分Object引用类型(Array,Object ,Date
  • instanceof
    • 前提是要有构造函数 不能判断基本类型和null undefined(没有构造函数)
  • Object.prototype.toString.call(this)
    • 官方给出的最准确方法

4 promise(resolve, reject, then, all, race)

另一篇文章详解: 手写promise - 掘金 (juejin.cn)

class myPromise {
  static PENDING = 'pending';
  static FULFILLED = 'fulfiled';
  static REJECTED = 'rejected';

  constructor(func) {
    // 实例的属性直接用this.xx定义
    this.promiseStatus = myPromise.PENDING;
    this.promiseResult = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    try {
      /**
       * reslove是在实例外部调用,会使this丢失,
       * 用bind绑定this为实例中的this
       */
      func(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error);
    }
  }

  resolve(result) {
    setTimeout(() => {
      if (this.promiseStatus === myPromise.PENDING) {
        this.promiseStatus = myPromise.FULFILLED;
        this.promiseResult = result;
        /**
         * 执行then中收集的回调函数
         */
        this.onFulfilledCallbacks.forEach(callback => callback(result));
      }
    });
  }

  reject(reason) {
    setTimeout(() => {
      if (this.promiseStatus === myPromise.PENDING) {
        this.promiseStatus = myPromise.REJECTED;
        this.promiseResult = reason;
        this.onRejectedCallbacks.forEach(callback => callback(reason));
      }
    });
  }
  /**
   * 返回promise 可以链式调用
   * 根据状态来执行
   * 回调函数执行返回的值需要resolvePromise来处理
   * @param {*} onResolved 
   * @param {*} onRejected 
   * @returns 
   */
  then(onResolved, onRejected) {
    let promise = new myPromise((resolve, reject) => {
      if (this.promiseStatus === myPromise.PENDING) {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              if (typeof onResolved !== 'function') {
                resolve(this.promiseResult);
              } else {
                let value = onResolved(this.promiseResult);
                resolvePromise(promise, value, resolve, reject);
              }
            } catch (error) {
              reject(error);
            }
          });
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              if (typeof onRejected !== 'function') {
                reject(this.promiseResult);
              } else {
                let value = onRejected(this.promiseResult);
                resolvePromise(promise, value, resolve, reject);
              }
            } catch (error) {
              reject(error);
            }
          });
        });
      }

      if (this.promiseStatus === myPromise.FULFILLED) {
        setTimeout(() => {
          try {
            if (typeof onResolved !== 'function') {
              resolve(this.promiseResult);
            } else {
              let value = onResolved(this.promiseResult);
              resolvePromise(promise, value, resolve, reject);
            }
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.promiseStatus === myPromise.REJECTED) {
        setTimeout(() => {
          try {
            if (typeof onRejected !== 'function') {
              reject(this.promiseResult);
            } else {
              let value = onRejected(this.promiseResult);
              resolvePromise(promise, value, resolve, reject);
            }
          } catch (error) {
            reject(error);
          }
        });
      }
    });

    return promise;
  }

  all(paramsArr) {
    const result = [];
    let count = 0;
    return new myPromise((resolve, reject) => {
      paramsArr.forEach((item, index) => {
        /**
         * 注意处理 参数不是promise的情况
         */
        if (item instanceof myPromise) {
          item.then(
            res => {
              count++;
              result[index] = res;
              count === paramsArr.length && resolve(result);
            },
            err => {
              reject(err);
            }
          );
        } else {
          count++;
          result[index] = item;
          count === paramsArr.length && resolve(result);
        }
      });
    });
  }

  race(paramsArr) {
    return new myPromise((resolve, reject) => {
      paramsArr.forEach(item => {
        if (item instanceof myPromise) {
          item.then(
            res => {
              resolve(res);
            },
            err => reject(err)
          );
        } else {
          resolve(item);
        }
      });
    });
  }

}
//处理promise链式
  /**
   * x值的处理
   * (1)x是基本类型
   * (2)是自定义的promise对象
   * (3)是promise实例
   */
  /**
   * then结果返回值处理函数,处理回调函数是promise的情况
   * 递归查找then的链
   * @param {*} promise then的返回值promise
   * @param {*} x then回调函数的返回值
   * @param {*} resolve 对x的处理函数 then的返回值promise中的
   * @param {*} reject 对x的处理函数 then的返回值promise中的
   * @returns 
   */
function resolvePromise(promise, x, resolve, reject) {
    if (x === promise) {
      throw new TypeError('chaining cycle detected for promise');
    }
    if (x instanceof myPromise) {
      x.then(y => {
        resolvePromise(promise, y, resolve, reject);
      }, reject);
    } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
      try {
        // 有then属性的对象 或者其他规范的promise,获取then属性,
        // get操作可能会有异常
        var then = x.then;
      } catch (error) {
        return reject(error);
      }
      // then应该是个可执行函数
      if (typeof then === 'function') {
        // 添加一个锁,只执行一次
        let called = false;
        try {
          then.call( //执行
            x,
            y => {
              if (called) return;
              called = true;
              resolvePromise(promise, y, resolve, reject);
            },
            rej => {
              if (called) return;
              called = true;
              reject(rej);
            }
          );
        } catch (error) {
          if (called) return;
          called = true;
          reject(error);
        }
      } else {
        resolve(x);
      }
    } else {
      resolve(x);
    }
  }

5 ajax

  • XMLHTTPRequest对象
  • ajax是对它的封装
  • axios返回promise
  • fetch返回的是promise
  • JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致(同源)
const ajax = (method, url, data, isAsync=true) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = () => {
      if (xhr.readyState != 4) return;
      if (xhr.status == 200 || xhr == 304) { // 状态码是number, 304资源未被修改,客户端取缓存
        resolve(xhr.responseText);
      } else {
        reject(new Error(xhr.responseText));
      }
    };
    // xhr.setRequestHeader('Accept', 'application/json'); // 客户端期望接收的响应类型
    xhr.open(method, url, isAsync);
    xhr.send(data);
  });
};

6 跨域 JSONP

  • 跨域解决方案
  • 创建一个 <script src='' />标签
  • 把那个跨域的API数据接口地址
  • 赋值给scriptsrc
  • 还要在这个地址中向服务器传递该函数名(可以通过问号传参?callback=fn)
const jsonp = ({ url, params, callback }) => {
  return new Promise(res => {
    const script = document.createElement('script');
    script.src = url + '?' + 
        + Object.keys(params).map(key => key+'='+params[key]).join('&') 
        + '&callback='+callback;
    document.body.appendChild(script);
    window[callback] = data => {
      res(data);
      document.body.removeChild(script);
    };
  });
};

7 偏函数

  • 偏函数就是将一个 n 参的函数转换成固定 x 参的函数,
  • 剩余参数(n - x)将在下次调用全部传入
  • 原理:返回包装函数
  • 场景 bind(cxt, arg1) bind给出固定参数
  • 易错:最后也是return
const partial = (fn, ...args) => {
  return (...rest)=>{
    return fn(...args, ...rest); 
  }
}

8 柯里化

偏函数是柯里化的一个实例应用,柯里化更加一般化

  • 柯里化的基本形态是:有多个参数的函数转换成使用一个参数的函数
  • fn(a,b,c)-->fn(a)(b)(c)...
  • 使用到的有:闭包+递归
  • lodash工具库里有curry方法可以直接用 import curry from 'lodash/fp/curry'
  • 参考: juejin.cn/post/727236…
const curry = func => {
  let args = []; //使用闭包存储剩余参数和预设参数
  return function curried(...innerArgs) { //收集参数
    args = [...args, ...innerArgs];  //等同 args = args.concat(innerArgs);
    if (args.length >= func.length) { //func.length形参的个数
      return func(...args);
    } else {
      return curried; //继续收集剩余的参数
    }
  };
};

测试

const add = (a, b, c) => {
  return a + b + c;
}
let addCurry = curry(add);
addCurry(1)(2)(3)  // 6

9 图片懒加载

  • 图片全部加载完成后移除事件监听;
  • 加载完的图片,从 imgList 移除;
let imgList = [...document.querySelectorAll('img')];
let length = imgList.length;
const imgLazyLoad = (function() {
  let count = 0;
  return function() {
    let deleteIndexList = [];
    imgList.forEach((img, index) => {
      let rect = img.getBoundingClientReact(); //相对于视口(viewport)信息的DOM API return {top right bottom left width height}
      if(rect.top < window.innerHeight) {
        //进入窗口
        img.src = img.dataset.src; //获取data-src 添加到src来加载图片
        deleteIndexList.push(index);
        count++;
        if(count==length){
          document.removeEventListener('scroll', imgLazyLoad)
        }
      }
    })
    // 加载完的图片移除掉
    imgList = imgList.filter((i,index) => !deleteIndexList.includes(index))
  }
})() //立即执行
document.addEventListener('srcoll', imgLazyLoad)

10 字符串模板匹配

题目:

//输入 template,person
//输出:我是布兰,年龄12,性别男
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
    name: '布兰',
    age: 12,
    sex: '男'
}
console.log(render(template, person))

代码:

const render = (template, data) => {
  return template.replace(/\{\{(.+?)\}\}/g, (match, key) => {
    return data[key]
  })
}

string.replace(rules, replacer) 搜索所有符合rules的匹配项,用replacer函数替换这些匹配项
replacer是个函数的时候 replacer(match, key, offset, string)

  • match: 匹配到的内容,题目中匹配到的是 {{name}} {{age}}..
  • key: rules中的捕获组,在圆括号 () 内的部分 题目中的keyname age..
  • offset:原字符串开始位置索引
  • string:原字符串 "我是{{name}},年龄{{age}},性别{{sex}}"

replace(/\{\{(.+?)\}\}/g, function (match, key){}) 找到所有形如 {{key}} 的占位符,并将其内容(例如这里的 key)捕获到分组中。

  • .任意单个字符
  • +至少出现一次
  • ? 尽可能少地匹配字符,最短的一段非空文本

11 解析 URL 参数为对象

function parseParams(url) {
  const reg = /([^?&=]+)=([^?&=]*)/g;
  return url.match(reg).reduce((params, param) => {
    let [key, value] = param.split('=');
    value = decodeURIComponent(value); // 解码
    value = /^\d+$/.test(value) ? Number(value) : value; //转成数字
    if (params.hasOwnProperty(key)) {
      // 属性已经有值
      params[key] = [].concat(params[key],value);
    } else {
      params[key] = value;
    }
    return params;
  }, {});
}

测试

let url = 'http:// w.com/index.html?name=haha&age=18&six=man&six=man&six=man';
console.log(parseParams(url)); //{name: haha, age: 18, six: [man, man, man]}

解析正则规则:

  • /([^?&=]+)=([^?&=]*)/g
  • ()代表一个捕获组 匹配 ()=()
  • [^?&=] 不包含 ?&= 的字符
  • +至少出现一次
  • *零个或多个
  • 上面例子匹配得到的结果数组:['name=haha', 'age=18', 'six=man'...]
  • /^\d+$/ ^开头 $结尾 \d+是出现一个或多个的数字(连续)

12 发布订阅模式(事件总线)

一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新
事件总线是对发布-订阅模式的一种实现

  • 订阅on
  • 取消订阅 off
  • 发布 emit(name,once=[true|false],fn); 执行name注册的所有事件
class EventEmitter {
  constructor(){
    //创建一个数据源
    this.cache = {}
  }
  on(name, fn){
    if(this.cache[name]){
      this.cache[name].push(fn)
    }else{
      this.cache[name] = [fn]
    }
  }
  off(name, fn){
    let tasks = this.cache[name];
    if(tasks){
      const index = tasks.findIndex(item => item == fn);
      index>=0 && tasks.splice(index,1);
    }
  }
  emit(name, once=false, ...args){
    if(!this.cache[name]) return;
    let task = this.cache[name];
    for(let fn of task) {
      fn(...args);
      once && this.off(name, fn)
    }
  }
}

测试:

let eventBus = new EventEmitter()
let fn1 = function(name, age) {
	console.log(`fn1, ${name} ${age}`)
}
let fn2 = function(name, age) {
	console.log(`fn2, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
// eventBus.emit('aaa', false, '布兰', 12)
eventBus.off('aaa', fn1)
// eventBus.off('aaa', fn2)

eventBus.emit('aaa', false, 'sayhai', 13)
eventBus.emit('aaa', true, 'sayhai', 13)
eventBus.emit('aaa', false,'sayhai', 13)

13 深复制/深拷贝

对象的 赋值?浅复制?深复制?

  • 赋值:得到对象的引用
  • 浅复制:得到一个新的对象,但属性只复制了一层,如果属性是对象的话,值是引用
  • 深复制:一个新的对象
const deepClone = function (obj, visited = new WeakMap()) {
  if (visited.get(obj)) {
    return visited.get(obj);
  }
  if (typeof obj === 'object') {
    const cloneTarget = Array.isArray(obj) ? [] : {};
    visited.set(obj, cloneTarget); // 要拷贝的值 记录到map结构中 防止循环引用进入死循环
    for (let key in obj) {
      cloneTarget[key] = deepClone3(obj[key], visited); //每个属性都深拷贝
    }
    return cloneTarget;
  } else {
    return obj; //基本类型
  }
};
 //WeakMap键是弱引用 没有引用了会垃圾回收

其他方法:

//JSON安全 obj先转成JSON字符串,在转换成JSON Obj
//不能解决循环引用
const deepClone = function(obj){
  return JSON.parse(JSON.stringify(obj))
}
//html dom自带的API structuredClone(value,{transfer:[]}) 
//不支持含有循环引用、未实现可迭代接口的自定义对象
const deepClone = function(obj) {
  return structuredClone(obj) //原生API
}

测试:

let cloneObj = deepClone(obj);

console.log('obj',obj);
console.log('cloneObj',cloneObj);
console.log('----change----')  

cloneObj.children.user = 'cloner'

console.log('obj',obj);
console.log('cloneObj',cloneObj);

14 浅复制

const shallowClone = function(obj){
  const cloneTarget = Array.isArray(obj) ? [] : {};
  for(let key in obj){
    cloneTarget[key] = obj[key];
  }
  return cloneTarget;
}

简化

/**
 * 扩展运算符
 * */
const shallowClone3 = (obj) => {
  return Array.isArray(obj)? [...obj] : {...obj}
}

原生方法

  • 对象: Object.assign()
  • 数组: arr.slice()

15 继承

js中实现继承的方式

1. 原型链继承

缺点

  • 1 所有实例共享原型上的属性和方法
  • 2 子类实例化的时候不能向父类构造函数传参承
  • 3 注意constructor丢失的问题
function People() {
  this.name = ['people'];
  this.sayHi = function(){
    console.log('hi')
  }
}
function Women() {}
Women.prototype = new People(); //原型链继承
Women.prototype.constructor = Women
//测试
const rose = new Women();
rose.name.push('rose')
const lili = new Women();
lili.name.push('lili')
console.log(lili.name) // ['people', 'rose']
lili.sayHi() // hi
//属性共享, womem的原型是people的实例,所以people上所有的属性和方法都会被实例共享

2. 构造函数继承

优点

  • 1 实例上属性和方法是单例的不共享
  • 2 可以向父类构造函数传递参数

缺点

  • 子类访问不到父类的方法, 只能写到构造函数中才能访问
function People(name) {
 this.name = name;
 this.family = ['family']
 this.sayHi = function(){
   console.log('hi, i am ' + name);
 }
}
People.prototype.greet = function() { //父类上的方法
 console.log('nice to meet you');
}
function Women(name) {
 People.call(this,name) //调用构造函数,this绑定新建的实例
}
const rose = new Women('rose');
rose.family.push('rose')
const lili = new Women('lili');
console.log(lili.name, lili.family) // lili, ['family']
lili.sayHi();//hi, i am lili
lili.greet();//Uncaught TypeError: lili.greet is not a function 不能访问父类中的方法

3. 组合继承 (原型链继承方法 构造函数继承属性)

优点

  • 在构造函数继承的基础上, 子类可以继承父类上的方法

缺点

  • 父类被调用了两次 call+1 new+1
function People(name) {
  console.log('构造函数被调用')
  this.name = name;
  this.family = ['family']
}
People.prototype.greet = function() {
  console.log('i am '+ this.name +',nice to meet you');
}
function Women(name) {
  People.call(this,name) // 构造函数被调用
}
Women.prototype = new People(); // 构造函数被调用
Women.prototype.constructor = Women
const lili = new Women('lili'); 
console.log(lili.name, lili.family) // lili, ['family']
lili.greet();//i am lili,nice to meet you

4. 寄生式组合继承

  • 最优的方法
  • 要注意重写原型会丢失constructor要重新指定
function People(name) {
  this.name = name;
  this.family = ['family']
}
People.prototype.greet = function() {
  console.log('i am '+ this.name +',nice to meet you');
}
function Women(name) {
  People.call(this,name)
}

Women.prototype = Object.create(People.prototype) //new People(); // 组合继承中的原型链继承
Women.prototype.constructor = Women; 
const lili = new Women('lili'); 
console.log(lili.name, lili.family) // lili, ['family']
lili.greet();//i am lili,nice to meet you
console.log(lili.constructor.name) // Women

5. class继承

class People {
  constructor(name) {
    //这里定义的是实例的
    this.name = name;
    this.family = ['family'];
  }
  //这里定义的原型上的,是父类People上的方法,等同于People.prototype.greet
  greet() {
    console.log('i am ' + this.name + ',nice to meet you');
  }
}
class Women extends People {
  constructor(name) {
    super(name); // 调用父类的构造函数 等同于People.call(this,params)
  }
}
const lili = new Women('lili');
console.log(lili.name, lili.family); // lili, ['family']
lili.greet(); //i am lili,nice to meet you
console.log(lili.constructor.name);// Women

16 new操作符

new操作符做了哪些?

  1. 创建一个新的空对象,并将其原型设置为构造函数的 prototype
  2. 使用 apply 方法调用构造函数,并传入新创建的对象作为 this 上下文以及提供的其他参数。
  3. 检查构造函数返回值,如果返回值是一个对象,则返回该对象;否则返回新创建的对象。
const myNew = (fn, ...args) => {
  const obj = Object.create(fn.prototype); //创建新对象并指定原型
  const res = fn.apply(obj,args); //执行构造函数
  return res instanceof Object ? res : obj; //返回新对象或者构造函数返回的对象
}

测试

function People(name) {
  this.name = name;
  this.sayHi = function(){
    console.log('hi,i am '+name)
  }
}
People.prototype.walk = function() {
  console.log('I am walking...');
}
const lili = myNew(People,'lili');
console.log(lili.name);//lili
lili.sayHi();//hi,i am lili
lili.walk();//I am walking...

17 【数组方法】reduce

  • 基本形态 arr.reduce(fn(pre, curr, index, arr),init)
  • 返回一个遍历完数组后计算的值
Array.prototype.myReduce = function (fn, init) {
  let res = init || this[0]; //当前数组的第一个元素
  let i = init ? 0 : 1;
  if (typeof fn == 'function') {
    for (i; i < this.length; i++) {
      res = fn(res, this[i], i, this);
    }
  } else {
    throw new Error('parameter1 is not a function');
  }
  return res;
};

测试

let arr = [1, 2, 3];
console.log(arr.myReduce((pre, curr) => pre + curr));
console.log(arr.myReduce((pre, curr) => pre + curr, 6));

18 【数组方法】map

Array.prototype.myMap = function (fn, thisArg) {
  const res = [];
  const cxt = thisArg || window;
  if (typeof fn == 'function') {
    for (let i = 0; i < this.length; i++) {
      res.push(fn.call(cxt, this[i], i, this));
    }
  } else {
    return new TypeError('Parameter1 is not a function');
  }
  return res;
};

测试

const arr = [1, 2, 3, 4];
console.log(arr.myMap(item => item + 1));

19 【数组方法】forEach

  • 基本形态 arr.forEach(function(item,index,arr),thisArg)
  • 两个参数
    • 1-函数(每一项,下标,数组本身)
    • 2-上下文
Array.prototype.myForEach = function (fn, thisArg) {
  const cxt = thisArg || window;
  if (typeof fn === 'function') {
    for (let i = 0; i < this.length; i++) {
      fn.call(cxt, this[i], i, this);
    }
  } else {
    return new TypeError('parameter1 is not a function');
  }
};

测试

const arr = [1, 2, 3, 4];
arr.myForEach(item => console.log(item * 2));

20 【数组方法】filter

  • 基本形态 arr.filter(fn(item,index,arr),thisArg) 返回新数组--内容是符合条件的
  • 参考 juejin.cn/post/703739…
Array.prototype.myFilter = function (fn, thisArg) {
  const cxt = thisArg || window;
  const res = [];
  if (typeof fn === 'function') {
    for (let i = 0; i < this.length; i++) {
      fn.call(cxt, this[i], i, this) && res.push(this[i]);
    }
  } else {
    return new TypeError('parameter1 is not a function');
  }
  return res;
};

测试

const arr = [1, 2, 3, 4];
console.log('filter',arr.myFilter(i => i % 2 == 0));

21 【数组方法】some

  • 基本形态 arr.some(fn(item,index,arr),thisArg) 返回boolean
    • 1存在一个元素满足条件true
    • 2都不满足返回false
  • 参考 juejin.cn/post/703806…
Array.prototype.mySome = function (fn, thisArg) {
  const cxt = thisArg || window;
  const res = false;
  if (typeof fn === 'function') {
    for (let i = 0; i < this.length; i++) {
      if (fn.call(cxt, this[i], i, this)) {
        return true;
      }
    }
  } else {
    return new TypeError('parameter1 is not a function');
  }
  return res;
};

测试

const arr = [1, 2, 3, 4];
console.log('some',arr.mySome(i => i % 2 == 0));

22 【数组方法】every

  • 基本形态 arr.every(fn(item,index,arr),thisArg) 返回boolean 只要有一个不满足就false 全满足才true
  • 参考 juejin.cn/post/703806…
Array.prototype.myEvery = function (fn, thisArg) {
  const cxt = thisArg || window;
  const res = true;
  if (typeof fn === 'function') {
    for (let i = 0; i < this.length; i++) {
      if (!fn.call(cxt, this[i], i, this)) {
        return false;
      }
    }
  } else {
    return new TypeError('parameter1 is not a function');
  }
  return res;
};

测试

const arr = [1, 2, 3, 4];
console.log('every',arr.myEvery(i => i % 2 == 0));

23 【函数原型方法】call

Function.prototype.callx = function (context, ...args) {
  context = context || window;
  const fn = Symbol(); //创建唯一属性键名
  context[fn] = this; // 当前函数赋给上下文的属性中 来实现this的绑定
  let res = context[fn](...args);
  delete context[fn];
  return res;
};

测试

var age = 0;
var name = 'win';

var obj = {
  age: 25,
  name: 'sun',
};

var say = function (a, b) {
  console.log(this, this.age, this.name, a, b);
};
say(100, 101);
say.c(obj, 100, 101);

say为例 上述代码

  • say调用callx this指向say
  • obj.fn = this say挂载到obj的方法上 这样调用saythis变成了obj 改变了this指向
  • 删除掉原来的方法
  • 参考 blog.csdn.net/weixin_4092…

24 【函数原型方法】apply

  • apply的参数部分是接收参数数组,要判断 apply 参数可能是空数组的情况
Function.prototype.applyx = function (cxt, args) {
  cxt = cxt || window;
  const fn = Symbol();
  cxt[fn] = this;
  const res = args?.length ? cxt[fn](...args) : cxt[fn]();
  delete cxt[fn];
  return res;
};

测试

var age = 0;
var name = 'win';

var obj = {
  age: 25,
  name: 'sun',
};

var say = function (a, b) {
  console.log(this, this.age, this.name, a, b);
};
say(100, 101);
say.applyx(obj, [100, 101]);

25 【函数原型方法】bind 用call或apply来实现

  • bind 改变上下文(this指向) 不同的是返回一个新函数 新函数也可以传递参数(偏函数)
  • 利用闭包的方法 能够保留上下文
Function.prototype.bindx = function (cxt, ...args) {
  const func = this; //当前执行的函数
  return function (...rest) {
    return func.call(cxt, ...args, ...rest); 
    // func.apply(cxt, [...args, ...rest])
  };
};

测试

var age = 0;
var name = 'win';

var obj = {
  age: 25,
  name: 'sun',
};

var say = function (a, b) {
  console.log(this, this.age, this.name, a, b);
};
say(100, 101);
const hi = say.bindx(obj, 100, 101);
hi();

26 防抖debounce

  • 在计时器的间隔内,每触发一次就重新计时,当不再有触发事件的时候执行一次
  • 防抖主要适用于那些只需要最后一次操作结果的场景,如表单提交、搜索
const debounce = function (fn, delay) {
  let timer = null;
  return function () {
    if (timer) {
      clearInterval(timer);
    }// 删除刷新
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay);
  };
};

27 节流throttle

  • 计时器间隔内出发多次,一旦时间到了,立即执行,且执行一次
  • 固定了吞吐量,所以节流更适合那些需要一定时间内稳定的动作结果,例如实时反馈位置信息但不需要每秒多次更新的情况
  • 拖拽事件(限制状态更新频率)、滚动事件、窗口大小变化
const throttle = function (fn, delay) {
  let timer;
  return function () {
    if(timer) return; //执行过了 就不再执行
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      timer = null;
    },delay)
  }
}

完结🎉