打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
搞懂90%,就可进大厂的前端面试题(二) | 面经分享
作者:晟小明
来源:CSDN博客

编者按:想进大型互联网公司总是需要掌握很多面试题,身为三本的本文作者,凭借这些前端面试题拿到了百度和京东的Offer。

作者用⭐标记了标题的重点程度,⭐越多越重点。下面我们就来看看这些能让我们进大厂的前端面试题吧。

历史发布:

搞懂90%,就可进大厂的前端面试题(一)

01

执行栈和执行上下文

什么是作用域,什么是作用域链?⭐⭐⭐⭐

规定变量和函数的可使用范围称作作用域
每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。

什么是执行栈,什么是执行上下文?⭐⭐⭐⭐

执行上下文分为:

  • 全局执行上下文
    • 创建一个全局的window对象,并规定this指向window,执行js的时候就压入栈底,关闭浏览器的时候才弹出
  • 函数执行上下文
    • 每次函数调用时,都会新创建一个函数执行上下文
    • 执行上下文分为创建阶段和执行阶段
      • 创建阶段:函数环境会创建变量对象:arguments对象(并赋值)、函数声明(并赋值)、变量声明(不赋值),函数表达式声明(不赋值);会确定this指向;会确定作用域
      • 执行阶段:变量赋值、函数表达式赋值,使变量对象编程活跃对象
  • eval执行上下文
执行栈:

  • 首先栈特点:先进后出
  • 当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行弹栈。
  • 栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
  • 只有浏览器关闭的时候全局执行上下文才会弹出

02

闭包

什么是闭包?闭包的作用?闭包的应用?⭐⭐⭐⭐⭐

函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护和保存的作用。

作用:

  • 保护
    • 避免命名冲突
  • 保存
    • 解决循环绑定引发的索引问题
  • 变量不会销毁
    • 可以使用函数内部的变量,使变量不会被垃圾回收机制回收
应用:

  • 设计模式中的单例模式
  • for循环中的保留i的操作
  • 防抖和节流
  • 函数柯里化
缺点:

  • 会出现内存泄漏的问题

03

原型和原型链

什么是原型?什么是原型链?如何理解⭐⭐⭐⭐⭐

原型:原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。每个构造方法都有一个显式原型。

  • __proto__是隐式原型;prototype是显式原型
  • 所有实例的__proto__都指向他们构造函数的prototype
  • 所有的prototype都是对象,自然它的__proto__指向的是Object()的prototype
  • 所有的构造函数的隐式原型指向的都是Function()的显示原型
  • Object的隐式原型是null

原型链:多个__proto__组成的集合成为原型链(概念类似于作用域链)

  • instanceof 就是判断某对象是否位于某构造方法的原型链上。

03

继承

说一说 JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点。⭐⭐⭐⭐⭐

原型继承、组合继承、寄生组合继承、ES6的extend

原型继承
// ----------------------方法一:原型继承 // 原型继承 // 把父类的实例作为子类的原型 // 缺点:子类的实例共享了父类构造函数的引用属性 不能传参 var person = { friends: ['a', 'b', 'c', 'd'] }
var p1 = Object.create(person)
    p1.friends.push('aaa')//缺点:子类的实例共享了父类构造函数的引用属性
console.log(p1); console.log(person);//缺点:子类的实例共享了父类构造函数的引用属性

组合继承

  // ----------------------方法二:组合继承    // 在子函数中运行父函数,但是要利用call把this改变一下,    // 再在子函数的prototype里面new Father() ,使Father的原型中的方法也得到继承,最后改变Son的原型中的constructor
// 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用 // 优点可传参,不共享父类引用属性 function Father(name) { this.name = name this.hobby = ['篮球', '足球', '乒乓球'] }
Father.prototype.getName = function () { console.log(this.name); }
function Son(name, age) { Father.call(this, name) this.age = age }
Son.prototype = new Father() Son.prototype.constructor = Son

var s = new Son('ming', 20)
    console.log(s);

寄生组合继承

// ----------------------方法三:寄生组合继承 function Father(name) { this.name = name this.hobby = ['篮球', '足球', '乒乓球']    }
Father.prototype.getName = function () { console.log(this.name); }
function Son(name, age) { Father.call(this, name) this.age = age    }
Son.prototype = Object.create(Father.prototype)    Son.prototype.constructor = Son
var s2 = new Son('ming', 18)    console.log(s2);
extend

// ----------------------方法四:ES6的extend(寄生组合继承的语法糖)    //     子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的第一句话    // 必须是 super 。    class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype      constructor(y) {        super(200)  // super(200) => Father.call(this,200)        this.y = y      }    }

03

内存泄露、垃圾回收机制

什么是内存泄漏⭐⭐⭐⭐⭐

内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏。

为什么会导致的内存泄漏⭐⭐⭐⭐⭐

内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃。

垃圾回收机制都有哪些策略?⭐⭐⭐⭐⭐

  • 标记清除法
    • 垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除
  • 引用计数法
    • 当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数 1,当该值赋值给另一个变量的时候,该计数 1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象
    • 缺点:当两个对象循环引用的时候,引用计数无计可施。如果循环引用多次执行的话,会造成崩溃等问题。所以后来被标记清除法取代。

04

深拷贝和浅拷贝

