前端面试手撕编程之ES+算法

目录

ES6

防抖 

节流

防抖、节流应用

改变this

call

typeof this !== 'function'

context = context || window

context._this = this

delete context._this

bind: return _this.apply(context, [...arguments].slice(1));

深拷贝

!arr|| arr == null || typeof arr != 'object'

arr instanceof Array ?  [] :  {}

for (const key in arr)

result[key] = cloneDeep(arr[key])

setTimeout()

倒计时

setTimeout模拟实现setInterval

setInterval模拟实现setTimeout

new :Fn=[...arguments].shift()

*寄生组合式继承:call,create,constructor

Object.defineProperty(obj, prop, descriptor)

Object.create 

Object.freeze 

Object for of

*instance of

 算法

合法的URL

千位分割:num.slice(render, len).match(/\d{3}/g).join(',')

ES6

防抖 

触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,会重计算函数执行时间。

function debounce(fun,time) {
        let flag // 定义状态
        return function () {
            clearTimeout(flag)// 在执行之前 清除 定时器的 flag 不让他执行
            flag = setTimeout(() => {
                fun.call(this,arguments)//拿到正确的this对象,即事件发生的dom
            }, time)
        }
    }

节流

连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。

function throttle(fun, time) {
        let flag // 定义一个空状态
        return function () { // 内部函数访问外部函数形成闭包
            if (!flag) { // 状态为空执行
                flag = setTimeout(() => {
                    fns.apply(this, arguments) // 改变this指向 把 event 事件对象传出去
                    flag = null // 状态为空
                }, time)
            }
        }
      }

防抖、节流应用

防止某一时间频繁触发

  • 防抖debounce:time内只执行一次
    • search搜索联想,用户在不断输入值时,用防抖来节约请求资源
    • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
  • 节流throttle: 间隔time执行
    • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
    • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

改变this

call

  1. typeof this !== 'function'
  2. context = context || window
  3. context._this = this
  4. delete context._this
    // 给function的原型上面添加一个 _call 方法
    Function.prototype._call = function (context) {
        //  判断调用者是否是一个函数  this 就是调用者
        if (typeof this !== 'function') {
           throw new TypeError('what is to be a function')
        }
        // 如果有 context 传参就是传参者 没有就是window
        context = context || window
        // 保存当前调用的函数
        context._this = this   
        // 截取传过来的参数
        /*
          arguments
                 a: 1
                 fn: ƒ fns()
        */
        // 通过 slice 来截取传过来的参数
        const local = [...arguments].slice(1)
        // 传入参数调用函数
        let result = context._this(...local)
        // 删属性
        delete context._this
        return result
    }

    let obj = { a: 1 }
    function fns(a, b) {
        console.log(a, b);
        console.log(this)
    }
    fns._call(obj, 23, 555)

bind: return _this.apply(context, [...arguments].slice(1));

深拷贝

  1. !arr|| arr == null || typeof arr != 'object'

  2. arr instanceof Array ?  [] :  {}

  3. for (const key in arr)

  4. result[key] = cloneDeep(arr[key])

 function cloneDeep(arr = {}) {
        // 终止递归 
        if (!arr|| arr == null || typeof arr != 'object' ) return arr
        // 用 instanceof 判断原型链上是否有该类型的原型 是 Array => [] ! Arrays =>{}
        let result=arr instanceof Array ?  [] :  {}
        // forin 循环对象的key值
        for (const key in arr) {
            //  对象 key 赋值 result
            result[key] = cloneDeep(arr[key])
        }
        return result
   }

setTimeout()

倒计时

​//返回值timeoutID是一个正整数,表示定时器的编号。
let timeoutID = scope.setTimeout(function[, delay]),//delay表示最小等待时间,真正等待时间取决于前面排队的消息
clearTimeout(timeoutID) //取消该定时器。

function timedCount() {
			c -=1;
			if(c===0){
				clearTimeout(t);
				return;
			}
			t = setTimeout(function() {
				timedCount()
			}, 1000);
}
 

setTimeout模拟实现setInterval

