前端进阶这些技能你是否已经Get了,查漏补缺

332 阅读6分钟

前言

又到了跳槽找工作的旺季,近几天整理了一些面试常考题,希望对你有所帮助,会的可以温习一下,不会的可以好好学习一下。欢迎点赞支持,共同学习成长!由于本文代码太多,掘金有总字符限制,分为2篇来写。

call 方法实现

实现详情

所有的方法都可以调用 call/apply/bind 方法, 它们是函数原型上的方法,因此需要挂载到Function.prototype

下面我们来动手实现一下我们自己的 call/apply/bind方法

  • 需要注意一下事项
  1. 如果第一个参数没有,那么在非严格模式下默认指向 window 或者 global
  2. 传入的第一个参数是 this 指向的对象,根据 this 绑定规则,我们知道 context.funfun 中的 this 被隐式绑定到 context 上,因此可以使用 context.fun(...args) 来执行函数
  3. 原本 context 并不存在 fun 属性,函数执行结束后删除 fun
  4. 返回函数执行结果
Function.prototype.myCall = function( ){

    const [ context, ...args ] = [ ...arguments ]
    // 判断传入的第一个参数是否存在,如不存在,在非严格模式下根据运行环境 取window 或者 global
    if( !context){
        context = typeof window !== 'undefined' ? window : global;
    }
    // this的指向是当前函数fun
    context.fun=this

    let result = context.fun(...args)
    //原本context并不存在fun属性,函数执行结束后删除fun
    delete context.fun

    return result
}

apply 的实现

实现详情

callapply 的区别

call 可以有2个以上参数, apply 最多有两个参数,且第二个参数只能是数组或类数组

  • fun.call(context,arg1,arg2,arg3,...) : 第一个参数是 this 指向的对象,其他参数依次传入
  • fun.apply(context,[args]) : 第一个参数是 this 指向的对象,第二个参数是数组或者类数组
Function.prototype.myApply = function (context,args){

    // 判断传入的第一个参数是否存在,如不存在,在非严格模式下根据运行环境 取window 或者 global
    if(!context){
        context = typeof window !== 'undefined' ? window : global;
    }
    // this的指向是当前函数fun
    context.fun = this;

    let result
    //根据第二个参数返回不同的执行结果
    if(!args){
        result = context.fun()
    }else{
        result = context.fun(...args)
    }
    //原本context并不存在fun属性,函数执行结束后删除fun
    delete context.fun
    return result
}

bind 实现

实现详情

call/apply 不同 bind 返回的是一个函数

Function.prototype.myBind = function(){
    let [context,args] = [...arguments]
    let _this = this;
    return function Fun(){
        //考虑使用new的情况
        if(this instanceof Fun){
            return new _this(...args,...arguments)
        }
        return _this.apply(context,args.concat(arguments))
    }
}

深拷贝/浅拷贝区别,实现深拷贝

实现详情

深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝

深拷贝

深拷贝对于基本变量是直接复制其值,对于非基本变量则层层递归至基本变量后再复制。深拷贝后的结果与原始对象完全隔离互不影响,修改任意一个对象都不会影响另一个对象。

浅拷贝

浅拷贝是将对象的第一层属性进行复制,如果属性值是引用类型时,复制其地址;当引用地址指向的值发改变时,浅拷贝的结果也跟着变化。

可以通过扩展运算符 ... 或者 Object.assign 等实现浅拷贝

let foo = {
    a: 1,
    b: 2,
    c: 3,
    d: ['e','f','g']
};

let foo1 = Object.assign({},foo) 
let foo2 = {...foo} 

foo.a = 5
foo.d.push('h')

console.log(foo)   // { a: 5, b: 2, c: 3, d: [ 'e', 'f', 'g', 'h' ] }
console.log(foo1)  // { a: 1, b: 2, c: 3, d: [ 'e', 'f', 'g', 'h' ] }
console.log(foo2)  // { a: 1, b: 2, c: 3, d: [ 'e', 'f', 'g', 'h' ] }

可以看出当第一层的属性值是基本类型时,修改原始对象不会影响新的对象,如果第一层的属性值是复杂数据类型时,新对象和原始对象的该属性值指向同一个内存地址,其中一个发生变化就会影响另一个;

深拷贝实现

对于复杂类型的数据实现深拷贝,需要层层深拷贝;而浅拷贝只拷贝一层

  • 基本类型变量直接复制
  • 非基本类型变量则递归至基本类型变量后再复制
  • 深拷贝后对象与原对象完全隔离,互不影响

