- 2021面试复习笔记
- 全方面面试总结">全方面面试总结
1.javascript- JS面试题">JS面试题
- JS面试题">JS面试题
- 手写无敌JS">手写无敌JS
- javascript深入">javascript深入
- 原始类型(基础类型)
- 对象(Object)类型
typeofvsinstanceof- 类型转换
- 执行上下文(EC)">执行上下文(EC)
- 作用域与作用域链
- 闭包
- this">this
- call、apply与bind有什么区别?
- 能说说深浅拷贝的区别与实现吗?">能说说深浅拷贝的区别与实现吗?
- 常用八种继承方案">常用八种继承方案
- JS 20道概念虽老但也略有收获的JS基础题,快速做题,高效复习,不妨试试?">JS 20道概念虽老但也略有收获的JS基础题,快速做题,高效复习,不妨试试?
- 防抖与节流
- 手写 call、apply 及 bind 函数">手写 call、apply 及 bind 函数
- 手写new
- instanceof 的原理
- 为什么 0.1 + 0.2 != 0.3
- localStorage和sessionStorage">localStorage和sessionStorage
- 常用的数组API">常用的数组API
- sort">sort
- 对象API">对象API
- Fetch">Fetch
- 介绍下原型和原型链?
- 数组去重">数组去重
- 数组排序
- map和forEach的区别?
- EventLoop
- 什么是Symbol,他是使用场景是什么
- 事件委托的应用场景
- JS面试题
- 2.css
- 3.浏览器与HTTP相关知识
- 网络方面还可以吧(我默默的点点头),说说TCP的长连接和短连接的区别?
- WebSocket,说说他的特点?
- 说说三次握手四次挥手呗?
- 握手:(模拟甲请乙吃饭,我是这么理解的)
- 挥手:
- 为什么建立连接是三次握手,而断开连接是四次挥手呢?
- 输入 URL 到页面渲染的整个流程">输入 URL 到页面渲染的整个流程
- HTTP1.0、HTTP1.1 和 HTTP2.0 的区别">HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
- http缓存详解,http缓存推荐方案">http缓存详解,http缓存推荐方案
- 看完这篇HTTP,跟面试官扯皮就没问题了">看完这篇HTTP,跟面试官扯皮就没问题了
- 能说下ajax吗?">能说下ajax吗?
- 九种跨域方式实现原理(完整版)">九种跨域方式实现原理(完整版)
- 设计模式">设计模式
- 常用数据结构">常用数据结构
- 4.vue
- 5.react
- 6.性能优化
- 7.webpack/前端工程化
- 8.算法
- 9.综合问题/技术相关
- 10.综合问题/职业相关
- 前端安全">前端安全
- ES6新特性
- 前端 100 问">前端 100 问
- 简历">简历
- 面试指南">面试指南
- Typescript">Typescript
2021面试复习笔记
全方面面试总结
1.javascript
JS面试题
JS面试题
手写无敌JS
javascript深入
原始类型(基础类型)
booleannullundefinednumberstringsymbol
null不是对象类型,虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug.
对象(Object)类型
对象类型和原始类型不同的是,原始类型存储的是值,对象类型存储的是地址(指针)。
typeof vs instanceof
涉及面试题:typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?
typeof 对于原始类型来说,除了 null 都可以显示正确的类型
typeof 1 // 'number'typeof '1' // 'string'typeof undefined // 'undefined'typeof true // 'boolean'typeof Symbol() // 'symbol'
typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型
typeof [] // 'object'typeof {} // 'object'typeof console.log // 'function'
如果想判断一个对象的正确类型,这时候可以考虑使用 instanceof,因为内部机制是通过原型链来判断的,
const Person = function() {}const p1 = new Person()p1 instanceof Person // truevar str = 'hello world'str instanceof String // falsevar str1 = new String('hello world')str1 instanceof String // true
类型转换
vs=
首先会判断两者类型是否相同。相同的话就是比大小了
类型不相同的话,那么就会进行类型转换
会先判断是否在对比
null和undefined,是的话就会返回true判断两者类型是否为string和number,是的话就会将字符串转换为number
判断其中一方是否为boolean,是的话就会把boolean转为number
判断其中一方是否为
object且另一方为string、number或者symbol,是的话就会把object转为原始类型再进行判断
[] == ![]
①、根据运算符优先级 ,! 的优先级是大于 == 的,所以先会执行 ![]
!可将变量转换成boolean类型,null、undefined、NaN以及空字符串(‘’)取反都为true,其余都为false。
所以 ! [] 运算后的结果就是 false
也就是 [] == ! [] 相当于 [] == false
②、根据上面提到的规则(如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1),则需要把 false 转成 0
也就是 [] == ! [] 相当于 [] == false 相当于 [] == 0
③、根据上面提到的规则(如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较,如果对象没有valueOf()方法,则调用 toString())
而对于空数组,[].toString() -> ‘’ (返回的是空字符串)
也就是 [] == 0 相当于 ‘’ == 0
④、根据上面提到的规则(如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值)
Number(‘’) -> 返回的是 0
相当于 0 == 0 自然就返回 true了
总结一下:
[] == ! [] -> [] == false -> [] == 0 -> ‘’ == 0 -> 0 == 0 -> true
那么对于 {} == !{} 也是同理的
关键在于 {}.toString() -> NaN(返回的是NaN)
根据上面的规则(如果有一个操作数是NaN,则相等操作符返回 false)
总结一下:
{} == ! {} -> {} == false -> {} == 0 -> NaN == 0 -> false
执行上下文(EC)
https://www.cnblogs.com/echolun/p/11438363.html
执行上下文有三个种类,全局执行上下文,函数执行上下文,eval执行上下文,javascript在执行前,首先会创建全局执行上下文,也就是我们说的window,全局执行上下文会被压到执行栈的底部,当每函数被调用时会创建一个执行上下文,该执行上下文会压到全局上下文的顶部,执行完函数被弹出,符合先进后出原则,这种关系也叫做执行栈。执行上下文创建阶段主要是干了三件事,第一种确定this的指向,创建词法环境,创建变量环境,词法环境和变量环境都有一个外部环境引入记录,这个外部环境引入记录也可以称之为作用域。词法环境主要是用来定义let,const,function等变量,变量词法环境主要是用来定义var出来的变量。主要区别就是函数的词法环境多了一个argument的变量。 词法环境和变量环境的区别也可以来解释,为什么let,const有暂时性死区以及变量提升和函数提升的问题。因为词法环境定义了let,const但是并未初始化,但是function被定义并初始化了,变量词法环境中的var被声明了并且被设置了undefined。
作用域与作用域链
作用域是变量和函数的作用域范围与生命周期且是在变量和函数被定义时所创建的而非代码执行时创建,作用域链是当在当前作用域查找某变量时,如果没找到就会去上层作用域找,此行为可以一直找到全局对象window(非严格模式),而这个查找过程也就是所谓的作用域链。
闭包
闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。
在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量.
一篇文章看懂JS闭包,都要2020年了,你怎么能还不懂闭包?
闭包是由函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量。
闭包的应用场景
this
this的类型可以大致分为5中类型,分别为默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定
默认绑定可以理解为函数调用时并无任何前缀,该类型的this指向window,
隐式绑定可以理解为函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上,如果有多个前缀,那么this指向距离调用自己最近的对象。
显式绑定就是通过bind,call,apply改变this的行为,且通过bind改变的this,无法在改变。
new绑定就是通过new构造函数创建了一个新对象,而在函数体内,this将指向这个新对象上(可以抽象理解为新对象就是this)。
箭头函数中没有this,箭头函数的this指向取决于外层作用域中的this,外层作用域或函数的this指向谁,箭头函数中的this便指向谁。
this的优先级为
显式绑定 > 隐式绑定 > 默认绑定
new绑定 > 隐式绑定 > 默认绑定
call、apply与bind有什么区别?
1.call、apply与bind都用于改变this绑定,但call、apply在改变this指向的同时还会执行函数,而bind在改变this后是返回一个全新的boundFcuntion绑定函数,这也是为什么上方例子中bind后还加了一对括号 ()的原因。
2.bind属于硬绑定,返回的 boundFunction 的 this 指向无法再次通过bind、apply或 call 修改;call与apply的绑定只适用当前调用,调用完就没了,下次要用还得再次绑。
3.call与apply功能完全相同,唯一不同的是call方法传递函数调用形参是以散列形式,而apply方法的形参是一个数组。在传参的情况下,call的性能要高于apply,因为apply在执行时还要多一步解析数组。
能说说深浅拷贝的区别与实现吗?
浅拷贝只是拷贝了对象或者数组的第一层,如果属性值也是引用类型,那么浅拷贝出来的新对象,其中的引用类型只是拷贝了引用指针,并没有完全拷贝,改变原对象其中的一个属性值,拷贝后对象也会变。深拷贝则没用这种问题,是完全拷贝。
深拷贝的边界问题:假设我们要实现一个深拷贝函数deepCopy,因为前面说了深拷贝是针对引用数据来说的,第一点,传进来的参数一定是得检验数据数据类型,若是基础类型就没拷贝的必要了。第二点,对象的属性值也可能是一个对象,因此在复制时还得对值进行判断,若仍然是对象,我们得递归。第三点,我们拷贝对象自然是希望拷贝对象自身属性,而对于非自身的继承属性我们得过滤掉
常用八种继承方案
function inheritPrototype(parent,child){const prototype = Object.create(parent.prototype)prototype.constructor = childchild.prototype = prototype}function Parent(name){this.name = name}Parent.prototype.sayName = function(){console.log(this.name)}function Child(name,age){this.age = ageParent.call(this,name)}inheritPrototype(Parent,Child)const child = new Child('张三',18)console.log(child.name,child.age)//张三 18child.sayName() // 张三
JS 20道概念虽老但也略有收获的JS基础题,快速做题,高效复习,不妨试试?
防抖与节流
防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。
- 防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
function debounce (fn,wait,immediate){let timer = nullreturn function(){let arg = argumentslet _this = thisif(immediate){immediate = falsereturn fn.apply(_this,arg)}if(time) clearTimeout(timer)timer = setTimeout(()=>{return fn.apply(_this,arg)},wait)}}复制代码
- 节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。
function throttle(fn,wait,immediate){let timer = nullreturn function(){let arg = argumentslet _this = thisif(immediate){immediate = falsereturn fn.apply(_this,arg)}if(!time){setTimeout(()=>{timer = nullreturn fn.apply(_this,arg)},wait)}}}
手写 call、apply 及 bind 函数
手写new
在调用 new 的过程中会发生以上四件事情:
- 新生成了一个对象
- 链接到原型
- 绑定 this
- 如果函数没有返回对象类型
Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用。
function New(fn){let obj = {}obj.__proto__ = fn.prototypeconst result = fn.apply(obj,[...arguments].slice(1))return result instanceof Object ? result:obj}
instanceof 的原理
涉及面试题:instanceof 的原理是什么?
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
我们也可以试着实现一下 instanceof
function myInstanceof(left, right) {let prototype = right.prototypeleft = left.__proto__while (true) {if (left === null || left === undefined)return falseif (prototype === left)return trueleft = left.__proto__}}
以下是对实现的分析:
- 首先获取类型的原型
- 然后获得对象的原型
- 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为
null,因为原型链最终为null
为什么 0.1 + 0.2 != 0.3
localStorage和sessionStorage
首先这两兄弟都是存在浏览器中,local只要不删掉就会一直存在,session关掉当前窗口或者标签页就会清空.并且这两兄弟不同浏览器都是不共享的,local同源下的网页都是共享的,session则是不同页面或者标签页就不会共享 , 如果有iframe且他们是同源,那也是共享的…
常用的数组API
push:改变原数组,返回新数组长度
pop:改变原数组,返回删除的元素
unshift:改变原数组,返回新数组长度
shift:改变原数组,返回删除的元素
slice:不改变原数组,返回新数组
splice:改变原数组,返回被修改的内容
sort
小于0=>false,不换位置 , 大于0=>true,换位置
如果[2,3] , 如果是a-b =>2-3<0 不换位置,那不就是升序了, 如果是b-a =>3-2>0 那就换位置[3,2]不就是降序了.
结合实际,很好理解
对象API
assign:合并对象,浅拷贝
create:方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
defineProperty: 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
keys:方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
values:方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。
Object.entries():方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
isPrototypeOf() :方法用于测试一个对象是否存在于另一个对象的原型链上。
hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
Fetch
请注意,fetch 规范与 jQuery.ajax() 主要有三种方式的不同:
- 当接收到一个代表错误的 HTTP 状态码时,从
fetch()返回的 Promise 不会被标记为 reject, 即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的ok属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。 fetch()可以不会接受跨域 cookies;你也可以不能使用fetch()建立起跨域会话。其他网站的Set-Cookie头部字段将会被无视。fetch不会发送 cookies。除非你使用了credentials 的初始化选项。(自 2017 年 8 月 25 日以后,默认的 credentials 政策变更为same-origin。Firefox 也在 61.0b13 版本中进行了修改)
fetch方法可以接收两个参数,一个是指定获取资源的资源路径,第二个是初始化配置option,该方法会返回一个Promise,除了网络故障,否则Promise状态不会为reject。
介绍下原型和原型链?
https://www.cnblogs.com/echolun/p/12321869.html
https://www.cnblogs.com/echolun/p/12384935.html
首先我们要知道javascript中的万物皆对象,那么万物也就包括那些基础数据类型:string,number,Boolean等,当然除了null和undefined外,虽然是typeof null 为object ,但是这只是JS的一个小BUG,实际上null并未为对象,这些对象在JS中称之为包装对象,除了包装对象以外就是普通对象了。当然这些对象都是构造函数来创建的,构造函数虽然叫做构造函数但是与普通函数没什么区别,通过构造函数new出来的叫做实例,这个实例的__proto__就指向他的构造函数的prototype,所以说每一个函数都会有一个prototype,这个prototype就叫做原型,那么原型其实也是一个对象,那么说直接点,原型对象就是一个包含很多属性和方法的对象,既然原型是对象,那么原型的__proto__也就是指向object.prototype。当然这些普通的构造函数都是来源于Function这个构造函数,Function这个构造函数很特殊,既然Function是一个函数,所以他就是自己创建自己,所以他的__proto__和prototype都是指向Function.prototype,所以Function.prototype的constructor就指向Function本身,但是当我console.log(typeof Function.prototype)时他居然输出的是function,但是Function.prototype的__proto__又是指向Object.prototype这就令人费解了,在我查阅相关文档的时候,文档明确指出,这奇葩的操作是为了兼容旧版的ECMAscript.
当一个对象访问某个属性时,它会先查找自己有没有,如果没有就顺着__proto__往上查找创建自己构造函数的原型有没有,这个查找的过程就.是原型链,原型链的顶端是null。
数组去重
ES6
function unique (arr) {return Array.from(new Set(arr))}var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];console.log(unique(arr))//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
ES5
function unique(arr){let result = []for(var i = 0,arrLength = arr.length;i<arrLength;i++){for(var j = 0,resLength = res.length;j<resLength;j++){if(arr[i]===res[j]){break}}if(resLength === j){result.push(arr[i])}}return result}function unique2(){let result = []for(var i = 0,arrLength = arr.length;i<arrLength;i++){let current = arr[i]if(result.indexOf(current) === -1){result.push(current)}}return result}function unique3(){let result = []let seenlet sortArray = arr.concat().sort()for(let i=0,len= sortArray.length;i<len;i++){let value = sortArray[i]if(!i || value !== seen){result.push(value)}seen = value}}//es5function unique4(arr){return arr.filter((item,index,arr)=>arr.indexOf(item) === index)}function unique5(arr){return arr.concat().sort().filter((item,index,array)=>!index || item !== array[index - 1])}
var array = [1, 2, 1, 1, '1'];function unique(array) {var obj = {};return array.filter(function(item, index, array){return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)})}console.log(unique(array)); // [1, 2, "1"]var array = [1, 2, 1, 1, '1'];function unique(array) {var obj = {};return array.reduce((item,next)=>{obj[typeof next + next] ? '' : obj[typeof next +next] = true && item.push(next)return item},[])}console.log(unique(array)); // [1, 2, "1"]
// 利用reduce方法遍历数组,reduce第一个参数是遍历需要执行的函数,第二个参数是item的初始值33 var obj = {};34 arr = arr.reduce(function(item, next) {35 obj[next.key] ? '' : obj[next.key] = true && item.push(next);36 return item;37 }, []);
数组排序
function mpSort(arr){for(let i= 0;i<arr.length - 1 ;i++){for(let j= 0;j<arr.length - 1 - i;j++ ){if(arr[j]>arr[j+1]){let tem = arr[j+1]arr[j] = temarr[j+1] = arr[j]}}}return arr}console.log(mpSort([2,3,6,1,2,5,6,7,8]));
map和forEach的区别?
相同点:
都是循环遍历数组中的每一项,forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组),需要用哪个的时候就写哪个,匿名函数中的this都是指向window,只能遍历数组。复制代码
不同:
map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。(原数组进行处理之后对应的一个新的数组),map()方法不会改变原始数组map()方法不会对空数组进行检测forEach()方法用于调用数组的每个元素,将元素传给回调函数.(没有return,返回值是undefined,forEach对于空数组是不会调用回调函数的。)
1.for循环可以使用break跳出循环,但forEach不能。
2.for循环可以控制循环起点(i初始化的数字决定循环的起点),forEach只能默认从索引0开始。
3.for循环过程中支持修改索引(修改 i),但forEach做不到(底层控制index自增,我们无法左右它)。
EventLoop
EL就是我们经常说的JS事件循环机制, 因为JS是单线程的,所以JS在执行代码的时候会遵循一种机制 , 在EventLoop中有一种概念叫做任务 , 任务大致可以分为同步任务和异步任务,同步任务顾名思义就是立即执行的任务,同步代码会直接进入到主线程中执行 , 异步任务反之, 异步任务通常包括settimeout,setInterval,setimmeiate,Promise,mutationObeserve等,异步任务也细分为宏任务和微任务。
同步任务在JS代码执行的同时,进入到执行栈中进入执行。宏任务和微任务则会由浏览器统一管理异步任务的回调函数,进入不同的队列,其中宏任务会进入到宏任务队列,微任务会进入到微任务队列,队列遵守先进先出原则,当同步任务执行完毕后,就会检查微任务队列是否存在任务,如果有就将微任务队列依次执行,直至清空,至此第一次事件循环结束,然后浏览器就会进行UI渲染,这种重复的过程就是所谓的EventLoop。
什么是Symbol,他是使用场景是什么
引用场景:https://www.nblogs.com/archives/489/
事件委托的应用场景
列表
JS面试题
var a = 1,b =2;console.log(a+++b)var result = (function(){return 1;},function(){return "2"})()console.log(typeof result)
var a={num:1,toString:function(){return a.num++;}}if(a==1&&a==2&&a==3){console.log("恭喜答对啦!");}else{console.log("还是错了小子!");}
2.css
nth-of-child和nth-of-type的区别
nth-child是在父元素下所有的子元素中筛选指定的选择器,而nth-of-type是在父元素下指定的选择器中筛选
css选择器+和 ~
相邻兄弟选择器 (+) 介于两个选择器之间,当第二个元素紧跟在第一个元素之后,并且两个元素都是属于同一个父元素的子元素,则第二个元素将被选中。
兄弟选择符(~),位置无须紧邻,只须同层级,A~B 选择A元素之后所有同层级B元素。
CSS 常考面试题资料
position的属性值有哪些,它们的区别是什么
列举一下移动端适配方案
less和CSS的区别,less的函数了解过什么
BFC
居中布局
水平居中
- 行内元素:
text-align: center - 块级元素:
margin: 0 auto absolute + transformflex + justify-content: center
- 行内元素:
垂直居中
line-height: heightabsolute + transformflex + align-items: centertable
水平垂直居中
absolute + transformflex + justify-content + align-items
去除浮动影响,防止父级高度塌陷
通过增加尾元素清除浮动
:after / <br> : clear: both
- 创建父级 BFC
- 父级设置高度
一篇文章弄懂flex布局
请简述下CSS选择器的权重与优先规则。
这个没啥好说的,!important > 行内样式 > ID选择器 > 类选择器 > 元素选择器 > 通配符选择器 > 继承 > 浏览器默认属性。
用css实现一个三角形
BEM
3.浏览器与HTTP相关知识
垃圾回收
网络方面还可以吧(我默默的点点头),说说TCP的长连接和短连接的区别?
如果了解得比较深入,可以说说各自的优缺点。
一、短连接
概念:客户端与服务器建立连接开始通信,一次/指定次数通信结束之后就断开本次TCP连接,当下次再次通信时,再次建立TCP的链接。优点:不长期占用服务器的内存,那么服务器能处理的连接数量是比较多的缺点:1、因为要等到发送或者获取资源时,才去请求建立连接,而且http协议只能客户端主动向服务端发送数据后,服务端才返回对应的数据,那么服务端想主动发送数据给客户端呢?Websocket可以让服务端主动发送数据给客户端,或者要等到下一次要请求数据时,才发送,比如我们采用轮询(30秒或者更长)拉取消息, 那么服务器与客户端通信的实时性就丧失了。2、客户端采用轮询来实时获取信息,或者说大量的客户端使用短连接的方式通信,那么就浪费了大量的CPU和带宽资源用于建立连接和释放连接,存在资源浪费,甚至是无法建立连接。比如经典的http长轮询(微信网页客户端端)复制代码

