万字长文系列:Web前端百度面经(含答案)第三章

前言

全系列分为四个章节,分别是电话面试部分、一面部分、二面部分、三面部分。全系列不光有题目还有详细答案,

收藏转发就完事了!

第一章:电话面试部分已经放出

以及第二章:一面部分

希望这个系列能够助力大家找到好的工作,年年一帆风顺!!

接下来,长文预警~大家有所心理准备

正文

题目:结合页面加载流程流程详细说下过程中的性能优化

这个问题就比较细节化,可以参考我之前写过的关于性能优化的文章,基本的优化方案里边都有,

这个问题回答期间面试官追问了一些具体的小细节问题,这里就不再给出了,文章中基本都包含了,这里就不展开篇幅讲了

这种笼统的大方向问题建议条理化回答,可以按照自己习惯的或者固定的方面去展开讲,否则这种大问题东一句西一句的容易让面试不耐烦,越条理越好,我自己当时是按照HTML、CSS、JS、网络通信,页面加载的顺序去说的,过程中面试官一直在记录,估计是看有没有说到他想要的那些点。

追问:开发过程中碰到过什么棘手的性能方面的问题么

这个问题当时都是回答的之前在开发过程中确实碰到的,感恩自己当时有心做了总结,总结请点击记一次惨痛的Vue-cli + VueX + SSR经历

这期间穿插着问了一点node,webpack的小知识点

题目:继承的方式有哪些

提供个父类进行继承

<code>function SuperType(name) {
this.name = name;
this.sexy = ["man", "woman", "unknow"];
this.showName = function() {
console.log(name);
};
};
SuperType.prototype.age = 18;/<code>

原型继承

  • 利用原型让一个引用类型继承另外一个引用类型的属性和方法
  • 重点:新实例的原型等于父类的实例
  • 特点: 1.实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!) 2.基于原型链,既是父类的实例,也是子类的实例
  • 缺点: 1.无法实现多继承 2.所有新实例都会共享父类实例的属性
<code>function SubType() {
this.name = 'coder';
}
SubType.prototype = new SuperType();
var subFun = new SubType();
console.log(subFun.age) // 18
console.log('outer', subFun instanceof SuperType) // true /<code>

构造函数继承

  • 重点:用call()和apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
  • 特点: 1、只继承了父类构造函数的属性,没有继承父类原型的属性。 2、解决了原型链继承缺点1、2、3。 3、可以继承多个构造函数属性(call多个)。 4、在子实例中可向父实例传参。
  • 缺点: 1、只能继承父类构造函数的属性。 2、无法实现构造函数的复用。(每次用每次都要重新调用) 3、每个新实例都有父类构造函数的副本,臃肿。
<code>function SubType() {
SuperType.call(this);
}
let instance1 = new SubType();
instance1.sexy.push("all");
console.log(instance1.sexy); // ["man", "woman", "unknow", "all"]
let instance2 = new SubType();

console.log(instance2.sexy); // ["man", "woman", "unknow"]/<code>

组合继承

  • 特点:利用原型链继承父类的原型属性和方法,利用构造函数继承实例属性和方法
  • 缺点:调用了两次父类构造函数,生成了俩实例
<code>function SubType(name) {
SuperType.call(this, name);
}
SubType.prototype = new SuperType();
var instance3 = new SubType("newCoder");
console.log(instance3.name) // newcoder/<code>

使用 ES6 extends 进行继承

<code>class A {
constructor(name, age) {
this.name = name;
this.age = age;
}
getName() {
return this.name;
}
}

class B extends A {
constructor(name, age) {
super(name, age);
this.job = "IT";
}
getJob() {
return this.job;
}
getNameAndJob() {
return super.getName() + this.job;
}
}

var b = new B("Tom", 20);
console.log(b.name); // Tom

console.log(b.age); // 20
console.log(b.getName()); // Tom
console.log(b.getJob()); // IT
console.log(b.getNameAndJob()); // TomIT/<code>

追问:new 的原理

  • 在调用 new 的过程中会发生以下四件事: 新生成一个对象 将构造函数的作用域赋值给新对象(即绑定新对象的 this) 执行构造函数中的代码(即为这个新对象添加属性) 返回新对象
  • 一个简版的new
