每天6道JavaScript手写 - day 1

94 阅读4分钟

1、Object.create()

方法一

 Object.prototype.myCreate = function(proto, propertiesObject=undefined){
     if( typeof proto!== 'object' && typeof proto!=='function' ){
         throw new TypeError(`The passed in prototype must be an object or null: ${proto}`)
     }
     if( propertiesObject === null ){
         throw new TypeError(`Cannot convert undefined or null to object`)
     }
 ​
     const newObj = {}
     Object.setPrototypeOf(newObj, proto) // 使用Object.setPrototypeOf()方法设置原型
     
     if( typeof propertiesObject != 'undefined' ){
         Object.defineProperties(newObj, propertiesObject)
     }
     
     return newObj
 }

方法二

我们使用构造函数实现一下

 Object.prototype.myCreate = function(proto, propertiesObject=undefined){
     if( typeof proto!== 'object' && typeof proto!=='function' ){
         throw new TypeError(`The passed in prototype must be an object or null: ${proto}`)
     }
     if( propertiesObject === null ){
         throw new TypeError(`Cannot convert undefined or null to object`)
     }
     
     function fn(){};
     fn.prototype = proto;
     fn.prototype.constructor = fn
     if( typeof propertiesObject != undefined ){
         Object.defineProperties(fn, propertiesObject)
     }
 ​
     return new fn()
 }

检验:

2、instanceof

 /*
 * @param {Object} obj 需要判断的数据
 * @param {Object} constructor 这里请注意
 * @return {Boolean}
 **/
 function myInstanceof(obj, constructor){
     if (!['function', 'object'].includes(typeof obj) || obj === null) return false
     let objProto = Object.getPrototypeOf(obj)
     while(true){
         if(!objProto) return false
         // 将 对象的隐式原型 和 构造函数的显式原型 比较
         if(objProto === constructor.prototype) return true
         
         objProto = Object.getPrototypeOf(objProto)
     }
 }

image.png

3、new

 /*
 * @param {Function} fn 构造函数
 * @return {*}
 **/
 function myNew(fn, ...args){
     if(typeof fn !== 'function'){
         return new TypeError('fn must be a function')
     }
     
     // 先创建一个对象
     let obj = Object.create(fn.prototype)
     // 通过apply让this指向obj, 并调用执行构造函数
     let res = fn.apply(obj, args)
     
     return (res instanceof Object) ? res : obj
 }

image.png

4、类型判断

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

image.png

5、防抖节流

防抖

防抖是指,短时间频繁触发fn,只执行最后一次,这样可以有效提高性能,减少服务器压力

也可以这样表述:函数在 n 秒后再执行,如果 n 秒内被触发,重新计时,保证最后一次触发事件 n 秒后才执行。

/*
* @param {Function} callback 回调函数
* @param {Number} delay 回调函数在delay ms 后执行
* @param {Boolean} immediate 是否立即执行回调
**/
function debounce(callback, delay=300, immediate=false){
	let time = null
    // 返回一个闭包
    return (...args) => {
        // 存在定时器则清空, 重新计时
        if(timer){
            clearInterval(timer)
            timer=null
        }
        // 若不立即执行
        if(!immediate){
            timer = setTimeout(() => {
                callback.apply(this, args)
            }, delay)
        }else{
            // 立即执行
            // timer 为空则代表此时不在delay中, 可以立即执行
            let flag = !timer
            flag && callback.apply(this, args)
            timer = setTimeout(() => {
                timer = null
            }, delay)
        }
    }
}

简单版:

function debounce(callback, delay=300){
    let timer = null
    return (...args) => {
        if(timer){
            clearInterval(timer)
            timer = null
        }
        timer = setInterval(() => {
            callback.apply(this, args)
        }, delay)
    }
}

节流

函数在一段时间内只会执行一次,如果多次触发,那么忽略执行

/*
* @param {Function} callback 执行函数
* @param {Number} delay 
*/
function throttle(callback, delay){
    // 上一次执行时的时间
    let start = Date.now()
    return function(){
        // 这一次触发时的时间
        const now = Date.now()
        // 如果相邻两次触发时间超过设置的delay, 说明已经过了限制时间,可以执行函数
        if(now - start >= delay){
            // 更新start, 方便下次对比
            start = now
            // 执行函数
            return callback.apply(this, arguments)
        }
    }
}

6、修改this指向