二、长连接
概念: TCP与服务器建立连接之后一直处于连接状态,直到最后不再需要服务的时候才断开连接优点: 1、传输数据快2、服务器能够主动第一时间传输数据到客户端缺点: 1、因为客户端与服务器一直保持这种连接,那么在高并发分布式集群系统中客户端数量会越来越多,占 用很多的系统资源2、TCP本身是一种有状态的数据,在高并发分布式系统会导致后台设计比较难做复制代码

WebSocket,说说他的特点?
完了这是跟我杠上了,我依稀记得一些…,可以看出对基础考察得比较厉害
1、最大的特点就是服务端可以主动向客户端发送数据2、与 HTTP 协议有着良好的兼容性。默认端口是80和443,并握手阶段采用HTTP协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器。3、数据格式比较轻量,性能开销小,通信高效。4、可以发送文本,也可以发送二进制数据。5、没有同源限制,客户端可以与任意服务器通信。6、协议标识符是ws(如果加密,则为wss,加密层是TLS),服务器网址就是 URL。
说说三次握手四次挥手呗?
握手:(模拟甲请乙吃饭,我是这么理解的)
1、客户端发送一个SYN码给服务器,要求建立数据连接;(甲:来我家吃饭,给你一张SYN门卡)2、服务器拿到SYN和自己处理一个SYN(标志);叫SYN+ACK(确认包);发送给客户端,可以建立连接;(乙:我收到你的门卡了,我也给你一张叫ASK的卡)3、客户端再次发送ACK向服务器,服务器验证ACK没有问题,则建立起连接;(甲:来了啊进来吧!乙:出示你的ASK卡,我看看走错没,甲:这不是吗?快进来吧)复制代码
挥手:
1、客户端发送FIN(结束)报文,通知服务器数据已经传输完毕;(甲:小声嘀咕..这货吃完了吧?该走了吧)2、务器接收到之后,通知客户端我收到了FIN,发送ACK(确认)给客户端,数据还没有传输完成(乙:我听到你说的话了,你确定赶我走吗?但是我还没吃完哎!)3、服务器已经传输完毕,再次发送FIN通知客户端,数据已经传输完毕(乙:我吃完了,别解释了,果然不爱了,我走了)4、客户端再次发送ACK,进入TIME_WAIT状态;服务器和客户端关闭连接;(甲:把你的ACK还给你,把我家米都吃空了)
为什么建立连接是三次握手,而断开连接是四次挥手呢?
建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个 报文里发送给客户端。而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了 但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一 些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分 开发送,从而导致多了一次。
输入 URL 到页面渲染的整个流程
https://segmentfault.com/a/1190000013662126
- DNS 解析:将域名解析成 IP 地址
- TCP 连接:TCP 三次握手
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 断开连接:TCP 四次挥手
HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
http缓存详解,http缓存推荐方案
看完这篇HTTP,跟面试官扯皮就没问题了
http请求头包含哪些东西?
HTTP Code
能说下ajax吗?
| 属性 | 描述 |
|---|---|
| onreadystatechange | 存储函数(或函数名),每当 readyState 属性改变时,就会调用该函数。 |
| readyState | 存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。0: 请求未初始化1: 服务器连接已建立2: 请求已接收3: 请求处理中4: 请求已完成,且响应已就绪 |
| status | 200: “OK” 404: 未找到页面 |
九种跨域方式实现原理(完整版)
设计模式
常用数据结构
4.vue
Vue路由守卫有哪些,怎么设置,使用场景等
常用的两个路由守卫:router.beforeEach 和 router.afterEach 每个守卫方法接收三个参数: to: Route: 即将要进入的目标 路由对象 from: Route: 当前导航正要离开的路由 next: Function: 一定要调用该方法来 resolve 这个钩子。 在项目中,一般在beforeEach这个钩子函数中进行路由跳转的一些信息判断。 判断是否登录,是否拿到对应的路由权限等等。
响应式原理
Vue是采用数据劫持配合发布者-订阅者模式,通过Object.defineProperty来()来劫持各个属性的getter和setter 在数据发生变化的时候,发布消息给依赖收集器,去通知观察者,做出对应的回调函数去更新视图。 具体就是: MVVM作为绑定的入口,整合Observe,Compil和Watcher三者,通过Observe来监听model的变化 通过Compil来解析编译模版指令,最终利用Watcher搭起Observe和Compil之前的通信桥梁 从而达到数据变化 => 更新视图,视图交互变化(input) => 数据model变更的双向绑定效果。
vue2响应式原理的核心就是用object.definePrototype遍历每个属性,设置get和set,在get时进行依赖收集 , set时进行派发更新 .
Vue中的computed和watch的区别?
1、computed:是一个有缓存的watcher,依赖的属性发生变化就会更新视图。 适用于计算比较消耗性能的计算场景。当表达式过于 复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理 2、watch:没有缓存性,更多的是起到一个观察的作用。可以监听某些数据执行回调,当需要深度监听时,将deep:true开启即可。
双向绑定原理
通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终 利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化->视图更新。在 初始化vue实例时,遍历data这个对象,给每一个键值对利用Object.definedProperty 对data的键值对新增get和set方法,利用了事件监听DOM的机制,让视图去改变数据。
用过keep-alive吗,说说对它的理解?
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,有以下特性:
1、一般结合路由和动态组件一起使用,用于缓存组件。
2、提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹 配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高。
3、对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated, 当组件被移除时,触发钩子函数 deactivated。
组件中为什么data是一个函数
data() { return { message: “123”, } } 原因:因为组件是需要被复用的,所以必须是一个函数,如果是一个对象,作用域没有分开,子组件 的data属性值会相互影响。是一个函数的话那么每个实例可以维护一份被返回对象的独立的拷贝,组 件之间的 data 属性值不会互相影响。
那为什么new Vue里data可以是一个对象?
new Vue({el: '#app',router,template: '<App/>',components: {App}})原因:因为JS里的对象是引用关系,而且new Vue是不会被复用的,所以不存在引用对象的问题。
Virtual DOM了解吗?
Vue 常考基础知识点
https://juejin.cn/book/6844733763675488269/section/6844733763780345869
https://juejin.cn/book/6844733763675488269/section/6844733763784556552
5.react
6.性能优化
假设现在有1000条数据的长列表,说下不用分页你怎么优化加载?
重绘(Repaint)和回流(Reflow)
重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。
- 重绘是当节点需要更改外观而不会影响布局的,比如改变
color就叫称为重绘 - 回流是布局或者几何属性需要改变就称为回流。
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
以下几个动作可能会导致性能问题:
- 改变
window大小 - 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒模型
并且很多人不知道的是,重绘和回流其实也和 Eventloop 有关。
- 当 Eventloop 执行完 Microtasks 后,会判断
document是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。 - 然后判断是否有
resize或者scroll事件,有的话会去触发事件,所以resize和scroll事件也是至少 16ms 才会触发一次,并且自带节流功能。 - 判断是否触发了 media query
- 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执行
requestAnimationFrame回调 - 执行
IntersectionObserver回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 - 更新界面
- 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行
requestIdleCallback回调。
以上内容来自于 HTML 文档。
既然我们已经知道了重绘和回流会影响性能,那么接下来我们将会来学习如何减少重绘和回流的次数。
减少重绘和回流
- 使用
transform替代top
<div class="test"></div><style>.test {position: absolute;top: 10px;width: 100px;height: 100px;background: red;}</style><script>setTimeout(() => {// 引起回流document.querySelector('.test').style.top = '100px'}, 1000)</script>
使用
visibility替换display: none,因为前者只会引起重绘,后者会引发回流(改变了布局)不要把节点的属性值放在一个循环里当成循环里的变量
for(let i = 0; i < 1000; i++) {// 获取 offsetTop 会导致回流,因为需要去获取正确的值console.log(document.querySelector('.test').style.offsetTop)}
不要使用
table布局,可能很小的一个小改动会造成整个table的重新布局动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用
requestAnimationFrameCSS 选择符从右往左匹配查找,避免节点层级过多
将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于
video标签来说,浏览器会自动将该节点变为图层。
设置节点为图层的方式有很多,我们可以通过以下几个常用属性可以生成新图层will-changevideo、iframe标签
性能优化琐碎事
7.webpack/前端工程化
发布工具
https://juejin.cn/book/6844733763675488269/section/6844733763776315399
8.算法
一个树形结构数据,不用递归,如何用遍历去访问它?
9.综合问题/技术相关
组件封装
h5,和pc端有什么不同。
10.综合问题/职业相关
请描述一下 React 和 Vue的区别
1.设计思想vue的官网中说它是一款渐进式框架,采用自底向上增量开发的设计。react主张函数式编程,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以手动实现,比如借助 onChange 和 setState 来实现一个双向的数据流。2.编写语法Vue推荐的做法是webpack+vue-loader的单文件组件格式,vue保留了html、css、js分离的写法React的开发者可能知道,react是没有模板的,直接就是一个渲染函数,它中间返回的就是一个虚拟DOM树,React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js'。3.构建工具vue提供了CLI 脚手架,可以帮助你非常容易地构建项目。React 在这方面也提供了 create-react-app,但是现在还存在一些局限性,不能配置等等4.数据绑定vue是实现了双向数据绑定的mvvm框架,当视图改变更新模型层,当模型层改变更新视图层。在vue中,使用了双向绑定技术,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。(这里我们可以继续深入讲解一下双向数据绑定的原理,我之前的文章手写Vue源码可参考)react是单向数据流,react中属性是不允许更改的,状态是允许更改的。react中组件不允许通过this.state这种方式直接更改组件的状态。自身设置的状态,可以通过setState来进行更改。(注意:React中setState是异步的,导致获取dom可能拿的还是之前的内容,所以我们需要在setState第二个参数(回调函数)中获取更新后的新的内容。)【这里如果你了解深入的话可以尝试描述一下React中setState的异步操作是怎么实现的,Vue中的更新是通过微任务等】5.diff算法vue中diff算法实现流程:1.在内存中构建虚拟dom树2.将内存中虚拟dom树渲染成真实dom结构3.数据改变的时候,将之前的虚拟dom树结合新的数据生成新的虚拟dom树4.将此次生成好的虚拟dom树和上一次的虚拟dom树进行一次比对(diff算法进行比对),来更新只需要被替换的DOM,而不是全部重绘。在Diff算法中,只平层的比较前后两棵DOM树的节点,没有进行深度的遍历。5.会将对比出来的差异进行重新渲染react中diff算法实现流程:DOM结构发生改变-----直接卸载并重新createDOM结构一样-----不会卸载,但是会update变化的内容所有同一层级的子节点.他们都可以通过key来区分-----同时遵循1.2两点(其实这个key的存在与否只会影响diff算法的复杂度,换言之,你不加key的情况下,diff算法就会以暴力的方式去根据一二的策略更新,但是你加了key,diff算法会引入一些另外的操作)
React会逐个对节点进行更新,转换到目标节点。而最后插入新的节点,涉及到的DOM操作非常多。diff总共就是移动、删除、增加三个操作,而如果给每个节点唯一的标识(key),那么React优先采用移动的方式,能够找到正确的位置去插入新的节点。vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制。
以上内容来自于Vue 和 React 的优点分别是什么?,大家说的越多理解的越深越好。我只说了其中的三点。
前端常用的数据请求格式有哪些?都有些什么特点?
这个问题在http中已经回答了一部分了,这里就在单独详细的描述一下使用场景
Get/Post/Delete/Patch/Put经常用的这五种,其他的就不说了通常:我们使用Get请求来获取数据我们使用Post请求来发送数据我们使用Put请求来更新数据我们使用Delete请求来删除数据我们使用Patch请求用于对资源应用部分修改。Get和Post的区别:1.Get 请求能缓存,Post 不能2.Post 相对 Get 安全一点点,因为Get 请求都包含在 URL 里,(当然你想写到 body 里也是可以的),且会被浏览器保存历史纪录。Post 不会,但是在抓包的情况下都是一样的。4.URL有长度限制,会影响 Get 请求,但是这个长度限制是浏览器规定的,不是 RFC 规定的5.Post 支持更多的编码类型且不对数据类型限制
二次封装axios,描述一下你在项目中封装axios的思路和想法
关于axios等封装,我之前有一篇简易封装axios的文章,大家可以简单参考。通常来说,我们在二次封装axios,一般会引入UI组件的一些Message和Loading组件用来做提示信息。1.通过获取存储在浏览器端,或者是vuex中的token信息,判断是否跳转登录页面2.在获取到token的情况下设置自定义请求头部信息3.在响应事件中,根据返回的不同状态码,根据业务需求,使用switch判断跳转页面还是发出提示信息。4.封装请求和响应事件的返回结果,使用Promise封装。5.增加请求loading和提示信息。简单版本大致如上,复杂版本需要根据业务进行对应的封装。
请用一个例子来形象的描述原型链
每个门派都有一个祖师爷。学徒在山上学艺,学成下山后谨记师门教导,施展一身武艺。恰逢一日对敌,面对敌人的怪异武功,师门好像未曾教过破解之法,便高喊一声祖师爷救我,刹那间一道白光降于头顶,祖师爷灵魂附体,一套精绝凌厉的拳法杀得敌人措手不及。但敌人也极是难缠,恐怕非要那门传说中从天而降的掌法才能制敌。你心中暗自着急,催促着祖师爷赶快发招,这时只听身内传来了祖师的声音:“MD这破掌法当年偷懒没学,我去把我师祖也叫来问问...”每个门派(FunctionX)都有一个祖师爷(prototype)。学徒(object)在山上学艺(= new FunctionX),学成下山后谨记师门教导,施展一身武艺(http://object.xxxobject.xxx)。恰逢一日对敌,面对敌人的怪异武功,师门好像未曾教过破解之法(object对象没有yyy方法),便高喊一声祖师爷救我,刹那间一道白光降于头顶,祖师爷(__proto__)灵魂附体,一套精绝凌厉的拳法杀得敌人措手不及(继续寻找原型中是否有yyy方法)。但敌人也极是难缠,恐怕非要那门传说中从天而降的掌法才能制敌。你心中暗自着急,催促着祖师爷赶快发招,这时只听身内传来了祖师的声音:“MD这破掌法当年偷懒没学,我去把我师祖也叫来问问...”(如果原型中没有yyy方法,则继续查找原型的原型,是谓原型链)
做过单点登录吗?了解他是怎么实现的吗?
实话实说没做过,但我看过一些关于单点登录的技术博客,大概整理原理如下:
概念:
简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。列如:淘宝、天猫都属于阿里旗下的产品,当用户登录淘宝后,再打开天猫,系统便自动帮用户登录了天猫,这种就是用单点登录实现的。复制代码
流程:
1、用户访问A系统,系统A发现用户未登录,跳转至sso认证中心,并把自己的地址作为参数。2、sso认证中心发现用户未登录,则引导用户到登录页面。3、用户输入用户名和密码提交登录。4、sso认证中心验证用户信息,创建用户->sso之间的会话(全局会话),同时创建授权令牌。5、sso认证中心带着令牌跳转到A系统6、系统A拿到令牌,去sso认证中心校验令牌是否有效。7、sso认证中心校验令牌,返回有效,注册系统A。8、系统A使用该令牌创建与用户的会话(局部会话),返回请求资源。9、用户访问系统B。10、系统B发现用户未登录,跳转至sso认证中心,也将自己的地址作为参数。11、sso认证中心发现用户已登录,跳转到系统B,并附上令牌。12、系统B拿到令牌,去sso认证中心校验令牌是否有效。13、sso认证中心校验令牌,返回有效,注册系统B。14、系统B使用该令牌创建与用户的局部会话,返回请求资源。复制代码
注意:
1、局部会话存在,全局会话一定存在2、全局会话存在,局部会话不一定存在3、全局会话销毁,局部会话必须销毁
说下工作中你解决的觉得最难的事情是什么
还记得上篇文章中第一家公司我便遇到了这个问题,不知道怎么答才好,于是我问他们技术总监下次我该怎么答,这次我就根据上次总结后展开说了,我说我项目比较简单,技术上没有太难的东西,真的觉得难还是自己负责项目开发时,关于项目推进,如何与同事有效沟通等等展开说了。
所以说要多总结啊,别一个问题次次遇到都是不知道,没听过,不了解,面试过程中不管任何问题,只要你开头答了,都比你啥都不说要强,而且是强很多,这也是考你随机应变的一部分。
知道为什么很多公司要问这两个问题吗?因为这两个问题的答案,基本能给你以往经历定级了,有没有解决过特别复杂的问题,有没有技术深入研究,有没有代码追求等等在你的答案中都能体现出来。所以这两个问题一定要好好回顾自己过往经历,好公司必问!
谈谈你对于未来的规划
初步规划是想向全栈发展,再往后就是管理层。然后面试官就问我为什么想转全栈,我说为了提升自己的竞争力,我有关注招聘信息,从中我能得知现在行业需要什么技术,哪些能提升我的竞争力;面试官又问为什么有做管理的想法,这个我就直说了,同事朋友都觉得性格很好,有耐心,对于技术也有追求,很适合带人,当然,做管理不是技术好就可以了,只是他们的话在我脑中留下了一枚种子,如果真要往这方面发展,我需要学习的还很多。
说完这些面试官就对于我未来发展给出建议了,大致意思就是,学后端能拓展你对于前端问题的看法,比如很多问题在前端会遇到后端就不会,比如一个问题在前端做很麻烦,但在后端有专门的思路方法解决这件事,那么在你了解后端后,下次你遇到这样的问题,在你的脑中会潜移默化,我如何在前端中用后端现有思路解决它。其实总结面试官的话,永远学思想而不是学框架。
在上家公司有没有你觉得可以改进的事
说说你对于技术新旧的看法,如何抉择?
下面是我面试回答的观点,不一定对,大家就当个参考:
新不代表好,旧不代表过时,如何选择还是看它能为我们带来什么,优势是什么,缺点是什么,比如风险和不稳定性。举个例子,JQ过时吗?其实站在DOM操作层面,JQ在各浏览器间优秀的兼容性以及操作便捷性,无疑是一个不错的选择,它为什么”过时”呢?这是因为目前主流框架vue,angular,react已经不需要我们过多关注视图层,操作dom的需求少了这才导致JQ”过时”。
如何抉择一个框架呢?我觉得得看它与我们需求的匹配度,拿bootstrap举例,很多人不爱使用bootstrap,觉得我自己也能做适配,何必用它。这就得看具体需求了,如果我们一个项目是要做到PC,平板,移动端自动适配,用bootstrap就是首选,因为这个框架主打的就是栅格化系统一套样式实现响应式,让你自己用媒体查询自己写,那不得写疯。但如果我们兼容的只是部分容器,需求不大,这时候用bootstrap就有种杀鸡焉用牛刀的感觉,很不值得,所以怎么选还是得根据需求而定。
为什么从上家公司离职?
两个方面,一是公司开发需求少,有种温水煮青蛙的感觉,会很焦虑,害怕自己被这个行业淘汰,所以顶着疫情也要选择离职。第二个方面是孤独,公司技术氛围不是很好,大家对于技术热情不是很高,我会学很多有用的知识,想讨论却发现没一个人可以交流,因为渴望交流,所以养成了写博客的习惯,与网上同行们交流。
如何写好一封简历
面试常用技巧
前端安全
课件
ES6新特性
1.ES6引入来严格模式变量必须声明后在使用函数的参数不能有同名属性, 否则报错不能使用with语句 (说实话我基本没用过)不能对只读属性赋值, 否则报错不能使用前缀0表示八进制数,否则报错 (说实话我基本没用过)不能删除不可删除的数据, 否则报错不能删除变量delete prop, 会报错, 只能删除属性delete global[prop]eval不会在它的外层作用域引入变量eval和arguments不能被重新赋值arguments不会自动反映函数参数的变化不能使用arguments.caller (说实话我基本没用过)不能使用arguments.callee (说实话我基本没用过)禁止this指向全局对象不能使用fn.caller和fn.arguments获取函数调用的堆栈 (说实话我基本没用过)增加了保留字(比如protected、static和interface)2.关于let和const新增的变量声明3.变量的解构赋值4.字符串的扩展includes():返回布尔值,表示是否找到了参数字符串。startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。5.数值的扩展Number.isFinite()用来检查一个数值是否为有限的(finite)。Number.isNaN()用来检查一个值是否为NaN。6.函数的扩展函数参数指定默认值7.数组的扩展扩展运算符8.对象的扩展对象的解构9.新增symbol数据类型10.Set 和 Map 数据结构ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 Set 本身是一个构造函数,用来生成 Set 数据结构。Map它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。11.ProxyProxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。Vue3.0使用了proxy12.PromisePromise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。特点是:对象的状态不受外界影响。一旦状态改变,就不会再变,任何时候都可以得到这个结果。13.async 函数async函数对 Generator 函数的区别:(1)内置执行器。Generator 函数的执行必须靠执行器,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。(2)更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。(3)正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。(4)返回值是 Promise。async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。14.Classclass跟let、const一样:不存在变量提升、不能重复声明...ES6 的class可以看作只是一个语法糖,它的绝大部分功能ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。15.ModuleES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。import和export命令以及export和export default的区别
