JS的手写-1月23日更新

115 阅读10分钟

1.Object.create()

/**
 * Object.create()
 * 使用现有对象作为新创建对象的原型
 *
 *
 * 笔记:新创建对象的原型==》构造函数
 */

function create(obj) {
  function F() {}
  F.prototype = obj
  return new F()
}

const person = { age: 18 }

const child1 = Object.create(person)
const child2 = create(person)

console.log(child1, child1.age)
console.log(child2, child2.age)
// 结果一致

2.instanceof

/**
 * instanceof()
 * 检测构造函数的prototype属性是否在对象的原型链上
 *
 * 
 * 笔记:谁的实例?==》
 * 构造函数、实例对象 ==》
 * 对象的隐式原型如果指向构造函数的显示原型是不是就是?==》
 * 解决了A和Person的问题,思考怎么解决A和Object的问题
 */

function Person(age) {
  this.age = age
}

const A = new Person(10)

console.log(A instanceof Person)
console.log(A instanceof Object)
// 都为true
// 难点:A不仅Instanceof Person,同时也是Object
 
 
// 解决:
function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left)
  const prototype = right.prototype

  while (true) {
    if (!proto) return false
    if (proto === prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
}

console.log(myInstanceof(A, Person))
console.log(myInstanceof(A, Object))

3.new

/**
 * new
 *
 * 创建一个对象
 * 对象的原型指向函数的原型
 * this指向这个对象,执行构造函数的代码
 * 如果构造函数有返回值,返回一个对象则返回对象,否则返回该对象
 *
 *
 * 笔记:
 * 1,首先需要2个参数,即构造函数和构造函数的参数
 * 2,创建一个对象(可以用字面量,可以用Object.create)
 * 3,this指向这个对象(推荐用apply)
 */
 
 // 第一种写法
function myNew(fn, ...args) {
  const obj = {}
  obj.__proto__ = fn.prototype
  const value = fn.apply(obj, args)
  return value instanceof Object ? value : obj
}

// 第二种写法
function myNew(fn, ...args) {
  const obj = Object.create(fn.prototype)
  const value = fn.apply(obj, args)
  return value instanceof Object ? value : obj
}

4.promise

5-1.防抖

/**
 * 场景:不停的点击,只触发最后一次;echarts图的resize,滚动条
 * 
 * 
 * 思路:
 * 1,闭包,_debounce中声明一个函数,然后返回,在声明的函数中调用fn
 * 2,全局变量timer,只要有timer就清除掉前一个定时器
 * 3,this.value现在拿不到值了,因为返回的是内部的函数,指向window,要通过call/apply指向input
 */

function _debounce(fn, time) {
    let timer = null
    return function (...arg) {
      timer && clearTimeout(timer)
      timer = setTimeout(() => {
        fn.apply(this, arg)
      }, time)
    }
}

5-2.节流

/**
 * 节流
 * 一定时间内只触发一次,指同时只有一个定时器,后来的会被取消
 * 使用场景:滚动条
 *
 *
 * 和节流类似,不同的是,当定时器timer有值时就return,没值就触发一次
 */

function changeColor(params) {
    let r = Math.floor(Math.random() * 255)
    let g = Math.floor(Math.random() * 255)
    let b = Math.floor(Math.random() * 255)
    document.body.style.backgroundColor = `rgb(${r},${g},${b})`
}

window.addEventListener("resize", _throttle(changeColor, 1000))

function _throttle(fn, time) {
    let timer = null
    return function (...arg) {
      if (timer) {
        return
      }

      timer = setTimeout(() => {
        fn.apply(this, arg)
        timer = null
      }, time)
    }
}

6.手写call

/**
 * 手写call
 *
 *
 * 笔记:
 * person.call(a)本质上就是让对象a中有person这个函数,然后a.person()通过隐式绑定就将this指向了a
 * person.myCall() // 通过隐式绑定,此时this指向person函数,
 * 现在的this既然是person函数就太好了,将this赋值给content的一个属性content.fn,执行这个函数即可,为了不影响content的结构,把这个fn删除掉
 * 现在再来处理参数,用arguments这个可迭代对象,利用展开运算符可以用slice(1)截取
 * 继续完善:如果传入null报错,应该返回window,==》content = content || window
 *
 * 继续完善:返回值,content.fn(...myArguments)这一步实际上在执行person,如果person有返回值,要给返回出去
 * const res = content.fn(...myArguments)
 * return res
 *
 * 最后一波完善:如果this不是一个函数,直接捕获错误
 * if (typeof this !== "Function") {
 * return
 * }
 */
function person(a, b, c) {
  console.log(this)
  console.log(this.name)
  console.log(a, b, c)
}

const a = { name: "a" }

Function.prototype.myCall = function (content) {
  if (typeof this !== "Function") {
    return
  }
  const myArguments = [...arguments].slice(1)
  content = content || window
  content.fn = this
  const res = content.fn(...myArguments)
  delete content.fn
  return res
}

const res = person.myCall(a, "h", "e", "y")

7.手写apply

/**
 * 手写apply
 *
 *
 * 笔记:
 * 和手写call相似,
 * 不同之处在于:
 * argument只有两项,第二项是参数,即可迭代对象
 */

function person(a, b) {
  console.log(this)
  console.log(this.name)
  console.log(a, b)
}

const a = { name: "a" }

Function.prototype.myApply = function (content) {
  if (typeof this !== "function") {
    return
  }
  content = content || window
  content.fn = this
  let res = null
  if (arguments[1]) {
    res = content.fn(...arguments[1])
  } else {
    res = content.fn()
  }
  delete content.fn
  return res
}

// person.apply(a, ["x", "y"])
// 指向a    a   x,y

person.myApply(a, ["x", "y"])
// 指向a    a   x,y

8.手写bind

/**
 * 手写bind
 *
 * 笔记:
 * bind和call\apply不同,它返回是一个函数,不调用。同时它的传参是柯里化的,可以像call\apply一样传,也可以继续调用新函数传参,这是手写的难点之一
 * 同时注意,person函数调用时的返回值,也要一起返回来
 * 另一个难点:bind可以结合构造函数new来使用,new会导致this指向改变,构造函数的this怎么处理?
 *
 *
 * 难点1:
 * 将两次传参的arguments合并起来使用
 *
 *
 * 难点2:
 * 如果是构造函数,通过new来操作怎么办?
 * 首先给return出去的匿名函数加上名字F
 * 如果是通过new构造函数的方式,那么F函数中的this就指向实例
 */
 
 
 function person(a, b) {
  console.log(this)
  console.log(a, b)
  return 123
}

// const newFn = person.bind("ctx", 1)
// newFn(2)
// // 'ctx' 1,2

Function.prototype.myBind = function (ctx) {
  //   const myArg = [...arguments].slice(1) // es6写法
  const arg = Array.prototype.slice.call(arguments, 1)
  const fn = this
  return function F() {
    const restArg = Array.prototype.slice.call(arguments, 0)
    const myArg = [...arg, ...restArg]
    if (Object.getPrototypeOf(this) === F.prototype) {
      return new fn(...myArg)
      // 此处可以写成new的手写,创建一个新对象-对象原型指向函数原型-
      // 改变this指向,执行代码-注意构造函数的返回值情况分情况返回
    } else {
      return fn.apply(ctx, myArg)
    }
  }
}
const newPerson = person.myBind("ctx", 1)
const res = new newPerson(2)
console.log(res)

9.柯里化

/**
 * 函数柯里化
 *
 * 将传入多个参数的函数转化为一系列使用一个参数的函数
 *
 * 使用场景:如下
 *
 *
 * 笔记:和bind的思路相似,返回一个函数,同时要将传入的参数合并成一个数组
 * 难点1:需要判断参数数量是否够了,即参数数量》=fn的长度即可
 * 够的话直接掉调用函数,不够的递归调用
 */



// 使用场景:
function fn(x, y, z) {
  console.log(x + y + z)
  return x + y + z
}

// 1 + 2 + 10
// 1 + 3 + 10
// 1 + 4 + 10
// 1 + 5 + 10

fn(1, 2, 10)
fn(1, 3, 10)
fn(1, 4, 10)
fn(1, 4, 20)

// 函数的第一项都一样是1,那么希望得到一个函数g = curry(fn,1),再由g(2,10),g(3,10),g(4,10),g(4,20)
g = curry(fn, 1)
g(2, 10)
g(3, 10)
g(4, 10)
g(4, 20)

// 次数函数的第一项又有一样的4,那么希望得到一个函数h = curry(fn,1,4),再由h(10),h(20),以此往复
// 结果
function fn(x, y, z) {
  console.log(x + y + z)
  return x + y + z
}
function curry(fn) {
  const arg1 = Array.prototype.slice.call(arguments, 1) // [1,2]
  return function () {
    const arg2 = [...arguments] // [3]
    const args = [...arg1, ...arg2] // [1,2,3],全参数齐了

    if (args.length >= fn.length) {
      return fn(...args)
    } else {
      return curry.call(null, fn, ...args)
    }
  }
}

const g = curry(fn, 1)
// g(2, 10)
// g(3, 10)
// g(4, 10)
// g(4, 20)
const h = curry(g, 4)
// h(10)
// h(20)


// 简化
function curry(fn, ...args) {
  return args.length >= fn.length ? fn(...args) : curry.bind(null, fn, ...args)
}


10.AJAX

/**
 * AJAX
 *
 * 是一种技术,是浏览器开放给JS的
 * 异步JavaScript和XML
 */

// const server_url = "/server"
const server_url = "./herolist.json"
const xhr = new XMLHttpRequest()

// 创建请求
xhr.open("GET", server_url)

// 状态监听
xhr.onreadystatechange = function () {
  if (xhr.readyState !== 4) return
  if (xhr.status === 200) {
    console.log("success,连接成功", this.response)
  } else {
    console.log("error,连接失败", this.statusText)
  }
}

// 异常处理
xhr.onerror = function () {
  console.error("错误", this.statusText)
}

// 设置请求头
xhr.responseType = "json"
// xhr.setRequestHeader("Accept", "application/json")

// 发送请求
xhr.send(null)

11.promise封装ajax请求

function getJson(params) {
  const myPromise = new Promise((resolve, reject) => {
    const server_url = "./herolist.json"
    const xhr = new XMLHttpRequest()
    // 创建请求
    xhr.open("GET", server_url, true)

    // 状态监听
    xhr.onreadystatechange = function () {
      if (xhr.readyState !== 4) return
      if (xhr.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")

    // 发送请求
    xhr.send(null)
  })

  return myPromise
}

12.深拷贝

/**
 * 深拷贝(包含循环引用问题)
 *
 *
 * 投机取巧的方法:JSON序列化,无法解决循环引用问题
 * const newObj = JSON.parse(JSON.stringify(obj)) // Converting circular structure to JSON
 *
 *
 * 正统写法:
 * // Maximum call stack size exceeded,无限递归
 * 解决方法:
 * 缓存cache,以闭包的形式
 *
 *
 * 缓存的什么?==》一个对象对应的一个克隆结果的map结构,本质上是用空间换时间
 * 优化:用weakMap取代map,有利于垃圾回收,
 */
 
const obj = {
  arr: [1, 2],
  a: 4,
}
obj.sub = obj
obj.arr.push(obj)

function deepClone(obj) {
  const cache = new WeakMap()

  function _deepClone(obj) {
    if (obj === null || typeof obj !== "object") {
      return obj
    }

    if (cache.has(obj)) {
      return cache.get(obj)
    }
    const res = Array.isArray(obj) ? [] : {}
    cache.set(obj, res)
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        res[key] = _deepClone(obj[key])
      }
    }
    return res
  }

  return _deepClone(obj)
}

const newObj = deepClone(obj)

console.log(newObj.arr !== obj.arr)
console.log(newObj.sub !== obj.sub)
console.log(newObj.arr[2] !== obj)
console.log(newObj.arr[2] === newObj)

13.打乱数组

/**
 * 打乱数组、洗牌函数
 *
 *
 * 笔记:
 * 难点1:生成一个随机数,在数组的索引值范围内
 * Math.round(Math.random() * (arr.length - 1))
 */

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const randomNum = Math.round(Math.random() * (arr.length - 1))

function shuffle(arr) {
  let _arr = arr.slice()

  for (let i = 0; i < _arr.length; i++) {
    ;[_arr[i], _arr[randomNum]] = [_arr[randomNum], _arr[i]]
  }

  return _arr
}

const newArr = shuffle(arr)
console.log(newArr)

14.数组扁平化

/**
 * 数组扁平化
 */

const arr = [1, 2, [3, 4], [5, [6, 7]]]

function myFlat(arr) {
  let result = []

  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      result = [...result, ...myFlat(arr[i])]
    } else {
      result.push(arr[i])
    }
  }

  return result
}