// 使用闭包实现
function mySetInterval(fn, t) {
  let timer = null;
  function interval() {
    fn();
    timer = setTimeout(interval, t);
  }
  interval();
  return {
    // cancel用来清除定时器
    cancel() {
      clearTimeout(timer);
    }
  };
}

setInterval模拟实现setTimeout

function mySetTimeout(fn, time) {
  let timer = setInterval(() => {
    clearInterval(timer);
    fn();
  }, time);
}

// 使用
mySetTimeout(() => {
  console.log(1);
}, 2000);

new :Fn=[...arguments].shift()

"_new"函数,该函数会返回一个对象,

该对象的构造函数为函数参数、原型对象为函数参数的原型,核心步骤有:

  1. 创建一个新对象
  2. 获取函数参数
  3. 将新对象的原型对象和函数参数的原型连接起来
  4. 将新对象和参数传给构造器执行
  5. 如果构造器返回的不是对象,那么就返回第一个新对象
const _new = function() {
    const object1 = {}
    const Fn = [...arguments].shift()
    object1.__proto__ = Fn.prototype
    const object2 = Fn.apply(object1, arguments)
    return object2 instanceof Object ? object2 : object1
}

*寄生组合式继承:call,create,constructor

通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:
1. 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
2. 给"Chinese"构造函数的原型上添加"getAge"函数,该函数返回调用该函数对象的"age"属性

  1. 在"Human"构造函数的原型上添加"getName"函数
  2. 在”Chinese“构造函数中通过call函数借助”Human“的构造器来获得通用属性
  3. Object.create函数返回一个对象,该对象的__proto__属性为对象参数的原型。此时将”Chinese“构造函数的原型和通过Object.create返回的实例对象联系起来
  4. 最后修复"Chinese"构造函数的原型链,即自身的"constructor"属性需要指向自身
  5. 在”Chinese“构造函数的原型上添加”getAge“函数
function Human(name) {
    this.name = name
    this.kingdom = 'animal'
    this.color = ['yellow', 'white', 'brown', 'black']
}
Human.prototype.getName = function() {
    return this.name
}

function Chinese(name,age) {
    Human.call(this,name)//call函数借助”Human“的构造器来获得通用属性
    this.age = age
    this.color = 'yellow'
}

//返回的对象__proto__属性为对象参数的原型
Chinese.prototype = Object.create(Human.prototype)//使用现有的对象来作为新创建对象的原型
//修复"Chinese"构造函数的原型链,即自身的"constructor"属性需要指向自身
Chinese.prototype.constructor = Chinese

Chinese.prototype.getAge = function() {
    return this.age
}

Object.defineProperty(obj, prop, descriptor)

Object.create 

该函数创建一个新对象,使用现有的对象来提供新创建的对象的proto,核心步骤有:

  1. 创建一个临时函数
  2. 将该临时函数的原型指向对象参数
  3. 返回该临时对象的实例

Object.create法创建一个新对象,使用现有的对象来提供新创建的对象的proto。

const _objectCreate = proto => {
    if(typeof proto !== 'object' || proto === null) return

    const fn = function() {}
    fn.prototype = proto

    return new fn()
}

Object.freeze 

Object.freeze = writable: false + Object.seal = writable: false + Object.preventExtensions + configable: false

  • Symbol 类型作为 key 值的情况,也要冻结
  • 只冻结对象自有的属性(使用 for ... in 会把原型链上的可枚举属性遍历出来)。
  • 注意不可扩展性(不能添加新属性,使用 Object.preventExtensions() 或 Object.seal() 实现,同时也相当于把原型链冻结)。

key:

  1. Object.getOwnPropertyNames/Symbol
  2. forEach
  3. Object.defineProperty:configurable,writable
  4. Object.preventExtensions(object)
const _objectFreeze = object => {

    if(typeof object !== 'object' || object === null) {
        throw new TypeError(`the ${object} is not a object`)
    }

    const keys = Object.getOwnPropertyNames(object);
    const symbols = Object.getOwnPropertySymbols(object);
    
    [...keys, ...symbols].forEach(key => {
        Object.defineProperty(object, key, {
            configurable: false,
            writable: false,
        })
    })

    Object.preventExtensions(object)
}

Object for of

// 创建一个构造函数
function MyObject() {
  // 为构造函数的实例对象添加两个属性
  this.prop1 = 'Value 1';
  this.prop2 = 'Value 2';
}

// 在原型上添加一个自定义的迭代器方法
MyObject.prototype[Symbol.iterator] = function () {
  // 获取实例对象的所有属性名并存储在数组 keys 中
  const keys = Object.keys(this);
  let index = 0;

  // 返回一个迭代器对象,包含 next 方法
  return {
    next: () => {
      // 如果还有属性未遍历完
      if (index < keys.length) {
        // 获取当前属性名 key,并递增索引 index
        const key = keys[index++];
        // 返回包含当前属性名和对应值的对象,并设置 done 为 false 表示未遍历完
        return { value: [key, this[key]], done: false };
      } else {
        // 如果所有属性都已遍历完,返回 done 为 true 表示遍历结束
        return { done: true };
      }
    },
  };
};

// 创建一个实例对象
const instance = new MyObject();

// 使用 for...of 遍历实例对象的属性
for (const [key, value] of instance) {
  // 打印属性名和对应值
  console.log(key, value);
}

*instance of

  1. 获取首个对象参数的原型对象
  2. 获取Fn函数的原型对象
  3. 进入死循环,当两个参数的原型对象相等时返回true
  4. 当两个参数的原型对象不相等时获取首个对象参数原型的原型并且循环该步骤直到null时返回false
const _instanceof = (target, Fn) => {
    let proto = target.__proto__
    let prototype = Fn.prototype
    while(true) {
        if(proto === Fn.prototype) return true
        if(proto === null) return false
        proto = proto.__proto__
    }
}

 const _instanceof = (target, Fn) => {
     return Fn.prototype.isPrototypeOf(target);
          
}

 算法

合法的URL

URL结构一般包括协议、主机名、主机端口、路径、请求信息、哈希

  1. 域名不区分大小写:"www"子域名(可选)、二级域名、"com"顶级域名
  2. 只能包含字母(a-z、A-Z)、数字(0-9)和连字符(-)(但-不能再首尾)
https://www.bilibili.com/video/BV1F54y1N74E/?spm_id_from=333.337.search-card.all.click&vd_source=6fd32175adc98c97cd87300d3aed81ea
//开始:                     ^
//协议:                     http(s)?:\/\/
//域名:                     [a-zA-Z0-9]+-[a-zA-Z0-9]+|[a-zA-Z0-9]+
//顶级域名 如com cn,2-6位:   [a-zA-Z]{2,6}
//端口 数字:                (:\d+)?
//路径 任意字符 如 /login:   (\/.+)?
//哈希 ? 和 # ,如?age=1:    (\?.+)?(#.+)?
//结束:                      $
//     https://           www.bilibili                com    /video/BV1F54y1N74E  ?spm..            
/^(http(s)?:\/\/)?(([a-zA-Z0-9]+-[a-zA-Z0-9]+|[a-zA-Z0-9]+)\.)+([a-zA-Z]{2,6})(:\d+)?(\/.+)?(\?.+)?(#.+)?$/.test(url)

千位分割:num.slice(render, len).match(/\d{3}/g).join(',')

  const format = (n) => {
        let num = n.toString() // 拿到传进来的 number 数字 进行 toString
        let len = num.length // 在拿到字符串的长度
        // 当传进来的结果小于 3 也就是 千位还把结果返回出去 小于3 不足以分割
        if (len < 3) {
            return num
        } else {
            let render = len % 3 //传入 number 的长度 是否能被 3 整除
            if (render > 0) { // 说明不是3的整数倍
                return num.slice(0, render) + ',' + num.slice(render, len).match(/\d{3}/g).join(',')
            } else {
                return num.slice(0, len).match(/\d{3}/g).join(',')
            }
        }
    }

    let str = format(298000)
    console.log(str)

你可能感兴趣的:(前端手撕,前端面试,javascript,前端,开发语言)