方法参数返回值作用
callthisArg, arg1, arg2, ...,注意,call接收的参数是一个参数列表函数返回值调用一个函数,将其 this 值设置为提供的值,并为其提供指定的参数。
applythisArg, [arg1, arg2, ...],请注意这里,apply接收的参数是一个参数数组函数返回值调用一个函数,将其 this 值设置为提供的值,并以数组形式为其提供参数。
bindthisArg, arg1, arg2, ...新函数创建一个新函数,当调用这个新函数时,它的 this 值被绑定到提供的值,并且它的参数序列前置指定的参数。

可以使用apply()call()方法以新创建的对象为上下文执行构造函数

开始之前,因为这三个都是会改变this的指向,而this指向是js中很重要的一个部分,所以我们停一停,复习一下this指向,以便更好地认识和实现这仨兄贵

this指向

1、构造函数通过new关键字构造对象时, this指向被构造出的对象

 function Game(name, type){
     this.name = name;
     this.type = type;
     Game.prototype.showThis = function(){
         console.log(this)
     }
 }
 var dmc = new Game('DMC', 'ACT')
 dmc.showThis()

image.png

可以看到指向的是被构造出来的dmc

2、全局作用域中,this指向window

image.png

3、谁调用方法,this指向谁

4、普通函数非通过其他人执行,this指向window

 function fn(){
     console.log(this)
 }
 fn()   //window
 ​
 function fn1(){
     console.log(this)  // window
     function fn2(){
         console.log(this)
     }
     fn2()  // window
 }
 fn1()
 ​
 // 被赋值后再调用
 var obj = {
     fn: function(){
         console.log(this)
     }
 }
 var fn3 = obj.fn
 fn3()   // window

5、数组里面的函数,按照数组索引取出运行时,this指向该数组,其实就是数组调用函数,所以指向数组

 function fn(){
     console.log(this)
 }
 var arr = [fn1]
 arr[0]()  // [fn] --> 就是arr

6、=>函数中的this指向他的父级非=>函数this

callapply都不能改变=>函数的this

以上可以浓缩为:非=>函数的this指向它执行时的上下文对象,=>函数的this指向它的父级非=>函数的this

7、callapplybind可以改变函数运行时候函数中this的指向

节流防抖中可以使用这种方式实现

call

call的主要用途:

  • 直接调用函数
  • 可改变函数内部的this
  • 可以实现继承

使用上面的例子:

  • fn() 相当于 fn.call(null)
  • fn(fn2)相当于fn.call(null, fn2)
  • obj.fn()相当于obj.fn.call(obj)

手写:

 // context - 传入的执行上下文对象, 不传或者传null就默认为window
 // args - 传入的参数列表
 Function.prototype.myCall = function(context, ...args){
     // this只有指向函数才能执行
     if(typeof this != 'function'){
         return new TypeError(`type error! ${this} is not a function`)
     }
     // 获取传入的执行上下文context, 若未传入则指向window, 就是例如fn.call()、fn.call(null)这种
     context = context || globalThis // 使用globalThis是考虑到web和node下的全局this不一样
     // 缓存this到传入的执行上下文对象去执行, 这一步相当于改变了this指向, 即指向了context
     context.fn = this
     // this指向改变后, 传入参数, 原fn在执行上下文对象运行并缓存此时fn在context下运行得到的返回值
     const res = context.fn(...args)
     // 用完就删除执行上下文中新增的this, 取消绑定
     delete context.fn
     // 返回原fn在context下运行得到的返回值
     return res
 }

apply

apply的用途:

  • 调用函数,改变内部的this指向
  • 参数必须为数组
  • 主要应用于数组类
 // apply和call很像,就后面传参有点改动
 Function.prototype.myApply = function(context, args){
     if(typeof this != 'function'){
         return new TypeError(`type error! ${this} is not a function`)
     }
     context = context || globalThis
     context.fn = this
     // 因为传入的是数组, 所以如果没有参数就不要展开传进fn执行
     const res = args ? context.fn(...args) : context.fn()
     delete context.fn
     return res
 }

bind

  • 改变原函数内部指向
  • 返回改变指向后的拷贝

这里因为返回的是拷贝,所以我们要return的是一个function

 Function.prototype.myBind = function(context, ...args1){
     if(typeof context != 'undefined'){
         return new TypeError(`type error! ${this} is not a function`)
     }
     context = context || globalThis
     context.fn = this
     return function(...args2){
         const res = context.fn(...args1, ...args2)
         delete context.fn
         return res
     }
 }