console.log(myFlat(arr))

15.手写flat

/**
 * flat手写===数组扁平化
 *
 * 不同的是:
 * 1,手写flat要注意写在Array.prototype上
 * 2,用this指向实例对象
 */

const arr = [1, 2, [3, 4], [5, [6, 7]]]

Array.prototype.myFlat = function () {
  let result = []

  for (let i = 0; i < this.length; i++) {
    if (Array.isArray(this[i])) {
      result = [...result, ...this[i].myFlat()]
    } else {
      result.push(this[i])
    }
  }
  return result
}

const newArr = arr.myFlat()
console.log(newArr)

16.实现push

/**
 * 实现数组的push
 *
 * 笔记:
 * 实现简单,但是要注意push的返回值是当前数组的长度,在myPush中要注意返回值
 */

const arr = [1, 2]

Array.prototype.myPush = function (params) {
  this[this.length] = params
  return this.length
}

arr.myPush(3)
arr.myPush(4)

17.实现filter

/**
 * 实现数组的filter
 */

const arr = [1, 2, 3, 4, 5]

Array.prototype.myFilter = function (fn) {
  const result = []
  for (let i = 0; i < this.length; i++) {
    fn(this[i]) && result.push(this[i])
  }

  return result
}

const res = arr.myFilter((e) => e >= 2)
console.log(res)

18.实现map

Array.prototype.myMap = function (fn) {
  const result = []
  for (let i = 0; i < this.length; i++) {
    result.push(fn(this[i]))
  }
  return result
}

19.实现add(1)(2)(3)(4)

/**
 * 实现+add(1)(2)(3)(4)
 *
 *
 * 笔记:返回的应该是一个函数,为什么能打印值呢?==》改写toString
 */

function add(x) {
  let num = x
  const tmp = function (y) {
    num += y
    return tmp
  }

  Function.prototype.toString = function () {
    return num
  }

  return tmp
}

console.log(+add(1)(2))

20.promise异步加载图片

/**
 * promise实现图片的异步加载
 */

const loadImg = function (url) {
  return new Promise((resolve, reject) => {
    let img = new Image()
    img.src = url
    img.onload = () => {
      resolve()
    }
    img.onerror = (err) => {
      reject(err)
    }
  })
}

21.发布订阅模式-简易版

/**
 * 手写发布-订阅模式
 */

class eventBus {
  // 1,定义事件容器
  constructor() {
    this.handlers = {}
  }

  // 2,订阅者,添加事件方法,参数:事件名,事件方法
  on(type, handler) {
    if (!this.handlers[type]) {
      this.handlers[type] = []
    }
    // 存入事件
    this.handlers[type].push(handler)
  }

  // 发布者
  emit(type, params) {
    // 没有注册事件,抛出错误
    if (!this.handlers[type]) {
      return new Error("事件未注册")
    }

    this.handlers[type].forEach((element) => {
      element(params)
    })
  }

  off(type, handler) {
    if (!this.handlers[type]) {
      return new Error("事件未注册")
    }

    const index = this.handlers[type].findIndex((e) => e === handler)
    if (index === -1) {
      return new Error("不存在该事件")
    } else {
      this.handlers.splice(index, 1)
    }
  }
}

22.Object.defineProperty

/**
 * Object.defineProperty
 */

const obj = {
  name: "a",
  age: 18,
  sex: "man",
}

const p = {}

for (const key in obj) {
  if (Object.hasOwnProperty.call(obj, key)) {
    Object.defineProperty(p, key, {
      get() {
        console.log("读取")
        return obj[key]
      },
      set(val) {
        console.log("修改")
        obj[key] = val
      },
    })
  }
}

23.proxy

/**
 * Proxy
 */

const obj = {
  name: "a",
  age: 18,
  sex: "man",
}

const p = new Proxy(obj, {
  get(target, propName) {
    console.log("读取", propName)
    return Reflect.get(target, propName)
  },
  set(val) {
    console.log("修改", val)
    return Reflect.set(target, propName, val)
  },
  deleteProperty() {
    console.log("删除")
    return Reflect.defineProperty(target, propName)
  },
})

24.实现路由

/**
 * 实现路由
 */
class Route {
  constructor() {
    // 路由存储对象
    this.Route = {}
    // 当前hash
    this.currentHash = ""
    // 绑定this,避免监听时this指向改变
    this.freshRoute = this.freshRoute.bind(this)
    // 监听
    window.addEventListener("load", this.freshRoute, false)
    window.addEventListener("hashchange", this.freshRoute, false)
  }
  // 存储
  storeRoute(path, cb) {
    this.Route[path] = cb || function () {}
  }
  // 更新
  freshRoute() {
    this.currentHash = location.hash.slice(1) || "/"
    this.Route[this.currentHash]()
  }
}

25.用settimeout实现setInterval

/**
 * setTimeout实现interval
 */

function mySetTimeout(fn, timeout) {
  // 控制是否继续执行
  let timer = { flag: true }

  function interval() {
    if (timer.flag) {
      fn()
      setTimeout(interval, timeout)
    }
  }
  setTimeout(interval, timeout)
  return timer
}

function fn1() {
  console.log(1)
}
mySetTimeout(fn1, 1000)

26.setInterval实现setTimeout

/**
 * setInterval实现setTimeout
 */

function mySetInterval(fn, timeout) {
  const timer = setInterval(() => {
    fn()
    clearInterval(timer)
  }, timeout)
}

function fn1() {
  console.log(1)
}
mySetInterval(fn1, 1000)

27.实现jsonp

/**
 * 实现JSONP
 *
 *
 * 概念:解决跨域的古老方法
 * 为什么:同源策略中,浏览器对标签的跨域请求限制较小
 *
 *
 * 笔记:
 * 1,准备一个回调函数响应数据
 * 2,动态创建script标签
 */
function callback(params) {
  console.log(params) // ==>
}

function myJsonp(src) {
  const script = document.createElement("script")
  script.src = src
  script.type = "text/javascript"
  script.onload = function () {
    script.remove()
  }
  document.body.append(script)
}

const oBtn = document.querySelector(".btn")

oBtn.onclick = function () {
  myJsonp()
}

28.获取url的参数并转换成对象

/**
 * 获取url的参数并转换成对象
 *
 * 笔记:
 * 考察正则,只要能正确截取就没有难度
 * 延伸:decodeURIComponent这个api
 */

function getQuery() {
  let res = {}
  const queryStr = window.location.search
  const reg = /([^&=?]+)=([^&]+)/g
  const found = queryStr.match(reg)
  if (found) {
    found.map((e) => {
      e.split
      const arr = e.split("=")
      res[arr[0]] = arr[1]
    })
  }
  return res
}

const res = getQuery(url)
console.log(res)

29.数组去重

/**
 * 数组去重
 *
 * 1,new Set
 *
 * 2,filter+indexOf查找
 *
 * 3,for循环includes
 *
 * 4,双层for循环嵌套+splice
 */

// filter+indexOf
function myFilter(arr) {
  return arr.filter((item, index, arr) => {
    return arr.indexOf(item, 0) === index
  })
}