面试
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 | if (process.env.NODE_ENV === 'production') { |
其大概原理是借助于环境变量的值去判断执行哪个分支
3.小程序的生命周期和路由以及setData原理
1 | onLaunch() { |
小程序的生命周期函数的调用顺序为:onLaunch>onShow>onHide
页面生命周期:
页面生命周期函数就是当你每进入/切换到一个新的页面的时候,就会调用的生命周期函数。Page(Object) 函数用来注册一个页面。接受一个Object类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。
1 | onLoad(options) { |
生命周期函数的调用顺序为: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 | var name = 'wang',age = 17; |
使用call/apply/bind
1 | var db = { |
以上除了bind多了个()外,结果都一致,由此可知,bind返回的是一个新函数,必须调用它能执行
call/apply/bind 传参数区别
1 | var name = 'wang',age = 17; |
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 | var carName = "Volvo"; |
全局变量在js程序的任何地方都可用
局部变量:
在函数内部声明的变量的作用域是局部的
1 | // 这里不能使用 carName 变量 |
函数内使用var声明的变量只能在函数内部可用,没有用var声明的变量全局可用
javascript的块级作用域:
使用var声明的变量不具有块级作用域的特性,在{}外仍然能使用
1 | { |
在es6之前没有块级作用域的概念
es6可以使用let关键字来实现块级作用域
let 声明的变量只在 let 命令所在的代码块 {} 内有效,在 {} 之外不能访问。
1 | { |
重新定义变量:
使用var关键字重新声明变量会带来问题
在块中重新声明变量也会重新声明块外的变量:
1 | var x = 10; |
let 关键字就可以解决这个问题,因为它只在 let 命令所在的代码块 {} 内有效。
1 | var x = 10; |
循环作用域:
1 | var i = 5; |
在第一个实例中,使用了 var 关键字,它声明的变量是全局的,包括循环体内与循环体外。
在第二个实例中,使用 let 关键字, 它声明的变量作用域只在循环体内,循环体外的变量不受影响。
局部变量:
在函数体内使用 var 和 let 关键字声明的变量有点类似。
它们的作用域都是 局部的:
1 | // 使用 var |
全局变量
在函数体外或代码块外使用 var 和 let 关键字声明的变量也有点类似。
它们的作用域都是 全局的:
1 | // 使用 var |
HTML 代码中使用全局变量:
在JavaScript中,全局作用域是针对JavaScript环境
在html中,全局作用域是指window对象
使用var关键字声明的全局作用域变量是属于 window 对象:
1 | var carName = "Volvo"; |
使用let声明的全局作用域变量不属于window对象:
1 | let carName = "Volvo"; |
重置变量:
使用var关键字声明的变量任何地方都可以修改:
1 | var x = 2; |
在相同的作用域下,不能使用let来重置var声明的变量:
1 | var x = 2; // 合法 |
在相同的作用域下,不能使用let重置let声明的变量:
1 | let x = 2; // 合法 |
在相同作用域下,不能使用var关键字来重置let声明的变量:
1 | let x = 2; // 合法 |
let 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:
1 | let x = 2; // 合法 |
变量提升:
js中,var声明的变量可以先使用后声明
1 | // 在这里可以使用 carName 变量 |
let声明的变量不可以先使用再声明:
1 | // 在这里不可以使用 carName 变量 |
const关键字
const用来声明一个或多个常量值,声明时必须进行初始化,且初始化后不可以再修改:
1 | const PI = 3.141592653589793; |
const和let定义变量的类似:
- 二者都是块级作用域
- 都不能和所在作用域内其他变量或函数同名
两者区别:
- const声明变量必须初始化,let可以不用初始化
- const初始化后不可以修改,let初始化后可以修改
const并非是一个真正的常量
const 的本质: const 定义的变量并非常量,并非不可变,它定义了一个常量引用一个值。使用 const 定义的对象或者数组,其实是可变的。下面的代码并不会报错:
1 | // 创建常量对象 |
但不能对常量对象重新赋值:
1 | const car = {type:"Fiat", model:"500", color:"white"}; |
在相同的作用域或块级作用域中,不能使用 const 关键字来重置 var 和 let关键字声明的变量:
1 | var x = 2; // 合法 |
在相同的作用域或块级作用域中,不能使用 const 关键字来重置 const 关键字声明的变量:
1 | const x = 2; // 合法 |
const 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:
1 | const x = 2; // 合法 |
const 关键字定义的变量则不可以在使用后声明,也就是变量需要先声明再使用。
1 | carName = "Volvo"; // 在这里不可以使用 carName 变量 |
9.箭头函数
ES6 之前,JavaScript 的 this 对象一直很令人头大,回调函数,经常看到 var self = this 这样的代码,为了将外部 this 传递到回调函数中,那么有了箭头函数,就不需要这样做了,直接使用 this 就行。
1 | // 回调函数 |
当我们需要维护this上下文的时候,使用箭头函数
箭头函数:
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体
基本用法:
1 | var f = v => v; |
当箭头函数没有参数或者有多个参数,要用 () 括起来:
1 | var f = (a,b) => a+b; |
当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
1 | var f = (a,b) => { |
当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
1 | // 报错 |
注意点:没有 this、super、arguments 和 new.target 绑定。
1 | var func = () => { |
箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。
1 | function fn(){ |
不可以作为构造函数,也就是不能使用 new 命令,否则会报错
10.vuex页面刷新数据保持不变
用vuex做全局状态管理的时候,页面刷新,数据会丢失,是因为store中的数据是保存在运行内存中的,页面刷新时就会重新加载vue实例,store会被重新赋值。
解决方法:
方案一:
由于state中的数据是响应式的,而数据又是通过mutation来修改的,所以在mutation修改state数据时调用localStorage.setItem()方法来进行数据的存储:
1 | import Vue from 'vue'; |
在页面加载的时候再通过localStorage.getItem()方法将值取出放回vuex,可在app.vue的created方法中写:
1 | created(){ |
方案二:
方案一能够顺利解决问题,但不断触发localStorage.setItem()方法对性能不是特别友好,而且一直将数据同步到localStorage中似乎就没必要再用vuex做状态管理,直接用localStorage即可,于是对以上解决方法进行了改进,通过监听beforeunload事件来进行数据的localStorage存储,beforeunload事件在页面刷新时进行触发,具体做法是在App.vue的created()周期函数中下如下代码:
1 | created(){ |
11.公众号openid和unionid的区别
- 微信openid和unionid长度是不一样的:openid=28,unionid=29
- openid同一用户同一应用唯一,unionid同一用户不同应用唯一
- 这里的不同应用是指在同一微信开发平台下的不同应用
- 为了识别用户,每个用户针对每个公众号会产生一个安全的openid
- 一个用户虽然对多个公众号和应用有多个不同的openid,但他对所有这些同一开放平台账号下的公众号和应用,只有一个unionid
12.axios统一封装
1 | import axios from 'axios'; |
12.webpack
loader和plugin的使用
- loader:下载、使用
- plugin: 下载、引入、使用
配置文件:
1 | const {resolve} = require('path'); |
常用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 | const hwp = require("html-webpack-plugin") |
- clean-webpack-plugin
13.数组的toString方法
数组的toString方法会将数组的每个元素转换为字符串,有逗号拼接并且没有其他界定符
1 | [1,2,3].toString() |
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 | Array.prototype.isPrototypeOf([]); //true |
- 用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中的数据
2.钩子函数
mixins中和实例中的钩子函数,都会执行,先执行mixins中的钩子函数
3.methods、components、directives
methods、components、directives会执行,当有冲突的时候,实例中的会覆盖mixins中的
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 | // Vue.set |
为了解决第二类问题,你可以使用 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代码