<code>function _new() {
\t// 创建一个新对象
let newObj = {};
// 获取构造函数
let Constructor = Array.prototype.shift.call(arguments);
// 连接新对象原型,新对象可以访问原型中的属性
newObj.__proto__ = Constructor.prototype;
// 执行构造函数,即绑定 this,并且为这个新对象添加属性
Constructor.apply(newObj, arguments);
// 返回该对象
return newObj;
}/<code>

追问:ES6 extends 的原理

这个问题当时回答的不好,很多关键点没有说出来,面试官也是很友好地帮我答疑解惑

  • ES6 中是通过 class 关键字去定义类,经过 bable 编码之后其实还是通过构造函数去实现的,但是为了规范类的使用,ES6中是不允许直接调用 class 创建的类,因为编码之后会产生一个 _classCallCheck 阻止你直接调用,会抛出错误
  • 继承过程其实归根结底也是类似原型继承,过程请看下图
万字长文系列:Web前端百度面经(含答案)第三章

首先 subClass.prototype.__proto__ = superClass.prototype 保证了 Child 的实例可以访问到父类的属性,包括内部属性,以及原型属性。其次,subClass.__proto__ = superClass,保证了Child.height 也能访问到,也就是静态方法。

题目:ES6 和 ES5

基础的问题我就不展开说了,面试过程中涉及到的我列出来,很多基本的大家开发中肯定都已经很熟悉了

  • let 和 const
  • 解构赋值
  • 箭头函数
  • 模板字符串
  • for...of 循环
  • 展开运算符...
  • Class 类
  • extends继承
  • Modules

题目:闭包的原理和优劣以及使用

闭包产生的原因

  • JS中存在全局作用域和函数作用域,当访问一个变量时,解释器会首先在当前作用域查找,如果没有找到,就去父作用域找,直到找到该变量或者不在父作用域中(也是作用域链的概念)
  • 作用域中的每个子函数都会拷贝上级的作用域,形成一个完整的作用域链条
  • 当前作用域中的变量存在着指向父级作用域的引用,便产生了闭包,下面用一段代码说明
<code>// JS的变量逐级查找
var a = 1;
function f1() {
\t// f1() 的作用域指向全局作用域(window)和它本身
\tvar a = 2
\tfunction f2() {
\t\t// f2的作用域指向全局作用域(window)、f1的作用域和它本身
\t\tvar a = 3;
\t\tconsole.log(a);//3
\t}
}

// fun会拿到父级作用域中的变量,输出2。
// 因为在当前环境中,含有对f2的引用 ,f2恰恰引用了window、f1和f2的作用域,
// 因此f2可以访问到f1的作用域的变量
function f1() {
\tvar a = 2

\tfunction f2() {
\t\tconsole.log(a); // 2
\t}
\treturn f2;
}
var fun = f1();
fun();/<code>

闭包的表现形式

  • 返回一个函数
  • 作为函数参数传递
  • 在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中,只要使用了回调函数,实际上就是在使用闭包
  • 立即执行函数表达式 保存了全局作用域和当前作用域,实际也是闭包

闭包的优劣

  • 优点 可以读取函数内部的变量 可以让这些局部变量保存在内存中,实现变量数据共享。
  • 缺点 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题 闭包会在父函数外部,改变父函数内部变量的值。

闭包的使用

  • 匿名自执行函数,因为外部无法引用其内部的变量,使用完会立即释放
  • 进行结果缓存
  • 进行封装工厂函数
  • 实现类的继承
  • 函数柯里化

题目:this 的指向问题

  • 记住一点:this最终指向调用它的对象 通俗解释:JS中存在上下文环境window或者函数,当执行的属于window时,则取的window的上下文环境,如果执行的属于函数,则取函数的上下文,this是堆栈的指针,堆栈里有什么就返回什么
  • 具体分析 如果是一般函数,this指向全局对象window 在严格模式下"use strict",为undefined 对象的方法里调用,this指向调用该方法的对象 构造函数里的this,指向创建出来的实例 () => console.log(this) 里面 this 跟外面的 this 的值一模一样 事件监听的时候,this是监听元素,setTimeout的函数内this是window(非严格模式下)