手写浅拷贝深拷贝⭐⭐⭐⭐⭐

// ----------------------------------------------浅拷贝 // 只是把对象的属性和属性值拷贝到另一个对象中 var obj1 = { a: { a1: { a2: 1 }, a10: { a11: 123, a111: { a1111: 123123 } } }, b: 123, c: '123' } // 方式1 function shallowClone1(o) { let obj = {}
for (let i in o) { obj[i] = o[i] } return obj } // 方式2 var shallowObj2 = { ...obj1 } // 方式3    var shallowObj3 = Object.assign({}, obj1)
let shallowObj = shallowClone1(obj1);
shallowObj.a.a1 = 999 shallowObj.b = true
console.log(obj1); //第一层的没有被改变,一层以下就被改变了

    // ----------------------------------------------深拷贝
// 简易版 function deepClone(o) { let obj = {} for (var i in o) { // if(o.hasOwnProperty(i)){ if (typeof o[i] === 'object') { obj[i] = deepClone(o[i]) } else { obj[i] = o[i] } // } } return obj }

var myObj = { a: { a1: { a2: 1 }, a10: { a11: 123, a111: { a1111: 123123 } } }, b: 123, c: '123' }
var deepObj1 = deepClone(myObj) deepObj1.a.a1 = 999 deepObj1.b = false    console.log(myObj);

// 简易版存在的问题:参数没有做检验,传入的可能是 Array、null、regExp、Date function deepClone2(o) { if (Object.prototype.toString.call(o) === '[object Object]') { //检测是否为对象 let obj = {} for (var i in o) { if (o.hasOwnProperty(i)) { if (typeof o[i] === 'object') { obj[i] = deepClone(o[i]) } else { obj[i] = o[i] } } } return obj } else { return o }    }
function isObject(o) { return Object.prototype.toString.call(o) === '[object Object]' || Object.prototype.toString.call(o) === '[object Array]' } // 继续升级,没有考虑到数组,以及ES6中的map、set、weakset、weakmap function deepClone3(o) { if (isObject(o)) {//检测是否为对象或者数组 let obj = Array.isArray(o) ? [] : {} for (let i in o) { if (isObject(o[i])) { obj[i] = deepClone(o[i]) } else { obj[i] = o[i] } } return obj } else { return o }    }

// 有可能碰到循环引用问题 var a = {}; a.a = a; clone(a);//会造成一个死循环 // 循环检测 // 继续升级 function deepClone4(o, hash = new map()) { if (!isObject(o)) return o//检测是否为对象或者数组 if (hash.has(o)) return hash.get(o) let obj = Array.isArray(o) ? [] : {}
hash.set(o, obj) for (let i in o) { if (isObject(o[i])) { obj[i] = deepClone4(o[i], hash) } else { obj[i] = o[i] } } return obj    }
// 递归易出现爆栈问题 // 将递归改为循环,就不会出现爆栈问题了 var a1 = { a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' }; var b1 = { b: { c: { d: 1 } } } function cloneLoop(x) { const root = {}; // 栈 const loopList = [ //->[]->[{parent:{a:1,b:2},key:c,data:{ c1: 3, c2: { c21: 4, c22: 5 } }}] { parent: root, key: undefined, data: x, } ]; while (loopList.length) { // 深度优先 const node = loopList.pop(); const parent = node.parent; //{} //{a:1,b:2} const key = node.key; //undefined //c const data = node.data; //{ a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' } //{ c1: 3, c2: { c21: 4, c22: 5 } }} // 初始化赋值目标,key 为 undefined 则拷贝到父元素,否则拷贝到子元素 let res = parent; //{}->{a:1,b:2,d:'asd'} //{a:1,b:2}->{} if (typeof key !== 'undefined') { res = parent[key] = {}; } for (let k in data) { if (data.hasOwnProperty(k)) { if (typeof data[k] === 'object') { // 下一次循环 loopList.push({ parent: res, key: k, data: data[k], }) } else { res[k] = data[k]; } } } } return root }

function deepClone5(o) { let result = {} let loopList = [ { parent: result, key: undefined, data: o } ]
while (loopList.length) { let node = loopList.pop() let { parent, key, data } = node let anoPar = parent if (typeof key !== 'undefined') { anoPar = parent[key] = {} }
for (let i in data) { if (typeof data[i] === 'object') { loopList.push({ parent: anoPar, key: i, data: data[i] }) } else { anoPar[i] = data[i] } } } return result }

let cloneA1 = deepClone5(a1) cloneA1.c.c2.c22 = 5555555 console.log(a1); console.log(cloneA1);
// ------------------------------------------JSON.stringify()实现深拷贝 function cloneJson(o) { return JSON.parse(JSON.stringify(o)) } // let obj = { a: { c: 1 }, b: {} }; // obj.b = obj; // console.log(JSON.parse(JSON.stringify(obj))) // 报错 // Converting circular structure to JSON
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
JavaScript的动态特性(通过eval,call,apply和bind来体现)
面试必备:夯实 JS 主要知识点
jQuery源码 框架分析(一)
前端面试送命题-JS三座大山
深入理解JavaScript系列(10):JavaScript核心(晋级高手必读篇)
春招季如何横扫 Javascript 面试核心考点? | 技术头条
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服