JavaScript手写(持续更新)

16 篇文章 0 订阅
10 篇文章 0 订阅

类型判断

主要是利用 Object.prototype.toString.call() ,其中toString方法返回反映这个对象的字符串。

如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中 type 是对象的类型。以下代码说明了这一点:

var o = new Object();
o.toString();  // returns [object Object]

具体查看:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/toString

不直接使用 obj.toString() 的原因:
在js中所有的对象类型(Array,Date,Function等)都是Object的实例,Object有一个toString方法也被这些实例继承下来,但是这些实例都无一例外的重写了toString方法,根据原型链的知识,直接调用toString,调用的只是重写过后的toString方法,所以也就不会像Object那样返回带有类型的字符串了。

自定义方法:

function typeOf(obj) {
  return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase()
}

new关键字执行的五个步骤,实现new

function mynew(Fn, ...args) {
    // 1. 创建一个新对象
    const obj = {}
    // 2. 修改新对象原型指向为构造函数原型对象
    Object.setPrototypeOf(obj, Fn.prototype)
    // obj.__proto__ = Fn.prototype
    // 3. 将this指向为新对象
    // 4. 执行构造函数内部代码
    const res = Fn.apply(obj, args) // 同时接收执行的结果
    // 5.根据返回值判断 
    return res instanceof Object ? res : obj
}

数组扁平化

let arr = [1,2,[3,4],[5,6,[7,8,9]]]
console.log(arr.flat(Infinity))

console.log(arr.toString().split(',').map(Number))
console.log(arr.join().split(',').map(Number))

简单实现flat()

// 利用递归
Array.prototype.flatten = function () {
  let res = [], len = this.length 
  for (let i = 0; i < len; i++) {
    if (Array.isArray(this[i])) { 
      res = res.concat(this[i].flatten())
    } else res.push(this[i])
  }
  return res
}

// reduce + concat 无限递归
const flatten = (arr) => {
  return arr.reduce(
    (acc, cur) => (Array.isArray(cur) ? acc.concat(flatten(cur)) : acc.concat(cur)),
    [] 
  )
}

深拷贝

简单的实现方式:

function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj))
}

局限很大,没法clone函数、symbol等。


call、apply、bind

手写call

Function.prototype.mycall = function(context) {
    // 获取上下文ctx,同时确保传入的上下文参数为null或undefined时替换为指向全局对象
    const ctx = context || globalThis  // 浏览器环境下全局对象为window
    // 给这个上下文添加一个函数本身的属性方法
    ctx.fn = this
    // 获取参数
    const args = [...arguments].slice(1)
    // 调用函数,因为是ctx.fn()的形式,这个fn的this自然指向ctx
    const res = ctx.fn(...args)
    // 删除这个临时使用的fn属性方法,因为是同一个内存地址,防止污染这个传入的对象
    delete ctx.fn
    // 返回结果
    return res
}

foreach、map、filter

手写foreach()

因为是第一个,所以再说下foreach的几个特性:

myMap.forEach(callback[, thisArg])

  • forEach 方法将对 Map 中真实存在的每一个元素执行一次参数中提供的回调函数,它不会对任何已经被删除的元素执行调用。然而,它还会对键存在而值为 undefined 的元素执行调用。
  • callback 函数有三个参数:
    • value - 元素的值
    • key - 元素的键
    • Map - 当前正在被遍历的对象
  • 如果参数 forEach 带有一个thisArg参数,在调用的时候,这个参数将传给 callback 函数作为其 this 的值。否则,函数将默认使用 undefined 传给 callback 函数作为其 this 值。

  • forEach 仅仅是对 Map 对象中的每一个元素执行一遍 callback 函数,然后直接返回 undefined。

不多说,直接上代码:

Array.prototype.myforEach = function (fn, context) {
  // 确保第一个参数为函数
  if (typeof fn !== 'function') 
    throw new Error(fn + ' is not a function!')

  // 第二参数,指定this,如果没有则默认将undefined当做this
  const ctx = context || undefined   

  // 这里的this还是指向调用该方法的数组对象本身
  for (let i = 0; i < this.length; i++) {
    if (!(i in this)) continue
      // 注意这里判断元素不存在的写法,是不会跳过值为undefined和NaN的元素的
    fn.call(ctx, this[i], i, this)   // 用call指定this
  }
}