<code>// 测试题
var app = {
fn1: function () {

console.log(this)
},
fn2: function(){
return function() {
console.log(this)
}
},
fn3: function() {
function fn() {
console.log(this)
}
return fn()
},
fn4: function() {
return {
fn: function () {
console.log(this)
}
}
},
fn5: function() {
setTimeout(function () {
console.log(this)
},10)
},
fn6: function() {
setTimeout( () => {
console.log(this)
},20)
},
fn7: function() {
setTimeout(function () {
console.log(this)
}.bind(this),30)
},
fn8: () => {
setTimeout( () => {
console.log(this)
},40)
}
}
app.fn1() // app
app.fn2()() // window
app.fn3() // window
app.fn4().fn() // fn
app.fn5() // window
app.fn6() // app
app.fn7() // app
app.fn8() // window/<code>

题目:VUE 的生命周期

  • vue 生命周期流程图(图片来自网络,侵联删)


万字长文系列:Web前端百度面经(含答案)第三章

  1. 创建实例,new Vue() 的过程中,首先执行 init()
  2. init() 过程首先是执行 beforeCreate ,初始化data、 props、 watch、computed,这些执行都是在 beforeCreate 阶段和 create 阶段,也是创建响应式数据的阶段,这个阶段不要去修改数据
  3. create 阶段结束,会去判断实例中有无 el option 选项,如果没有会执行 $mount(), 如果有,直接执行下一步
  4. 判断 template, 若有,会把 template 打成一个个 render function ,其中的传参h就是vue.createElement, 参数为 标签,对象(可以是props或事件),内容
  5. render函数发生在 beforemounted 和 mounted 之间,所以当 beforeMount 时,$el 还只是HTML上的节点,mounted 时才把渲染的内容挂载到 DOM 上,实际就是执行了 renderfunction
  6. beforeMount 有了 renderfunction 才执行,执行完执行 mount , mounted 执行完,整个生命周期中主动执行的函数就已经完毕,剩下的比如 beforeUpdata、updata、beforDestory、destory 需要外部触发

题目:VUE computed原理

  • 设置 computed 的 getter ,若执行了 computed 的函数,会去读取 data 值,就会触发 data 的 getter ,从而建立data的依赖关系
  • 首次mounted的值,会执行vm.computed对应的getter,没有getter的是赋值函数
  • 若computed的属性值依赖其他属性值,会将target暂存在栈中,先进行其他的依赖收集

题目:VUE watch流程

  1. 创建实例时会去处理watch,这点在前面生命周期中已经提到
  2. 遍历数据keys去创建监听
  3. 给监听注册回调(多种处理方式) name:{ handle(){} } 传入为对象去handler字段 name(){} 传入为函数直接监听回调 name: 'getName' 传入为字符串就去实例上获取回调
  4. 调用vm.$watch 判断是否立即执行回调 每个watch配发watcher(监听的key,callback,options)
  5. 监听的数据变化时,通知watch-watcher更新,然后使用updata()更新数据

题目:VUE 响应式数据处理流程

  1. init()时,利用object.defineproperty监听vue实例的响应式数据变化从而实现数据的劫持,其实是利用了数据的setter和getter
  2. 当render function被渲染时,读取实例中与视图相关的响应式数据,从而触发getter进行依赖收集
  3. 正常的渲染和更新
  4. 数据变化时,触发setter,通知依赖收集中和视图相关的watcher,告知重新渲染视图,watcher再次通过updata渲染视图

题目:特定状态下浏览器的兼容性

这个问题想必前端开发中大家都碰到过很多奇奇怪怪的兼容性问题,我也没有回答地特别细致,说了几个日常开发中碰到过的,面试官结合实际情况问了几个,具体问的已经记不清了,JS的也有,CSS的也有,IE的那些低版本的兼容性问题个人觉得不说也罢,毕竟用户量少的又少。

尾声

接下来就要迎来咱们系列的“大结局”,最后一篇终面部分啦。觉得系列不错的记得点赞、收藏、分享哦~让更多前端er,看到有用的文章~


分享到:


相關文章: