手写题 复习笔记

135 阅读5分钟

new

// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
// 使用 __proto__ 是有争议的,也不鼓励使用它。
// 因为它从来没有被包括在 EcmaScript 语言规范中,但是现代浏览器都实现了它。

function _new() {
  // Constructor
  let newObj = null,
      result = null,
      Constructor = Array.prototype.shift.call(arguments);
  /**
    * Constructor = Array.prototype.shift.call(arguments);
    * 删除 arguments 的第一个选项并返回,arguments 的长度被改变
    */
  
  // 1 创建一个空对象
  // 2 设置对象的原型为构造函数的原型对象
  // 3 执行构造函数,把 this 指向新建的对象
  // 4 返回新对象
  newObj = new Object();
  newObj.__proto__ = Constructor.prototype;
  Constructor.apply(newObj, arguments)
  return newObj;

  // 优化
  // newObj = Object.create(Constructor.prototype)
  // Constructor.apply(newObj, arguments)
  // return newObj;
}

// 测试代码
function Person() {
  this.name = 'zenghp'
  this.options = Array.from(arguments)
}
Person.prototype.getName = function(){
  return this.name;
}

let person = _new(Person, '123', '123')
console.log(person.getName(), person.name, person.options)

instanceof

// 判断构造函数的原型是否在实例的原型链上
function _instanceOf(a, b) {
  // Object.getPrototypeOf(a) === a.__proto__
  let aProto = Object.getPrototypeOf(a)
  const bProto = b.prototype
  while(aProto) {
    if(aProto === bProto) return true;
    aProto = Object.getPrototypeOf(aProto);
  }
}

call、apply、bind

promise promise.all promise.rece

实现类的继承 extend

map

reduce

千分符

深浅拷贝

// https://github.com/febobo/web-interview/issues/56

// 浅拷贝:创建一个对象,这个对象有着原始对象属性值的一份精确拷贝,基本数据类型拷贝的是值,引用数据类型拷贝的是引用地址
// 深拷贝:和浅拷贝类似,唯一不同是对象会重新开辟一个新的栈,和原来的的对象是两个不同的引用地址,修改一个对象的属性不会影响另一个对象

// 测试用例
const obj = {
    age: 18,
    nature: ['smart', 'good'],
    names: {
        name1: 'fx',
        name2: 'xka'
    },
    reg: new RegExp(/\d+/),
    love: function () {
        console.log('fx is a great girl')
    },
    nickname: undefined,
    unique: Symbol('#unique')
}

// 浅拷贝
function clone(obj) {
  const newObj = {}
  for(let prop in obj) {
    // 判断属性是否在对象的属性中 返回布尔值
    // if(!obj.hasOwnProperty(prop)) return; 
    newObj[prop] = obj[prop]
  }
  return newObj
}

// 合并源对象到目标对象,返回目标对象。 浅拷贝
// Object.assign(target, ...sources) 
const newObj = Object.assign({}, obj)

// 数组浅拷贝,由begin和end决定提取数组元素的索引开始和结束的位置
// Array.prototype.slice([begin[, end]])
const newAry = ary.slice()
// 合并多个数组,返回新数组。浅拷贝
// Array.prototype.concat
const newAry = [].concat(ary)

// 拓展运算符
const newAry = [...fxArr]

// 深拷贝,简化版
// 复杂版:请参考上面 github
function deepClone(obj) {
  let newObj = Array.isArray(obj) ? [] : {}
  function _isObj(type) {
    return type !== null && typeof type === 'object';
  }
  if(obj && _isObj(obj)) {
    for(key in obj) {
      if(obj.hasOwnProperty(key)) {
        const cur = obj[key]
        newObj[key] = cur && _isObj(cur) ? deepClone(cur) : cur
      }
    }
  }
  return newObj;
}

// 深拷贝,这种方式存在弊端,会忽略 Undefined Symbol Function, 正则会被拷贝成空对象!!
const newObj = JSON.parse(JSON.stringify(obj));

节流防抖

// 节流:在规定时间内只执行一次,如果在规定时间内重复触发,只会执行一次。(每隔一段时间执行一次)
// 防抖:在规定的时间内如果被重复触发,则时间重新计算。(只会执行一次)

// 想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应。
// 假设电梯有两种运行策略 debounce 和 throttle,超时设定为15秒,不考虑容量限制
// 电梯在15秒内会准时关上门,然后开始运送。 -- 节流。
// 如果在这过程中,有人触发了开门键或者挡住了门,则重新计算,直到15秒后关门开始运送。-- 防抖

