面试

1.webpack优化

  • 优化开发体验(提升开发效率)

    • 优化构建速度 (配置时相关路径尽量写绝对路径,以减少查找,代码中写导入语句时,确定文件时带上文件后缀,以避免查找)
    • 使用DllPlugin (动态链接库,原因在于包含大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码)
    • 使用HappyPack (在整个 Webpack 构建流程中,最耗时的流程可能就是 Loader 对文件的转换操作了,因为要转换的文件数据巨多,而且这些转换操作都只能一个个挨着处理。HappyPack的核心原理就是把这部分任务分解到多个进程去并行处理,从而减少了总的构建时间。)
    • 使用ParallelUglifyPlugin (ParallelUglifyPlugin 会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过 UglifyJS去压缩代码,但是变成了并行执行)
  • 优化使用体验

    • 使用自动刷新(使用 webpack 模块负责监听文件,webpack-dev-server 模块则负责刷新浏览器。)
    • 使用模块热替换 (hot)
  • 优化输出质量

    • 减少用户能感知到的加载时间,也就是首屏加载
      • 区分环境 (开发和线上环境 代码压缩和提示日志)
      • 压缩代码 (UglifyJsPlugin:通过封装 UglifyJS 实现压缩。ParallelUglifyPlugin:多进程并行处理压缩)
      • CDN加速 (CDN 又叫内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。要给网站接入 CDN,需要把网页的静态资源上传到 CDN 服务上去,在服务这些静态资源的时候需要通过 CDN 服务提供的 URL 地址去访问)
      • 使用Tree Shaking (Tree Shaking 可以用来剔除 JavaScript 中用不上的死代码)
      • 提取公共代码
      • 按需加载

2.webpack如何区分环境?

具体区分方法很简单,在源码中通过如下方式:

1
2
3
4
5
if (process.env.NODE_ENV === 'production') {
console.log('你正在线上环境');
} else {
console.log('你正在使用开发环境');
}

其大概原理是借助于环境变量的值去判断执行哪个分支

3.小程序的生命周期和路由以及setData原理

1
2
3
4
5
6
7
8
9
10
11
onLaunch() {
console.log('onLaunch监听小程序初始化');
}

onShow() {
console.log('onShow监听小程序显示');
}

onHide() {
console.log('onHide监听小程序隐藏');
}

小程序的生命周期函数的调用顺序为:onLaunch>onShow>onHide

页面生命周期:
页面生命周期函数就是当你每进入/切换到一个新的页面的时候,就会调用的生命周期函数。Page(Object) 函数用来注册一个页面。接受一个Object类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
onLoad(options) {
console.log('onLoad监听页面加载:首次进入页面加载时触发,可以在 onLoad 的参数中获取打开当前页面路径中的参数。');
}

onReady() {
console.log('onReady监听页面初次渲染完成');
}

onShow() {
console.log('onShow监听页面显示:加载完成后、后台切到前台或重新进入页面时触发');
}

onHide() {
console.log('onHide监听页面隐藏');
}

onUnload() {
console.log('onUnload监听页面卸载');
}

生命周期函数的调用顺序为:onLoad>onShow>onReady。

首次进入小程序会先触发应用生命周期中onLaunch方法和onShow方法,其次触发页面生命周期中onLoad、onShow和onReady方法。

前台切换到后台时,先触发页面生命周期中onHide方法,再触发应用生命周期的onHide方法。

后台切换到前台时,先触发应用生命周期中onShow方法,再触发页面生命周期的onShow方法。

setData:setData 函数用于将数据从逻辑层发送到视图层(异步),同时改变对应的 this.data 的值(同步)

setData工作原理
小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。

而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。

4.哪些css属性可以继承?

可继承的有: font-size font-family color、
不可继承的有:border、padding、margin、width、height
与字体相关的样式通常可以继承,与尺寸相关的样式通常不能继承

5.call/apply/bind都是用来重新定义this对象的