做个测试:

let arr = [1, , 3, 4, undefined, NaN]
arr.forEach((item, index, val) => {
  console.log(item, index, val[index])
})
console.log('---------------')
arr.myforEach((item, index, val) => {
  console.log(item, index, val[index])
})
/*
1 0 1
3 2 3
4 3 4
undefined 4 undefined
NaN 5 NaN
--------------
1 0 1
3 2 3
4 3 4
undefined 4 undefined
NaN 5 NaN
*/

手写map()

和foreach差不多,不过会返回一个执行回调函数后的新数组。这里不多说了。

Map的几个注意点:

  • Map生成的是一个新数组
  • 有第二参数,为指定的this,如果没有则默认this丢失
  • 对于稀疏数组,返回的仍然是稀疏数组(即map会保留“不存在”的元素)
Array.prototype.myMap = function (fn, context) {
  if (typeof fn !== 'function') 
      throw new Error(fn + ' is not a function!')

  let _arr = []
  const ctx = context || Object.create(null) // 继续用undefined也可以
  
  for (let i = 0; i < this.length; i++) {
    // 当调用的数组为稀疏数组时,要确保返回的数组也是稀疏的
    // 这里有两种写法:
    // 1. concat + push
    if (!(i in this)) _arr = _arr.concat([,])
    else _arr.push(fn.call(ctx, this[i], i, arr))  

    /* 2. continue + 索引添加元素
    if(!(i in this)) continue
    else _arr[i] = fn.call(ctx, this[i], i, arr)
    */
  }
  return _arr
}

做个测试:

let arr = [1, , 2, 3, undefined, NaN]
console.log( arr.map((val, index) =>  val * index) )   // [ 0, <1 empty item>, 4, 9, NaN, NaN ]
console.log( arr.myMap((val, index) =>  val * index) )   // [ 0, <1 empty item>, 4, 9, NaN, NaN ]

手写filter()

filter的注意点:

  • filter同样是创建一个新函数
  • 有第二参数,为指定的this,如果没有则默认this丢失
  • filter会直接跳过不存在的元素
Array.prototype.myFilter = function (fn, context) {
  if (typeof fn !== 'function') 
    throw new Error(fn + ' is not a function!')

  let _arr = []
  const ctx = context || Object.create(null)
  for (let i = 0; i <= this.length; i++) {
    if(!this[i]) continue  // 如果值为undefined、NaN或者不存在的时候直接跳过去
    if(fn.call(ctx, this[i], i, this)) _arr.push(this[i])
  }
  return _arr
}

做个测试:

let arr = [1, , 2, 3, undefined, NaN]
console.log(arr.filter((item, index) => item > index))  // [ 1, 3, 4 ]
console.log(arr.myFilter((item, index) => item > index))  // [ 1, 3, 4 ]

防抖

  function debounce(fn, delay = 500) {  // 一个要执行防抖的函数,以及延时参数,这里默认500ms
    // 在外部事先定义这个计时器,作为公共定时器变量,确保最后只会执行一个定时器,这样也就只会触发一次事件
    let timeout = null
    return function () {
      clearTimeout(timeout)  // 清除上一个定时器
      const ctx = this   // 保存this
      const args = arguments; // 参数列表,拿到event对象
        
      // 定义定时器,最后只会触发一次
      timeout = setTimeout(function () {  
        fn.apply(ctx, args)  // 利用apply,确保函数的this指向不变
          // 当然这里也可以直接使用箭头函数,这样这里的this就无需提前保存
      }, delay)
    }
  }

节流

一个普通版本的:

function throttle(fn, delay = 500) {
  let timeout = null
  return function () {
     // 如果已经有定时器了,直接返回不执行
    if (timeout) return
     // 如果没有,那么开始一个定时任务
    timeout = setTimeout((...args) => {
        // 
      fn.apply(this, args)
      timeout = null
    }, delay)
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值