最简单深拷贝实现: JSON.parse(JSON.stringify(target))

JSON.parse(JSON.stringify(target)) 虽然简单,但部分情况下无法拷贝:

  1. 会忽略 undefined
  2. 会忽略 Symbol
  3. 不能处理正则 RegExp
  4. 不能正确处理 Date类型数据
  5. 对象属性是function时无法拷贝
  6. 原型链上的属性无法拷贝

自己动手实现一个简单的 deepClone 函数,需要注意以下几点:

  • 如果是基本类型数据直接返回
  • 如果是复杂数据类型则递归
  • 如果是 RegExp 或者 Date 类型,返回对应的类型
  • 考虑循环引用问题
  • 考虑Symbol

function deepClone(target,wmap = new WeakMap()){
    if(target ===null || typeof target !== 'object'){
        //如果是基本类型直接返回
        return target;
    }
    if(target instanceof RegExp) return new RegExp(target)
    if(target instanceof Date) return new Date(target)
    if(wmap.has(target)){ //考虑引用
        return wmap.get(target)
    }
    let res = new target.constructor(); //如果target是Array 则 target.constructor为 [Function: Array] 
    //如果target是Object 则 target.constructor为 [Function: Object]
    wmap.set(target,res);
    let keys=[...Object.getOwnPropertyNames(target),...Object.getOwnPropertySymbols(target)]
    for(let key in keys){
        //递归
         res[key] = deepClone(target[key],wmap)
    }
    return res
}

new 的实现原理

实现详情
  1. 创建一个空的对象;
  2. 将新建对象作为构造函数的实例,构造函数中的this指向这个空对象
  3. 执行构造函数方法,将构造函数中的属性和方法添加到引用的对象上
  4. 如果构造函数执行的结果不是一个对象,则返回新建的对象,否则返回执行结果
function myNew(){
    //创建一个新对象
    let target = {}
    //解构出 构造函数及参数
    const [ constructor, ...args ] = [ ...arguments ]
    //将创建的对象作为构造函数的实例
    target.__proto__ = constructor.prototype
    //执行构造函数,将构造函数的属性和方法添加到新建的对象上
    let result = constructor.myApply(target, args)
    //如果构造函数返回的不是一个对象 则返回新建的对象
    return  result instanceof Object ? result : target
}
  • 验证 New 实现结果
function test(name){
    this.name=name
}
let date = myNew(test,2134474774)
let date2 = new test(2134474774)
console.log(date,date2)

JSONP 实现

实现详情

JSONP为前端实现跨域的一种方式,其原理是利用 htmlscript 标签的 src 属性不受浏览器同源策略束缚,可以任意获取服务器脚本并执行。

JSONP正是利用这一特性,动态创建script标签来实现跨域的。

  • 缺点

是只支持get请求,并且也需要后端做相应的处理

  • 实现步骤
  1. 创建script标签
  2. 创建挂载到window上的callback方法
  3. 拼接URL并赋值给script的src属性
  4. 服务端处理请求,并把数据放入前端传来的callback回调函数中返回给前端
  5. 前端解析并执行服务端返回的方法调用
function jsonp({url,params,callback}) {
    return new Promise((resolve,reject)=>{
        //创建script标签
        let script = document.createElement('script')
        //把回调函数挂载到window上
        window[callback] = function (data) {
            resolve(data)
            //执行结束,删除之前创建的script标签
            document.body.removeChild(script)
        }

        //拼接url
        params = {...params,callback}
        let fields=[]
        for(let key in params){
            fields.push(`${key}=${params[key]}`)
        }
        script.src = `${url}?${fields.join('&')}`

        document.body.appendChild(script)
    })
}
  • 测试验证需要写后端服务
function crosJsonp(data) {
    console.log(data)
}

jsonp({
    url:'http://localhost:8080/crosJsonpDemo',
    params:{
        dev:'test',
        fileName:'json'
    },
    callback:'crosJsonp'
}).then(res=>{
    console.log(res)
})

instanceOf 方法实现

实现详情
function instanceOf(suberType,superType) {
    var left = suberType.__proto__;
    var right = superType.prototype;
    while(true){
        if(left === null){
            return false
        }
        if(left === right){
            return true
        }
        left = left.__proto__
    }
}

function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();//JavaScript 原型继承

var foo = new Foo();

console.log(instanceOf(foo,Foo),instanceOf(foo,Aoo)) // true true