1
2
3
4
5
6
7
8
9
10
11
var name = 'wang',age = 17;
var obj = {
name: 'zhang',
objAag: this.age,
myFun:function(){
console.log(this.name + '年龄' + this.age)
}
}
obj.objAag; //17
obj.myFun(); //zhang年龄undefined

使用call/apply/bind

1
2
3
4
5
6
7
var db = {
name:'dema',
age: 99
}
obj.myFun.call(db); // dema年龄99
obj.myFun.apply(db); // dema年龄99
obj.myFun.bind(db)(); // dema年龄99

以上除了bind多了个()外,结果都一致,由此可知,bind返回的是一个新函数,必须调用它能执行

call/apply/bind 传参数区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var name = 'wang',age = 17;
var obj = {
name: 'zhang',
objAag: this.age,
myFun:function(fm,t){
console.log(this.name + '年龄' + this.age,'来自 '+ fm+' 去往'+t)
}
}
var db = {
name:'dema',
age: 99
}

obj.myFun.call(db,'成都','上海'); //dema年龄99 来自 成都 去往上海
obj.myFun.apply(db,['成都','上海']); //dema年龄99 来自 成都 去往上海
obj.myFun.bind(db,['成都','上海'])(); //dema年龄99 来自 成都,上海 去往undefined
obj.myFun.bind(db,'成都','上海')(); //dema年龄99 来自 成都 去往上海
obj.myFun.bind(db)('成都','上海'); // dema年龄99 来自 成都 去往上海

call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数:
call的参数直接放进去,多个参数用逗号分隔
apply的第二个参数是数组
bind 除了返回是函数以外,它 的参数和 call 一样

6.z-index属性

z-index 属性指定一个元素的堆叠顺序。

拥有更高堆叠顺序的元素总是会处于堆叠顺序较低的元素的前面。

注释:元素可拥有负的 z-index 属性值。

注释:Z-index 仅能在定位元素上奏效(例如 position:absolute, position:relative, or position:fixed)

可能的值:

  • auto :默认。堆叠顺序与父元素相等。
  • number :设置元素的堆叠顺序。
  • inherit: 规定应该从父元素继承 z-index 属性的值。

默认的 z-index 是 0

7.vue生命周期

  • beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用
  • created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。一般creadted钩子函数主要是用来初始化数据。
  • beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。
  • mounted: 实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。注意 mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick。该钩子函数是在挂在完成以后也就是模板渲染完成以后才会被调用
  • beforeUpdate: 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  • updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
  • activated:被 keep-alive 缓存的组件激活时调用。
  • deactivated:被 keep-alive 缓存的组件停用时调用
  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用
  • destroyed: 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁
  • errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

8.JavaScript的let和const

ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: let 和 const。

let 声明的变量只能let命令所在代码块内有效
const声明一个只读的常量,一旦声明了值,该值就不能改变。

在 ES6 之前,JavaScript 只有两种作用域: 全局变量 与 函数内的局部变量。

全局变量:

在函数外声明的变量是全局的

1
2
3
4
5
6
7
var carName = "Volvo";

// 这里可以使用 carName 变量

function myFunction() {
// 这里也可以使用 carName 变量
}

全局变量在js程序的任何地方都可用

局部变量:
在函数内部声明的变量的作用域是局部的

1
2
3
4
5
6
7
8
// 这里不能使用 carName 变量

function myFunction() {
var carName = "Volvo";
// 这里可以使用 carName 变量
}

// 这里不能使用 carName 变量

函数内使用var声明的变量只能在函数内部可用,没有用var声明的变量全局可用

javascript的块级作用域:
使用var声明的变量不具有块级作用域的特性,在{}外仍然能使用

1
2
3
4
{ 
var x = 2;
}
// 这里可以使用 x 变量

在es6之前没有块级作用域的概念
es6可以使用let关键字来实现块级作用域
let 声明的变量只在 let 命令所在的代码块 {} 内有效,在 {} 之外不能访问。

1
2
3
4
{ 
let x = 2;
}
// 这里不能使用 x 变量

重新定义变量:

使用var关键字重新声明变量会带来问题
在块中重新声明变量也会重新声明块外的变量:

1
2
3
4
5
6
7
8
var x = 10;
// 这里输出 x 为 10
{
var x = 2;
// 这里输出 x 为 2
}
// 这里输出 x 为 2

let 关键字就可以解决这个问题,因为它只在 let 命令所在的代码块 {} 内有效。

1
2
3
4
5
6
7
var x = 10;
// 这里输出 x 为 10
{
let x = 2;
// 这里输出 x 为 2
}
// 这里输出 x 为 10

循环作用域:

1
2
3
4
5
6
7
8
9
10
11
var i = 5;
for (var i = 0; i < 10; i++) {
// 一些代码...
}
// 这里输出 i 为 10

let i = 5;
for (let i = 0; i < 10; i++) {
// 一些代码...
}
// 这里输出 i 为 5

在第一个实例中,使用了 var 关键字,它声明的变量是全局的,包括循环体内与循环体外。

在第二个实例中,使用 let 关键字, 它声明的变量作用域只在循环体内,循环体外的变量不受影响。

局部变量:

在函数体内使用 var 和 let 关键字声明的变量有点类似。

它们的作用域都是 局部的:

1
2
3
4
5
6
7
8
9
// 使用 var
function myFunction() {
var carName = "Volvo"; // 局部作用域
}

// 使用 let
function myFunction() {
let carName = "Volvo"; // 局部作用域
}

全局变量
在函数体外或代码块外使用 var 和 let 关键字声明的变量也有点类似。

它们的作用域都是 全局的:

1
2
3
4
5
// 使用 var
var x = 2; // 全局作用域

// 使用 let
let x = 2; // 全局作用域

HTML 代码中使用全局变量:
在JavaScript中,全局作用域是针对JavaScript环境
在html中,全局作用域是指window对象

使用var关键字声明的全局作用域变量是属于 window 对象:

1
2
var carName = "Volvo";
// 可以使用 window.carName 访问变量

使用let声明的全局作用域变量不属于window对象:

1
2
let carName = "Volvo";
// 不能使用 window.carName 访问变量

重置变量:

使用var关键字声明的变量任何地方都可以修改:

1
2
3
4
5
6
7
var x = 2;

// x 为 2

var x = 3;

// 现在 x 为 3

在相同的作用域下,不能使用let来重置var声明的变量:

1
2
3
4
5
6
7
var x = 2;       // 合法
let x = 3; // 不合法

{
var x = 4; // 合法
let x = 5 // 不合法
}

在相同的作用域下,不能使用let重置let声明的变量:

1
2
3
4
5
6
7
let x = 2;       // 合法
let x = 3; // 不合法

{
let x = 4; // 合法
let x = 5; // 不合法
}

在相同作用域下,不能使用var关键字来重置let声明的变量:

1
2
3
4
5
6
7
let x = 2;       // 合法
var x = 3; // 不合法

{
let x = 4; // 合法
var x = 5; // 不合法
}

let 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:

1
2
3
4
5
6
7
8
9
let x = 2;       // 合法

{
let x = 3; // 合法
}

{
let x = 4; // 合法
}

变量提升:

js中,var声明的变量可以先使用后声明

1
2
3
// 在这里可以使用 carName 变量

var carName;

let声明的变量不可以先使用再声明:

1
2
3
// 在这里不可以使用 carName 变量

let carName;

const关键字
const用来声明一个或多个常量值,声明时必须进行初始化,且初始化后不可以再修改:

1
2
3
const PI = 3.141592653589793;
PI = 3.14; // 报错
PI = PI + 10; // 报错

const和let定义变量的类似:

  • 二者都是块级作用域
  • 都不能和所在作用域内其他变量或函数同名

两者区别:

  • const声明变量必须初始化,let可以不用初始化
  • const初始化后不可以修改,let初始化后可以修改

const并非是一个真正的常量
const 的本质: const 定义的变量并非常量,并非不可变,它定义了一个常量引用一个值。使用 const 定义的对象或者数组,其实是可变的。下面的代码并不会报错:

1
2
3
4
5
6
7
8
// 创建常量对象
const car = {type:"Fiat", model:"500", color:"white"};

// 修改属性:
car.color = "red";

// 添加属性
car.owner = "Johnson";

但不能对常量对象重新赋值:

1
2
const car = {type:"Fiat", model:"500", color:"white"};
car = {type:"Volvo", model:"EX60", color:"red"}; // 错误

在相同的作用域或块级作用域中,不能使用 const 关键字来重置 var 和 let关键字声明的变量:

1
2
3
4
5
6
var x = 2;         // 合法
const x = 2; // 不合法
{
let x = 2; // 合法
const x = 2; // 不合法
}

在相同的作用域或块级作用域中,不能使用 const 关键字来重置 const 关键字声明的变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
const x = 2;       // 合法
const x = 3; // 不合法
x = 3; // 不合法
var x = 3; // 不合法
let x = 3; // 不合法

{
const x = 2; // 合法
const x = 3; // 不合法
x = 3; // 不合法
var x = 3; // 不合法
let x = 3; // 不合法
}

const 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:

1
2
3
4
5
6
7
8
9
const x = 2;       // 合法

{
const x = 3; // 合法
}

{
const x = 4; // 合法
}

const 关键字定义的变量则不可以在使用后声明,也就是变量需要先声明再使用。

1
2
carName = "Volvo";    // 在这里不可以使用 carName 变量
const carName = "Volvo"

9.箭头函数

ES6 之前,JavaScript 的 this 对象一直很令人头大,回调函数,经常看到 var self = this 这样的代码,为了将外部 this 传递到回调函数中,那么有了箭头函数,就不需要这样做了,直接使用 this 就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 回调函数
var Person = {
'age': 18,
'sayHello': function () {
setTimeout(function () {
console.log(this.age);
});
}
};
var age = 20;
Person.sayHello(); // 20

var Person1 = {
'age': 18,
'sayHello': function () {
setTimeout(()=>{
console.log(this.age);
});
}
};
var age = 20;
Person1.sayHello(); // 18

当我们需要维护this上下文的时候,使用箭头函数

箭头函数:
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体

基本用法:

1
2
3
4
5
6
var f = v => v;
//等价于
var f = function(a){
return a;
}
f(1); //1

当箭头函数没有参数或者有多个参数,要用 () 括起来:

1
2
var f = (a,b) => a+b;
f(6,2); //8

当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。

1
2
3
4
5
var f = (a,b) => {
let result = a+b;
return result;
}
f(6,2); // 8

当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来

1
2
3
4
5
6
7
// 报错
var f = (id,name) => {id: id, name: name};
f(6,2); // SyntaxError: Unexpected token :

// 不报错
var f = (id,name) => ({id: id, name: name});
f(6,2); // {id: 6, name: 2}

注意点:没有 this、super、arguments 和 new.target 绑定。

1
2
3
4
5
6
7
8
9
10
11
var func = () => {
// 箭头函数里面没有 this 对象,
// 此时的 this 是外层的 this 对象,即 Window
console.log(this)
}
func(55) // Window

var func = () => {
console.log(arguments)
}
func(55); // ReferenceError: arguments is not defined

箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。

1
2
3
4
5
6
7
8
9
function fn(){
setTimeout(()=>{
// 定义时,this 绑定的是 fn 中的 this 对象
console.log(this.a);
},0)
}
var a = 20;
// fn 的 this 对象为 {a: 19}
fn.call({a: 18}); // 18

不可以作为构造函数,也就是不能使用 new 命令,否则会报错

10.vuex页面刷新数据保持不变

用vuex做全局状态管理的时候,页面刷新,数据会丢失,是因为store中的数据是保存在运行内存中的,页面刷新时就会重新加载vue实例,store会被重新赋值。
解决方法:

方案一:
由于state中的数据是响应式的,而数据又是通过mutation来修改的,所以在mutation修改state数据时调用localStorage.setItem()方法来进行数据的存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

export default new Vuex.Store({
state:{
orderList:[],
menuList:[]
},
mutations: {
orderList(state,data){
state.orderList = data;
localStorage.setItem('orderList', JSON.stringify(data))
},
menuList(state,data){
state.menuList = data;
localStorage.setItem('menuList', JSON.stringify(data))
}
}
})

在页面加载的时候再通过localStorage.getItem()方法将值取出放回vuex,可在app.vue的created方法中写:

1
2
3
4
5
created(){
if(window.localStorage.getItem('orderList')){
this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(window.localStorage.getItem('orderList'))))
}
}

方案二:
方案一能够顺利解决问题,但不断触发localStorage.setItem()方法对性能不是特别友好,而且一直将数据同步到localStorage中似乎就没必要再用vuex做状态管理,直接用localStorage即可,于是对以上解决方法进行了改进,通过监听beforeunload事件来进行数据的localStorage存储,beforeunload事件在页面刷新时进行触发,具体做法是在App.vue的created()周期函数中下如下代码:

1
2
3
4
5
6
7
8
created(){
if(window.localStorage.getItem('orderList')){
this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(window.localStorage.getItem('orderList'))))
}
window.addEventListener('beforeunload',function(){
window.localStorage.setItem("orderList",JSON.stringify(this.$store.state.orderList))
})
}

11.公众号openid和unionid的区别

  • 微信openid和unionid长度是不一样的:openid=28,unionid=29
  • openid同一用户同一应用唯一,unionid同一用户不同应用唯一
    • 这里的不同应用是指在同一微信开发平台下的不同应用
    • 为了识别用户,每个用户针对每个公众号会产生一个安全的openid
    • 一个用户虽然对多个公众号和应用有多个不同的openid,但他对所有这些同一开放平台账号下的公众号和应用,只有一个unionid

12.axios统一封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import axios from 'axios';
const _axios = axios.create();

_axios.default.baseURL = 'requesturl';
_axios.interceptors.request.use(function(config){
// 在发送请求前做些什么
config => {
config.withCredentials = true; // 允许携带token ,这个是解决跨域产生的相关问题
config.timeout = 12000;
config.headers = {
'Content-Type': 'application/json',
}
}
config.headers['Authorization'] = `${localStorage.getItem('token')}`;
return config;
}, function(error){
// 对请求错误做些什么
return Promise.reject(error)
})

// 添加相应拦截器
_axios.interceptors.response.use(function(response){
if(response.data.code == 0){
return response.data
}else{
return Promise.reject(response.data.message);
}
}, function(error){
// 对相应错误做些什么
swtich(error.response.status){
case 401:
//do something
break;
case 403:
//do something
break;
case 500:
//do something
break;
default:
//do something
break;
}
return Promise.reject(error)
})

export default _axios;

//使用axios
import _axios from './axios';
// 接口定义
getList(data){
return _axios({
method: 'GET',
url:'',
params:{data}
})
},
postList(data){
return _axios({
method: 'POST',
url:'',
data:{data}
})
}
// 页面接口调用
getList(data).then(res=>{

}).catch(err=>{

})

12.webpack

loader和plugin的使用

  • loader:下载、使用
  • plugin: 下载、引入、使用

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const {resolve} = require('path');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js', //打包的入口文件
output:{
// 输出的文件名,以及文件路径
filename: 'build.js', //输出的文件名
path: resolve(__dirname,'build') //// 当前路径(__dirname)的build文件夹
},
module:{
// 在此配置loader
rules: [
{
test: /\.css$/, //表示匹配以css结尾的文件
use:[
// 使用以下loader进行编译从下往上
'style-loader',
'css-loader'
]
},
{
test:/\.(png|svg|jpg|gif)&/,
use:[
'file-loader' //在Css中有背景和图标等图片,需要安装file-loader来处理
]
}
]
},
plugins: [
// 在此配置插件,插件的作用比loader要强,如压缩编译转换等
new CleanWebpackPlugin(), //清理 /dist 文件夹
new HtmlWebpackPlugin({
title: 'Output Management'
}), //生成html文件
],
mode: "development" //配置当前打包环境 development-开发环境、production-生产环境 (生产环境会压缩代码)
}

常用loader(按顺序引入)

处理 css

  • style-loader
  • css-loader

处理 less

  • style-loader
  • css-loader
  • less-loader(less-loader依赖less,要一起安装)

处理css图片资源

  • url-loader(依赖file-loader,可一并安装)

处理html图片资源

  • html-loader

处理其他资源(如:字体文件等)

  • file-loader

常用plugins

  • html-webpack-plugin
1
2
3
4
5
6
7
8
const hwp = require("html-webpack-plugin")
new hwp({
template:"./src/index.html"//以该文件为模板进行复制打包
minify:{
collapseWhitespace:true, //压缩空格
removeComments:true // 去除注释
}
})
  • clean-webpack-plugin

13.数组的toString方法

数组的toString方法会将数组的每个元素转换为字符串,有逗号拼接并且没有其他界定符

1
2
3
4
5
6
[1,2,3].toString()
"1,2,3"
["a","b","c"].toString()
"a,b,c"
[1,[2,"c"]].toString()
"1,2,c"

14.对象的toString方法

对象的toString方法没有任何参数,返回一个字符串,该方法返回的是调用它的对象的类型或值。
当使用“+”运算符把一个字符串和一个对象连接一起或把一个对象传递给alert()或document.write()时,会调用toString方法。

默认的toString方法返回的字符串形式总是[object class],class是对象的内部类型,通常对应的是该对象的构造函数名。例如,Array对象的class值是Array,Function对象的class值是“Function”,Date对象的class值是“Date”,内部Math对象的class值是“Math”,所有Error对象的的class值是“Error”,用户定义的对象(例如Complex)的class值是“Object”。

15.怎样判断一个对象是不是数组?

  • 从原型入手,Array.prototype.isPrototypeOf(obj);
    利用isPrototypeOf()方法判断Array是否在obj的原型链上
1
2
Array.prototype.isPrototypeOf([]); //true
Array.prototype.isPrototypeOf({}); //false
  • 用instanceof, obj instanceof Array
    instanceof检测构造函数的prototype属性是否存在实例对象的原型链上
1
[] instanceof Array //true
  • Object.prototype.toString.call(obj)
    根据对象的class属性,跨原型链调用toString方法
1
Object.prototype.toString.call([]); //"[object Array]"
  • Array.isArray()
1
Array.isArray([]); //true

16.js解决苹果移动端300ms延迟的问题

做移动端页面开发的可能会了解到,ios系统click事件会有卡顿的现象,这个问题的根源是苹果本身自带的safari有双击放大页面的功能,再次双击会返回到原始尺寸,所以在第一次点击的系统会延迟300ms来判断是不是双击操作,为了解决这个问题,网上也给了解决的办法,把click事件绑定到ontouchstart事件上,这样就解决了300ms延迟的问题,这个文件是fastclick.js

17.tcp三次握手

为了准确无误将数据送达目标处,TCP协议采取了三次握手协议。
用tcp协议将数据包送出去后,tcp不会对发送后的情况置之不理,它一定向对方确认是否收到。

  • 1.发送端首先发送带SYN标志的数据给对方。
  • 2.接收方收到后回传一个SYN/ACK标志的数据包以示传达确认信息。
  • 3.最后,发送端再回传一个带ACK标志的字段,代表握手结束

若在握手过程中某个阶段被打断,TCP会再次以相同的顺序发送数据包。

18.四次挥手

第一次:客户端请求断开FIN,seq=u
第二次:服务器确认客户端的断开请求ACK,ack=u+1,seq=v
第三次:服务器请求断开FIN,seq=w,ACK,ack=u+1
第四次:客户端确认服务器的断开ACK,ack=w+1,seq=u+1

19.osi模型

  • 应用层:提供应用程序间通道
  • 表示层:处理数据格式、数据加密等
  • 会话层:建立、维护和管理会话
  • 传输层:建立主机端到端的连接
  • 网络层:寻址和路由选择
  • 数据链路层:提供介质访问,链路管理等
  • 物理层:比特流传输

20.vue minxins

mixins定义:mixins选项接受一个混入对象的数组,类型:Array<Object>

mixins原理:真正的实现是靠mergeOptions函数实现的。

这个函数传进去的两个参数分别是this.options 和 mixin,而mergeOptions函数则实现了递归遍历this.options,然后执行mergeField,返回最终合并的this.options
mergeField函数:一般我们执行mergeField 里的key基本上就是上面strats的属性了,用的最多的可能就是data、methods、props了,所以如果我们在mixins中用到了data,其本质上就是合并当前vue实例对象里的data和我们传进去的mixin里的data,其他属性也是一样的

合并策略:
1.data
mixins中的data会合并到实例中的data中,有冲突的话,实例中data的数据会覆盖mixins中的数据

image

2.钩子函数
mixins中和实例中的钩子函数,都会执行,先执行mixins中的钩子函数

image

3.methods、components、directives
methods、components、directives会执行,当有冲突的时候,实例中的会覆盖mixins中的

image

21.git常用命令

创建仓库:
git init

提交代码相关:

  • 把当工作区文件加到暂存区
    • 单个文件加入:git add x.js
    • 全部文件加入: git add .
  • 把暂存区的内容提交到本地仓库: git commit -m “xxx”
  • 克隆一下远程仓库到本地:
    git clone git@github.com:michaelliao/gitskills.git
    当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。
  • 添加一个远程仓库:git remote add origin git@github.com:michaelliao/learngit.git
  • 本地库的所有内容推送到远程库上:git push -u origin master
  • 提交日志查看(详细):git log
  • 提交查看(简洁版):git log –pretty=oneline
  • 指定本地dev分支与远程origin/dev分支的链接:git branch –set-upstream-to=origin/dev dev

版本回退:

  • 回到上一个版本:git reset –hard HEAD^
  • 回到上倒数第3个版本:git reset –hard HEAD^^^
  • 回到指定版本:git reset –hard 1094a

撤销更改:
丢弃修改,这个文件回到最近一次git commit或git add时的状态。类似svn revert命令。
git checkout – readme.txt

删除本地分支 :git branch -d 本地分支名
删除远程分支:
git push origin –delete 分支名
git branch -r -d origin/branchName
git push origin :branchName

22.vue中的虚拟dom

1.什么是虚拟DOM
虚拟DOM 表现为一个 Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性
创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应

2.为什么需要虚拟DOM
DOM是很慢的,其元素非常庞大,页面的性能问题,大部分都是由DOM操作引起的

真实的DOM节点,哪怕一个最简单的div也包含着很多属性
操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验

3.Diff原理
diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。通俗的讲就是:diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁
通过diff算法比较 新 旧 两个VDOM,将不同的地方进行修改,相同的地方就地复用,最后再通过render函数渲染页面

23.vue双向绑定原理

vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

24.$on,$once,$emit,$off

vm.$on( event, callback ):
监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数

vm.$once( event, callback ):
监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除

vm.$off( [event, callback] ):
移除自定义事件监听器。

如果没有提供参数,则移除所有的事件监听器;

如果只提供了事件,则移除该事件所有的监听器;

如果同时提供了事件与回调,则只移除这个回调的监听器

vm.$emit( eventName, […args] ):
触发当前实例上的事件。附加参数都会传给监听器回调。

25.vue的响应式原理,数组的变异检测

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

检测变化的注意事项
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

对于对象:
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的

对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property

对于数组:
Vue 不能检测以下数组的变动:
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将在响应式系统内触发状态更新:

1
2
3
4
5
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

为了解决第二类问题,你可以使用 splice:vm.items.splice(indexOfItem, 1, newValue)

异步更新队列:

可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替

26.vue-loader

vue-loader作用:
解析和转换.vue文件,提取出其中的逻辑代码script,样式代码style,以及html模版template,再分别把他们交给对应的loader去处理

用途:js可以写es6,style样式可以写scss或less

css-loader:加载由 vue-loader 提取出的 CSS 代码。
vue-template-compiler:把vue-loader提取出的HTML模板编译成可执行的jacascript代码