0116面试题——bind/call/apply、new、类型判断、垃圾回收、强弱引用、内存泄露、请求合并

160 阅读4分钟

javascript

模拟实现

  • 实现bind,call,apply
// bind
Function.prototype.myBind = function (...args) {
  let _this = this
  let arr = args.slice(1)
  return function F(...params) {
    arr = arr.concat(params)
    // 返回的函数有可能以 new 的方式调用
    if (this instanceof F) {
    	return new _this(...arr)
    }
    return _this.apply(args[0], arr)
  }
}


// call
Function.prototype.myCall = function (context, ...args) {
    // 未传入context,则在window上执行
    if (context === undefined) {
    	return this(...args)
    }
    // 将 context 包装成对象
    context = new Object(context)
    context.fn = this
    let res = context.fn(...args)
    delete context.fn
    
    return res
}

// apply
Function.prototype.myApply = function (context, args) {
    // 未传入context,则在window上执行
    if (context === undefined) {
    	return this(...args)
    }
    // 将 context 包装成对象
    context = new Object(context)
    context.fn = this
    let res = context.fn(...args)
    delete context.fn
    
    return res
}
  • 实现new
function myNew (F, ...args) {
    let obj = {}
    obj.__proto__ = F.prototype
    let res = F.apply(obj, args)
    
    return typeof res==='object' and typeof res !== null ? res : obj
}

理论

  • Object.prototype.toString.call 是如何判断变量的类型的,讲原理
    • ES5
      • 对于 undefined 返回 [object Undefined]
      • 对于 Null 返回 [object Null]
      • 将 this 转为 Object
      • 取内部属性 [[class]]的值
      • 返回 [object [[class]] ]
    • ES6
      • 对于 undefined 返回 [object Undefined]
      • 对于 Null 返回 [object Null]
      • 令 O = Object(this)
      • 令 isArray = IsArray(O) ;
      • ReturnIfAbrupt(isArray) (如果 isArray 不是一个正常值,抛出一个错误,中断执行);
      • 如果 isArray 为 true , 令 builtinTag 为 'Array' ;
      • else ,如果 O is an exotic String object , 令 builtinTag 为 'String' ;
      • else ,如果 O 含有 [[ParameterMap]] internal slot, , 令 builtinTag 为 'Arguments' ;
      • else ,如果 O 含有 [[Call]] internal method , 令 builtinTag 为 Function ;
      • else ,如果 O 含有 [[ErrorData]] internal slot , 令 builtinTag 为 Error ;
      • else ,如果 O 含有 [[BooleanData]] internal slot , 令 builtinTag 为 Boolean ;
      • else ,如果 O 含有 [[NumberData]] internal slot , 令 builtinTag 为 Number ;
      • else ,如果 O 含有 [[DateValue]] internal slot , 令 builtinTag 为 Date ;
      • else ,如果 O 含有 [[RegExpMatcher]] internal slot , 令 builtinTag 为 RegExp ;
      • else , 令 builtinTag 为 Object ;
      • 令 tag 为 Get(O, @@toStringTag) 的返回值
      • ReturnIfAbrupt(tag) ,如果 tag 是正常值,继续执行下一步;
      • 如果 Type(tag) 不是一个字符串,let tag be builtinTag;
      • 返回由三个字符串 "[object", tag, "]" 拼接而成的一个字符串
      • 可以通过改变Symbol.toStringTag,来改变Object.prototype.toString的结果
      let obj = {}
      
      Object.defineProperty(obj, Symbol.toStringTag, {
          get: function() {
              return "newClass"
          }
      })
      
      console.log(Object.prototype.toString.call(obj)) // "[object newClass]"
      
      
  • JavaScript的垃圾回收机制是怎样的,简述一下
    • 标记清除法
      • 垃圾收集器找到所有的根,并“标记”(记住)它们。
      • 然后它遍历并“标记”来自它们的所有引用。
      • 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
      • 直到所有可达的(从根部)引用都被访问到。
      • 没有被标记的对象都会被删除。
  • 什么是强引用与弱引用
    • 拥有强引用的数据不会被垃圾回收机制回收
    • 拥有弱引用的数据,不计入垃圾回收机制
  • Map与WeakMap的区别有哪些
    • MAP
      • MAP 可以接受各种类型的数据作为键
      • MAP 对数据的引用是强引用
    • WeakMap
      • WeakMap 只能接受对象作为键名(不包括null)
      • WeakMap 对数据的引用是弱引用
      • 一旦消除对键的引用,它占用的内存就会被垃圾回收机制释放
      • WeakMap 有助于防止内存泄漏
  • null 与 undefined 有什么区别
    • null
      • null 表示空值
      • typeof null === 'object'
      • JSON.Stringify 不会 省略值为null的属性
    • undefined
      • undefined 表示当前变量未被赋值、参数未传递、函数无返回值
      • typeof undefined === 'undefined'
      • JSON.Stringify 会 省略值为undefined的属性
  • 常见的内存泄露场景有哪些,如何避免
    • 全局变量
      • 当全局变量不需要时,及时释放引用
      • 尽量少用全局变量
    • 事件绑定
      • 当在 DOM 上绑定事件时,如果 DOM 元素被移除,事件仍然存在(现代浏览器不存在这样的问题)
      • 移除 DOM 元素时,一同移除事件
    • DOM 引用
      • 当引用的 DOM 元素在文档中被移除时,引用就无意义了,应该及时释放引用
    • 定时器
      • 假如对某个 DOM 元素 绑定了定时器事件,如果该 DOM 元素被移除,那么定时器也应该及时释放

场景

  • 请求合并:短时间内需要请求多个资源合并成一个请求发送
// 首先有一个接口其请求路径为 /path
// query有一个id参数支持传一个或者多个id
// /path?id=1
// /path?id=1,2,3
// /path?id=1,2
// 返回内容格式为(假设请求的query是 id=1,2)
const demoRes = {
    1:{
        data:{}
    },
    2:{
        data:{}
    }
}
// request的构成
request({
    url:'/path',
    query:{
        id:''
    }
})

// 下面是使用场景实现,每个方法回调最终拿到的是自己需要的内容

getArticle(3).then(res=>{})
getArticle(4).then(res=>{})
getArticle(5).then(res=>{})
getArticle(6).then(res=>{})

// 实现这个getArticle方法

// 模拟发送请求,延迟返回数据
function request(options) {
    const { query: { id } } = options
    return new Promise((resolve, rej) => {
        const res = id.split(',').reduce((pre, _id) => {
            pre[_id] = {
                data: {
                    id: _id,
                    date: new Date().toLocaleString()
                }
            }
            return pre
        }, {})
        setTimeout(() => {
            // 2s后响应
            resolve(res)
        }, 2000)
    })
}

const getArticle = (() => {
    // 共用变量
    let timer = null
    let ids = []
    let resolves = {}

    return function (id) {
        return new Promise((resolve, rej) => {
            // 处理id重复的情况
            resolves[id] = !resolves[id] ? [resolve] : resolves[id].concat(resolve)
            ids.push(id)
            if (timer) {
                clearTimeout(timer)
            }
            timer = setTimeout(() => {
                const _resolves = resolves

                request({
                    path: '/path',
                    query: {
                        id: [...new Set(ids)].join(',')
                    }
                }).then(res => {
                    // 返回结果 
                    Object.keys(res).forEach(k => {
                        _resolves[k].forEach(rl => {
                        	rl((res[k]))
                        })
                    })
                })

                // 请求被发出则重置状态
                timer = null
                ids = []
                resolves = {}
            }, 0)
        })
    }

})()