// 节流:每个时间段执行一次
function throttle(fn, wait = 500) {
  let timer
  return function() {
    if(timer) return
    const _self = this
    const _arg = arguments
    timer = setTimeout(function(){
      timer = null
      fn.apply(_self, _arg)
    }, wait)
  }
}

// 防抖 只执行一次
function debounce(fn, wait = 500) {
  let timer
  return function(){
    // 触发回调,清除重新计算
    if(timer) clearTimeout(timer)
    const _self = this;
    const _arg = arguments
    timer = setTimeout(function(){
      fn.apply(_self, _arg)
    }, wait)
  }
}

// 测试用例
function handle(arg) {
  console.log(this, arg)
}

window.addEventListener('scroll', debounce(handle, 2000));

// 应用场景
// 
// 防抖
// 搜索框搜索输入。只需用户最后一次输入完,再发送请求
// 手机号、邮箱验证输入检测
// 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
//
// 节流
// 滚动加载,加载更多或滚到底部监听
// 搜索框,搜索联想功能

ajax

// Promise 封装
function _ajax(url) {
  let promise = new Promise(function(resolve, reject) {
    // new XMLHttpRequest 创建实例
    // 实例方法 open 初始化请求,设置请求方式、路径、是否异步操作(默认 true)
    // 实例方法 onreadystatechange 设置监听函数
    // 在回调函数中判断是否下载完成(readyState === 4)
    // 如果下载完成则判断是否是 200 状态码,(2开头的都是成功响应)
    // 最后 send 发送请求。

    let xhr = new XMLHttpRequest();
    // 新建一个 http 请求
    xhr.open("GET", url);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
      // xhr 状态码:
      // 0 ONSEND 代理被创建,但尚未调用 open() 方法。
      // 1 OPENED open() 方法已经被调用。
      // 2 HEADERS_RECEIVED send() 方法已经被调用,并且头部和状态已经可获得。
      // 3 LOADING 下载中; responseText 属性已经包含部分数据。
      // 4 DONE 下载操作已完成。
      if (this.readyState !== 4) return;
      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    // 设置错误监听函数
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 请求
    xhr.send(null);
  });
  return promise;
}

数组去重

// 利用数组去重
let ary = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null,null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];

// Array.prototype.from
function unique(ary){
  // 返回 Set 唯一值的集合,用 Array.from 转成数组
  return Array.from(new Set(ary))
}

// Array.prototype.indexOf
// 返回数组中指定成员的索引,如果不存在则返回 -1
// 对象和 NaN 没有过滤
function unique(ary) {
  let newAry = []
  ary.forEach(item => {
    if(newAry.indexOf(item) !== -1) return
    newAry.push(item)
  })
  return newAry;
}

// Array.prototype.includes
// 判断数组是否包含一个指定的值
// 对象并没有去重
function unique(ary) {
  let newAry = []
  ary.forEach(item => {
    if(newAry.includes(item)) return
    newAry.push(item)
  })
  return newAry;
}

// Array.prototype.filter
function unique(ary) {
  return ary.filter(function(item, index){
    // 判断当前数组选项的索引位置是否等于当前索引
    // indexOf 第二参数是开始查找的位置
    return ary.indexOf(item, 0) === index;
  })
}

// 解构
function unique(ary) {
  return [...new Set(ary)]
}

// Array.prototype.reduce
function unique(ary) {
  // reduce 传入两个参数
  return ary.reduce((prev, cur) => {
    return prev.includes(cur) ? prev : [...prev, cur]
  }, [])

  // 简化版
  // return ary.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], [])
}

数组扁平化

扁平化:将嵌套多层的数据转换成只嵌套一层

let ary = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [14]]], 10]

// Array.prototype.flat([depth]) depth: 提取嵌套的结构深度
// 使用 Infinity,可展开任意深度的嵌套数组
ary.flat(Infinity)

// 递归
function _flat(ary) {
  let result = []
  for (let i = 0; len = ary.length; i++) {
    if(Array.isArray(ary[i])) {
      // 递归调用
      // result = [...result, flatten(ary[i]]
      result = result.concat(_flat(ary[i]))
    } else {
      result.push(ary[i])
    }
  }
  return result;
}

function _flat(ary) {
  // map 遍历转成数字 ( +item 是转换成数字, 不是相加或拼接)
  return ary.toString().split(',').map(item => +item);
}

// 逻辑和递归一致 只是换种写法
// 注意 reduce 需要传入第二参数
function _flat(ary) {
  return ary.reduce(function(prev, cur) {
    return prev.concat(Array.isArray(cur) ? flatten(cur) : cur)
  }, [])
}

函数柯里化

算法

冒泡

快速排

插入排序