箭头函数的this指向哪⾥?
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕捉其所在高低⽂的 this 值,作为⾃⼰的 this 值,并且因为没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被扭转。
能够⽤Babel了解⼀下箭头函数:
// ES6 const obj = { getArrow() { return () => { console.log(this === obj); }; } }
转化后:
// ES5,由 Babel 转译var obj = { getArrow: function getArrow() { var _this = this; return function () { console.log(_this === obj); }; } };
代码输入后果
const first = () => (new Promise((resolve, reject) => { console.log(3); let p = new Promise((resolve, reject) => { console.log(7); setTimeout(() => { console.log(5); resolve(6); console.log(p) }, 0) resolve(1); }); resolve(2); p.then((arg) => { console.log(arg); });}));first().then((arg) => { console.log(arg);});console.log(4);
输入后果如下:
374125Promise{<resolved>: 1}
代码的执行过程如下:
- 首先会进入Promise,打印出3,之后进入上面的Promise,打印出7;
- 遇到了定时器,将其退出宏工作队列;
- 执行Promise p中的resolve,状态变为resolved,返回值为1;
- 执行Promise first中的resolve,状态变为resolved,返回值为2;
- 遇到p.then,将其退出微工作队列,遇到first().then,将其退出工作队列;
- 执行里面的代码,打印出4;
- 这样第一轮宏工作就执行完了,开始执行微工作队列中的工作,先后打印出1和2;
- 这样微工作就执行完了,开始执行下一轮宏工作,宏工作队列中有一个定时器,执行它,打印出5,因为执行曾经变为resolved状态,所以
resolve(6)
不会再执行; - 最初
console.log(p)
打印出Promise{<resolved>: 1}
;
对事件循环的了解
因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保障代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会始终期待其返回后果,而是会将这个事件挂起,继续执行执行栈中的其余工作。当异步事件执行结束后,再将异步事件对应的回调退出到一个工作队列中期待执行。工作队列能够分为宏工作队列和微工作队列,当以后执行栈中的事件执行结束后,js 引擎首先会判断微工作队列中是否有工作能够执行,如果有就将微工作队首的事件压入栈中执行。当微工作队列中的工作都执行实现后再去执行宏工作队列中的工作。
Event Loop 执行程序如下所示:
- 首先执行同步代码,这属于宏工作
- 当执行完所有同步代码后,执行栈为空,查问是否有异步代码须要执行
- 执行所有微工作
- 当执行完所有微工作后,如有必要会渲染页面
- 而后开始下一轮 Event Loop,执行宏工作中的异步代码
事件是什么?事件模型?
事件是用户操作网页时产生的交互动作,比方 click/move, 事件除了用户触发的动作外,还能够是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,蕴含了该事件产生时的所有相干信息( event 的属性)以及能够对事件进行的操作( event 的办法)。
事件是用户操作网页时产生的交互动作或者网页自身的一些操作,古代浏览器一共有三种事件模型:
- DOM0 级事件模型,这种模型不会流传,所以没有事件流的概念,然而当初有的浏览器反对以冒泡的形式实现,它能够在网页中间接定义监听函数,也能够通过 js 属性来指定监听函数。所有浏览器都兼容这种形式。间接在dom对象上注册事件名称,就是DOM0写法。
- IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行指标元素绑定的监听事件。而后是事件冒泡阶段,冒泡指的是事件从指标元素冒泡到 document,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attachEvent 来增加监听函数,能够增加多个监听函数,会按程序顺次执行。
- DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕捉阶段。捕捉指的是事件从 document 始终向下流传到指标元素,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。前面两个阶段和 IE 事件模型的两个阶段雷同。这种事件模型,事件绑定的函数是addEventListener,其中第三个参数能够指定事件是否在捕捉阶段执行。
AJAX
实现:利用 XMLHttpRequest
// getconst getJSON = (url) => { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); // open 办法用于指定 HTTP 申请的参数: method, url, async(是否异步,默认true) xhr.open("GET", url, false); xhr.setRequestHeader('Content-Type', 'application/json'); // onreadystatechange 属性指向一个监听函数。 // readystatechange 事件产生时(实例的readyState属性变动),就会执行这个属性。 xhr.onreadystatechange = function(){ // 4 示意服务器返回的数据曾经齐全接管,或者本次接管曾经失败 if(xhr.readyState !== 4) return; // 申请胜利,基本上只有2xx和304的状态码,示意服务器返回是失常状态 if(xhr.status === 200 || xhr.status === 304) { // responseText 属性返回从服务器接管到的字符串 resolve(xhr.responseText); } // 申请失败 else { reject(new Error(xhr.responseText)); } } xhr.send(); });}// postconst postJSON = (url, data) => { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open("POST", url); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function(){ if(xhr.readyState !== 4) return; if(xhr.status === 200 || xhr.status === 304) { resolve(xhr.responseText); } else { reject(new Error(xhr.responseText)); } } xhr.send(data); });}
什么是文档的预解析?
Webkit 和 Firefox 都做了这个优化,当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载前面须要通过网络加载的资源。这种形式能够使资源并行加载从而使整体速度更快。须要留神的是,预解析并不扭转 DOM 树,它将这个工作留给主解析过程,本人只解析内部资源的援用,比方内部脚本、样式表及图片。
实现 JSONP 跨域
JSONP 外围原理:script
标签不受同源策略束缚,所以能够用来进行跨域申请,长处是兼容性好,然而只能用于 GET 申请;
实现:
const jsonp = (url, params, callbackName) => { const generateUrl = () => { let dataSrc = ""; for(let key in params) { if(params.hasOwnProperty(key)) { dataSrc += `${key}=${params[key]}&` } } dataSrc += `callback=${callbackName}`; return `${url}?${dataSrc}`; } return new Promise((resolve, reject) => { const scriptEle = document.createElement('script'); scriptEle.src = generateUrl(); document.body.appendChild(scriptEle); window[callbackName] = data => { resolve(data); document.removeChild(scriptEle); } });}
代码输入后果
async function async1 () { console.log('async1 start'); await new Promise(resolve => { console.log('promise1') }) console.log('async1 success'); return 'async1 end'}console.log('srcipt start')async1().then(res => console.log(res))console.log('srcipt end')
输入后果如下:
script startasync1 startpromise1script end
这里须要留神的是在async1
中await
前面的Promise是没有返回值的,也就是它的状态始终是pending
状态,所以在await
之后的内容是不会执行的,包含async1
前面的 .then
。
代码输入后果
function foo() { console.log( this.a );}function doFoo() { foo();}var obj = { a: 1, doFoo: doFoo};var a = 2; obj.doFoo()
输入后果:2
在Javascript中,this指向函数执行时的以后对象。在执行foo的时候,执行环境就是doFoo函数,执行环境为全局。所以,foo中的this是指向window的,所以会打印出2。
罕用的正则表达式有哪些?
// (1)匹配 16 进制色彩值var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;// (2)匹配日期,如 yyyy-mm-dd 格局var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;// (3)匹配 qq 号var regex = /^[1-9][0-9]{4,10}$/g;// (4)手机号码正则var regex = /^1[34578]\d{9}$/g;// (5)用户名正则var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;
说一下slice splice split 的区别?
// slice(start,[end])// slice(start,[end])办法:该办法是对数组进行局部截取,该办法返回一个新数组// 参数start是截取的开始数组索引,end参数等于你要取的最初一个字符的地位值加上1(可选)。// 蕴含了源函数从start到 end 所指定的元素,然而不包含end元素,比方a.slice(0,3);// 如果呈现正数就把正数与长度相加后再划分。// slice中的正数的绝对值若大于数组长度就会显示所有数组// 若参数只有一个,并且参数大于length,则为空。// 如果完结地位小于起始地位,则返回空数组// 返回的个数是end-start的个数// 不会扭转原数组var arr = [1,2,3,4,5,6]/*console.log(arr.slice(3))//[4,5,6] 从下标为0的到3,截取3之后的数console.log(arr.slice(0,3))//[1,2,3] 从下标为0的中央截取到下标为3之前的数console.log(arr.slice(0,-2))//[1,2,3,4]console.log(arr.slice(-4,4))//[3,4]console.log(arr.slice(-7))//[1,2,3,4,5,6]console.log(arr.slice(-3,-3))// []console.log(arr.slice(8))//[]*/// 集体总结:slice的参数如果是负数就从左往右数,如果是正数的话就从右往左边数,// 截取的数组与数的方向统一,如果是2个参数则截取的是数的交加,没有交加则返回空数组 // ps:slice也能够切割字符串,用法和数组一样,但要留神空格也算字符// splice(start,deletecount,item)// start:起始地位// deletecount:删除位数// item:替换的item// 返回值为被删除的字符串// 如果有额定的参数,那么item会插入到被移除元素的地位上。// splice:移除,splice办法从array中移除一个或多个数组,并用新的item替换它们。//举一个简略的例子 var a=['a','b','c']; var b=a.splice(1,1,'e','f'); console.log(a) //['a', 'e', 'f', 'c'] console.log(b) //['b'] var a = [1, 2, 3, 4, 5, 6];//console.log("被删除的为:",a.splice(1, 1, 8, 9)); //被删除的为:2// console.log("a数组元素:",a); //1,8,9,3,4,5,6// console.log("被删除的为:", a.splice(0, 2)); //被删除的为:1,2// console.log("a数组元素:", a) //3,4,5,6console.log("被删除的为:", a.splice(1, 0, 2, 2)) //插入 第二个数为0,示意删除0个 console.log("a数组元素:", a) //1,2,2,2,3,4,5,6// split(字符串)// string.split(separator,limit):split办法把这个string宰割成片段来创立一个字符串数组。// 可选参数limit能够限度被宰割的片段数量。// separator参数能够是一个字符串或一个正则表达式。// 如果separator是一个空字符,会返回一个单字符的数组,不会扭转原数组。var a="0123456"; var b=a.split("",3); console.log(b);//b=["0","1","2"]// 留神:String.split() 执行的操作与 Array.join 执行的操作是相同的。
说一下HTTP和HTTPS协定的区别?
1、HTTPS协定须要CA证书,费用较高;而HTTP协定不须要2、HTTP协定是超文本传输协定,信息是明文传输的,HTTPS则是具备安全性的SSL加密传输协定;3、应用不同的连贯形式,端口也不同,HTTP协定端口是80,HTTPS协定端口是443;4、HTTP协定连贯很简略,是无状态的;HTTPS协定是具备SSL和HTTP协定构建的可进行加密传输、身份认证的网络协议,比HTTP更加平安
首屏和白屏工夫如何计算
首屏工夫的计算,能够由 Native WebView 提供的相似 onload 的办法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是onPageFinished事件。
白屏的定义有多种。能够认为“没有任何内容”是白屏,能够认为“网络或服务异样”是白屏,能够认为“数据加载中”是白屏,能够认为“图片加载不进去”是白屏。场景不同,白屏的计算形式就不雷同。
办法1:当页面的元素数小于x时,则认为页面白屏。比方“没有任何内容”,能够获取页面的DOM节点数,判断DOM节点数少于某个阈值X,则认为白屏。 办法2:当页面呈现业务定义的错误码时,则认为是白屏。比方“网络或服务异样”。 办法3:当页面呈现业务定义的特征值时,则认为是白屏。比方“数据加载中”。
代码输入后果
var length = 10;function fn() { console.log(this.length);}var obj = { length: 5, method: function(fn) { fn(); arguments[0](); }};obj.method(fn, 1);
输入后果: 10 2
解析:
- 第一次执行fn(),this指向window对象,输入10。
- 第二次执行arguments[0],相当于arguments调用办法,this指向arguments,而这里传了两个参数,故输入arguments长度为2。
vue实现双向数据绑定原理是什么?
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script> <!-- 引入vue文件 --> <div id="box"> <new-input v-bind:name.sync="name"></new-input> {{name}} <!-- 小胡子语法 --> <input type="text" v-model="name" /> </div> <script> Vue.component("new-input", { props: ["name"], data: function () { return { newName: this.name, }; }, template: `<label><input type="text" @keyup="changgeName" v-model="newName" /> 你的名字:</label>`, // 模板字符串 methods: { changgeName: function () { this.$emit("update:name", this.newName); }, }, watch: { name: function (v) { this.newName = v; }, }, // 监听 }); new Vue({ el: "#box", //挂载实例 data: { name: "nick", }, //赋初始值 }); </script> </body></html>
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <input type="text" v-mode="msg" /> <p v-mode="msg"></p> <script> const data = { msg: "你好", }; const input = document.querySelector("input"); const p = document.querySelector("p"); input.value = data.msg; p.innerHTML = data.msg; //视图变数据跟着变 input.addEventListener("input", function () { data.msg = input.value; }); //数据变视图变 let temp = data.msg; Object.defineProperty(data, "msg", { get() { return temp; }, set(value) { temp = value; //视图批改 input.value = temp; p.innerHTML = temp; }, }); data.msg = "小李"; </script> </body></html>
八股文我不想写了本人百度去
为什么会有BigInt的提案?
JavaScript中Number.MAX_SAFE_INTEGER示意最⼤平安数字,计算结果是9007199254740991,即在这个数范畴内不会呈现精度失落(⼩数除外)。然而⼀旦超过这个范畴,js就会呈现计算不精确的状况,这在⼤数计算的时候不得不依附⼀些第三⽅库进⾏解决,因而官⽅提出了BigInt来解决此问题。
如何判断一个对象是否属于某个类?
- 第一种形式,应用 instanceof 运算符来判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
- 第二种形式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,然而这种形式不是很平安,因为 constructor 属性能够被改写。
- 第三种形式,如果须要判断的是某个内置的援用类型的话,能够应用 Object.prototype.toString() 办法来打印对象的[[Class]] 属性来进行判断。
webpack 层面如何做性能优化
优化前的筹备工作
- 筹备基于工夫的剖析工具:咱们须要一类插件,来帮忙咱们统计我的项目构建过程中在编译阶段的耗时状况。
speed-measure-webpack-plugin
剖析插件加载的工夫 - 应用
webpack-bundle-analyzer
剖析产物内容
代码优化:
无用代码打消,是许多编程语言都具备的优化伎俩,这个过程称为 DCE (dead code elimination),即 删除不可能执行的代码;
例如咱们的 UglifyJs
,它就会帮咱们在生产环境中删除不可能被执行的代码,例如:
var fn = function() { return 1; // 上面代码便属于 不可能执行的代码; // 通过 UglifyJs (Webpack4+ 已内置) 便会进行 DCE; var a = 1; return a;}
摇树优化 (Tree-shaking),这是一种形象比喻。咱们把打包后的代码比喻成一棵树,这里其实示意的就是,通过工具 "摇" 咱们打包后的 js 代码,将没有应用到的无用代码 "摇" 下来 (删除)。即 打消那些被 援用了但未被应用 的模块代码。
- 原理: 因为是在编译时优化,因而最根本的前提就是语法的动态剖析,ES6的模块机制 提供了这种可能性。不须要运行时,便可进行代码字面上的动态剖析,确定相应的依赖关系。
问题: 具备 副作用 的函数无奈被
tree-shaking
- 在援用一些第三方库,须要去察看其引入的代码量是不是合乎预期;
- 尽量写纯函数,缩小函数的副作用;
- 可应用
webpack-deep-scope-plugin
,能够进行作用域剖析,缩小此类情况的产生,但仍须要留神;
code-spliting: 代码宰割技术 ,将代码宰割成多份进行 懒加载 或 异步加载,防止打包成一份后导致体积过大,影响页面的首屏加载;
Webpack
中应用SplitChunksPlugin
进行拆分;- 按 页面 拆分: 不同页面打包成不同的文件;
按 性能 拆分:
- 将相似于播放器,计算库等大模块进行拆分后再懒加载引入;
- 提取复用的业务代码,缩小冗余代码;
- 按 文件批改频率 拆分: 将第三方库等不常批改的代码独自打包,而且不扭转其文件 hash 值,能最大化使用浏览器的缓存;
scope hoisting : 作用域晋升,将扩散的模块划分到同一个作用域中,防止了代码的反复引入,无效缩小打包后的代码体积和运行时的内存损耗;
编译性能优化:
- 降级至 最新 版本的
webpack
,能无效晋升编译性能; 应用
dev-server
/ 模块热替换 (HMR
) 晋升开发体验;- 监听文件变动 疏忽 node_modules 目录能无效进步监听时的编译效率;
放大编译范畴
modules
: 指定模块门路,缩小递归搜寻;mainFields
: 指定入口文件形容字段,缩小搜寻;noParse
: 防止对非模块化文件的加载;includes/exclude
: 指定搜寻范畴/排除不必要的搜寻范畴;alias
: 缓存目录,防止反复寻址;
babel-loader
- 疏忽
node_moudles
,防止编译第三方库中曾经被编译过的代码 - 应用
cacheDirectory
,能够缓存编译后果,防止多次重复编译
- 疏忽
多过程并发
webpack-parallel-uglify-plugin
: 可多过程并发压缩 js 文件,进步压缩速度;HappyPack
: 多过程并发文件的Loader
解析;
第三方库模块缓存:
DLLPlugin
和DLLReferencePlugin
能够提前进行打包并缓存,防止每次都从新编译;
应用剖析
Webpack Analyse / webpack-bundle-analyzer
对打包后的文件进行剖析,寻找可优化的中央- 配置profile:true,对各个编译阶段耗时进行监控,寻找耗时最多的中央
source-map
:- 开发:
cheap-module-eval-source-map
- 生产:
hidden-source-map
;
- 开发:
优化webpack打包速度
缩小文件搜寻范畴
- 比方通过别名
loader
的test
,include & exclude
Webpack4
默认压缩并行Happypack
并发调用babel
也能够缓存编译Resolve
在构建时指定查找模块文件的规定- 应用
DllPlugin
,不必每次都从新构建 externals
和DllPlugin
解决的是同一类问题:将依赖的框架等模块从构建过程中移除。它们的区别在于- 在 Webpack 的配置方面,
externals
更简略,而DllPlugin
须要独立的配置文件。 DllPlugin
蕴含了依赖包的独立构建流程,而externals
配置中不蕴含依赖框架的生成形式,通常应用已传入 CDN 的依赖包externals
配置的依赖包须要独自指定依赖模块的加载形式:全局对象、CommonJS、AMD 等- 在援用依赖包的子模块时,
DllPlugin
毋庸更改,而externals
则会将子模块打入我的项目包中
- 在 Webpack 的配置方面,
优化打包体积
- 提取第三方库或通过援用内部文件的形式引入第三方库
- 代码压缩插件
UglifyJsPlugin
- 服务器启用
gzip
压缩 - 按需加载资源文件
require.ensure
- 优化
devtool
中的source-map
- 剥离
css
文件,独自打包 - 去除不必要插件,通常就是开发环境与生产环境用同一套配置文件导致
Tree Shaking
在构建打包过程中,移除那些引入但未被应用的有效代码开启
scope hosting
- 体积更小
- 创立函数作用域更小
- 代码可读性更好
ES6 提出了“模板语法”的概念。在 ES6 以前,拼接字符串是很麻烦的事件:
var name = 'css' var career = 'coder' var hobby = ['coding', 'writing']var finalString = 'my name is ' + name + ', I work as a ' + career + ', I love ' + hobby[0] + ' and ' + hobby[1]
仅仅几个变量,写了这么多加号,还要时刻小心外面的空格和标点符号有没有跟错中央。然而有了模板字符串,拼接难度直线降落:
var name = 'css' var career = 'coder' var hobby = ['coding', 'writing']var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`
字符串不仅更容易拼了,也更易读了,代码整体的品质都变高了。这就是模板字符串的第一个劣势——容许用${}的形式嵌入变量。但这还不是问题的要害,模板字符串的要害劣势有两个:
- 在模板字符串中,空格、缩进、换行都会被保留
- 模板字符串齐全反对“运算”式的表达式,能够在${}里实现一些计算
基于第一点,能够在模板字符串里无障碍地间接写 html 代码:
let list = ` <ul> <li>列表项1</li> <li>列表项2</li> </ul>`;console.log(message); // 正确输入,不存在报错
基于第二点,能够把一些简略的计算和调用丢进 ${} 来做:
function add(a, b) { const finalString = `${a} + ${b} = ${a+b}` console.log(finalString)}add(1, 2) // 输入 '1 + 2 = 3'
除了模板语法外, ES6中还新增了一系列的字符串办法用于晋升开发效率:
(1)存在性断定:在过来,当判断一个字符/字符串是否在某字符串中时,只能用 indexOf > -1 来做。当初 ES6 提供了三个办法:includes、startsWith、endsWith,它们都会返回一个布尔值来通知你是否存在。
- includes:判断字符串与子串的蕴含关系:
const son = 'haha' const father = 'xixi haha hehe'father.includes(son) // true
- startsWith:判断字符串是否以某个/某串字符结尾:
const father = 'xixi haha hehe'father.startsWith('haha') // falsefather.startsWith('xixi') // true
- endsWith:判断字符串是否以某个/某串字符结尾:
const father = 'xixi haha hehe' father.endsWith('hehe') // true
(2)主动反复:能够应用 repeat 办法来使同一个字符串输入屡次(被间断复制屡次):
const sourceCode = 'repeat for 3 times;'const repeated = sourceCode.repeat(3) console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;
有哪些可能引起前端平安的问题?
- 跨站脚本 (Cross-Site Scripting, XSS): ⼀种代码注⼊⽅式, 为了与 CSS 辨别所以被称作 XSS。晚期常⻅于⽹络论坛, 起因是⽹站没有对⽤户的输⼊进⾏严格的限度, 使得攻击者能够将脚本上传到帖⼦让其余⼈浏览到有歹意脚本的⻚⾯, 其注⼊⽅式很简略包含但不限于 JavaScript / CSS / Flash 等;
- iframe的滥⽤: iframe中的内容是由第三⽅来提供的,默认状况下他们不受管制,他们能够在iframe中运⾏JavaScirpt脚本、Flash插件、弹出对话框等等,这可能会毁坏前端⽤户体验;
- 跨站点申请伪造(Cross-Site Request Forgeries,CSRF): 指攻击者通过设置好的陷阱,强制对已实现认证的⽤户进⾏⾮预期的个⼈信息或设定信息等某些状态更新,属于被动攻打
- 歹意第三⽅库: ⽆论是后端服务器应⽤还是前端应⽤开发,绝⼤少数时候都是在借助开发框架和各种类库进⾏疾速开发,⼀旦第三⽅库被植⼊恶意代码很容易引起平安问题。
代码输入后果
async function async1 () { console.log('async1 start'); await new Promise(resolve => { console.log('promise1') }) console.log('async1 success'); return 'async1 end'}console.log('srcipt start')async1().then(res => console.log(res))console.log('srcipt end')
输入后果如下:
script startasync1 startpromise1script end
这里须要留神的是在async1
中await
前面的Promise是没有返回值的,也就是它的状态始终是pending
状态,所以在await
之后的内容是不会执行的,包含async1
前面的 .then
。
TCP的牢靠传输机制
TCP 的牢靠传输机制是基于间断 ARQ 协定和滑动窗口协定的。
TCP 协定在发送方维持了一个发送窗口,发送窗口以前的报文段是曾经发送并确认了的报文段,发送窗口中蕴含了曾经发送但 未确认的报文段和容许发送但还未发送的报文段,发送窗口当前的报文段是缓存中还不容许发送的报文段。当发送方向接管方发 送报文时,会顺次发送窗口内的所有报文段,并且设置一个定时器,这个定时器能够了解为是最早发送但未收到确认的报文段。 如果在定时器的工夫内收到某一个报文段的确认答复,则滑动窗口,将窗口的首部向后滑动到确认报文段的后一个地位,此时如 果还有已发送但没有确认的报文段,则从新设置定时器,如果没有了则敞开定时器。如果定时器超时,则从新发送所有曾经发送 但还未收到确认的报文段,并将超时的距离设置为以前的两倍。当发送方收到接管方的三个冗余的确认应答后,这是一种批示, 阐明该报文段当前的报文段很有可能产生失落了,那么发送方会启用疾速重传的机制,就是以后定时器完结前,发送所有的已发 送但确认的报文段。
接管方应用的是累计确认的机制,对于所有按序达到的报文段,接管方返回一个报文段的必定答复。如果收到了一个乱序的报文 段,那么接方会间接抛弃,并返回一个最近的按序达到的报文段的必定答复。应用累计确认保障了返回的确认号之前的报文段都 曾经按序达到了,所以发送窗口能够挪动到已确认报文段的前面。
发送窗口的大小是变动的,它是由接管窗口残余大小和网络中拥塞水平来决定的,TCP 就是通过管制发送窗口的长度来管制报文 段的发送速率。
然而 TCP 协定并不齐全和滑动窗口协定雷同,因为许多的 TCP 实现会将失序的报文段给缓存起来,并且产生重传时,只会重 传一个报文段,因而 TCP 协定的牢靠传输机制更像是窗口滑动协定和抉择重传协定的一个混合体。
如何实现浏览器内多个标签页之间的通信?
实现多个标签页之间的通信,实质上都是通过中介者模式来实现的。因为标签页之间没有方法间接通信,因而咱们能够找一个中介者,让标签页和中介者进行通信,而后让这个中介者来进行音讯的转发。通信办法如下:
- 应用 websocket 协定,因为 websocket 协定能够实现服务器推送,所以服务器就能够用来当做这个中介者。标签页通过向服务器发送数据,而后由服务器向其余标签页推送转发。
- 应用 ShareWorker 的形式,shareWorker 会在页面存在的生命周期内创立一个惟一的线程,并且开启多个页面也只会应用同一个线程。这个时候共享线程就能够充当中介者的角色。标签页间通过共享一个线程,而后通过这个共享的线程来实现数据的替换。
- 应用 localStorage 的形式,咱们能够在一个标签页对 localStorage 的变动事件进行监听,而后当另一个标签页批改数据的时候,咱们就能够通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色。
- 应用 postMessage 办法,如果咱们可能取得对应标签页的援用,就能够应用postMessage 办法,进行通信。
介绍一下 babel原理
babel
的编译过程分为三个阶段: parsing 、 transforming 、 generating ,以 ES6 编译为 ES5 作为例子:
ES6
代码输出;babylon
进行解析失去 AST;plugin
用babel-traverse
对AST
树进行遍历编译,失去新的AST
树;- 用
babel-generator
通过AST
树生成ES5
代码。
实现模板字符串解析性能
题目形容:
let template = '我是{{name}},年龄{{age}},性别{{sex}}';let data = { name: '姓名', age: 18}render(template, data); // 我是姓名,年龄18,性别undefined
实现代码如下:
function render(template, data) { let computed = template.replace(/\{\{(\w+)\}\}/g, function (match, key) { return data[key]; }); return computed;}
CSS3的新个性
transition
:过渡transform
: 旋转、缩放、挪动或歪斜animation
: 动画gradient
: 突变box-shadow
: 暗影border-radius
: 圆角word-break
:normal|break-all|keep-all
; 文字换行(默认规定|单词也能够换行|只在半角空格或连字符换行)text-overflow
: 文字超出局部解决text-shadow
: 程度暗影,垂直暗影,含糊的间隔,以及暗影的色彩。box-sizing
:content-box|border-box
盒模型- 媒体查问
@media screen and (max-width: 960px) {}
还有打印print
Cookie有哪些字段,作用别离是什么
Cookie由以下字段组成:
- Name:cookie的名称
- Value:cookie的值,对于认证cookie,value值包含web服务器所提供的拜访令牌;
- Size: cookie的大小
- Path:能够拜访此cookie的页面门路。 比方domain是abc.com,path是
/test
,那么只有/test
门路下的页面能够读取此cookie。 - Secure: 指定是否应用HTTPS平安协定发送Cookie。应用HTTPS平安协定,能够爱护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该办法也可用于Web站点的身份甄别,即在HTTPS的连贯建设阶段,浏览器会查看Web网站的SSL证书的有效性。然而基于兼容性的起因(比方有些网站应用自签订的证书)在检测到SSL证书有效时,浏览器并不会立刻终止用户的连贯申请,而是显示平安危险信息,用户仍能够抉择持续拜访该站点。
- Domain:能够拜访该cookie的域名,Cookie 机制并未遵循严格的同源策略,容许一个子域能够设置或获取其父域的 Cookie。当须要实现单点登录计划时,Cookie 的上述个性十分有用,然而也减少了 Cookie受攻打的危险,比方攻击者能够借此动员会话定置攻打。因此,浏览器禁止在 Domain 属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻打产生的范畴。
- HTTP: 该字段蕴含
HTTPOnly
属性 ,该属性用来设置cookie是否通过脚本来拜访,默认为空,即能够通过脚本拜访。在客户端是不能通过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能通过服务端来设置。该属性用于避免客户端脚本通过document.cookie
属性拜访Cookie,有助于爱护Cookie不被跨站脚本攻打窃取或篡改。然而,HTTPOnly的利用仍存在局限性,一些浏览器能够阻止客户端脚本对Cookie的读操作,但容许写操作;此外大多数浏览器仍容许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头。 - Expires/Max-size : 此cookie的超时工夫。若设置其值为一个工夫,那么当达到此工夫后,此cookie生效。不设置的话默认值是Session,意思是cookie会和session一起生效。当浏览器敞开(不是浏览器标签页,而是整个浏览器) 后,此cookie生效。
总结: 服务器端能够应用 Set-Cookie 的响应头部来配置 cookie 信息。一条cookie 包含了5个属性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 生效的工夫,domain 是域名、path是门路,domain 和 path 一起限度了 cookie 可能被哪些 url 拜访。secure 规定了 cookie 只能在确保安全的状况下传输,HttpOnly 规定了这个 cookie 只能被服务器拜访,不能应用 js 脚本拜访。
基于 Localstorage 设计一个 1M 的缓存零碎,须要实现缓存淘汰机制
设计思路如下:
- 存储的每个对象须要增加两个属性:别离是过期工夫和存储工夫。
- 利用一个属性保留零碎中目前所占空间大小,每次存储都减少该属性。当该属性值大于 1M 时,须要依照工夫排序零碎中的数据,删除一定量的数据保障可能存储下目前须要存储的数据。
- 每次取数据时,须要判断该缓存数据是否过期,如果过期就删除。
以下是代码实现,实现了思路,然而可能会存在 Bug,然而这种设计题个别是给出设计思路和局部代码,不会须要写出一个无问题的代码
class Store { constructor() { let store = localStorage.getItem('cache') if (!store) { store = { maxSize: 1024 * 1024, size: 0 } this.store = store } else { this.store = JSON.parse(store) } } set(key, value, expire) { this.store[key] = { date: Date.now(), expire, value } let size = this.sizeOf(JSON.stringify(this.store[key])) if (this.store.maxSize < size + this.store.size) { console.log('超了-----------'); var keys = Object.keys(this.store); // 工夫排序 keys = keys.sort((a, b) => { let item1 = this.store[a], item2 = this.store[b]; return item2.date - item1.date; }); while (size + this.store.size > this.store.maxSize) { let index = keys[keys.length - 1] this.store.size -= this.sizeOf(JSON.stringify(this.store[index])) delete this.store[index] } } this.store.size += size localStorage.setItem('cache', JSON.stringify(this.store)) } get(key) { let d = this.store[key] if (!d) { console.log('找不到该属性'); return } if (d.expire > Date.now) { console.log('过期删除'); delete this.store[key] localStorage.setItem('cache', JSON.stringify(this.store)) } else { return d.value } } sizeOf(str, charset) { var total = 0, charCode, i, len; charset = charset ? charset.toLowerCase() : ''; if (charset === 'utf-16' || charset === 'utf16') { for (i = 0, len = str.length; i < len; i++) { charCode = str.charCodeAt(i); if (charCode <= 0xffff) { total += 2; } else { total += 4; } } } else { for (i = 0, len = str.length; i < len; i++) { charCode = str.charCodeAt(i); if (charCode <= 0x007f) { total += 1; } else if (charCode <= 0x07ff) { total += 2; } else if (charCode <= 0xffff) { total += 3; } else { total += 4; } } } return total; }}
用过 TypeScript 吗?它的作用是什么?
为 JS 增加类型反对,以及提供最新版的 ES 语法的反对,是的利于团队合作和排错,开发大型项目
Nginx的概念及其工作原理
Nginx 是一款轻量级的 Web 服务器,也能够用于反向代理、负载平衡和 HTTP 缓存等。Nginx 应用异步事件驱动的办法来解决申请,是一款面向性能设计的 HTTP 服务器。
传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于event-driven模型的。正是这个次要的区别带给了 Nginx 在性能上的劣势。
Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其余的 worker process,这一点和Apache 十分像,然而 Nginx 的 worker process 能够同时解决大量的HTTP申请,而每个 Apache process 只能解决一个。
说一下JSON.stringify有什么毛病?
1.如果obj外面有工夫对象,则JSON.stringify后再JSON.parse的后果,工夫将只是字符串的模式,而不是对象的模式2.如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的后果将只失去空对象;3、如果obj里有函数,undefined,则序列化的后果会把函数或 undefined失落;4、如果obj里有NaN、Infinity和-Infinity,则序列化的后果会变成null5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则应用JSON.parse(JSON.stringify(obj))深拷贝后,会抛弃对象的constructor;6、如果对象中存在循环援用的状况也无奈正确实现深拷贝;
Vue路由守卫有哪些,怎么设置,应用场景等
罕用的两个路由守卫:router.beforeEach 和 router.afterEach每个守卫办法接管三个参数:to: Route: 行将要进入的指标 路由对象from: Route: 以后导航正要来到的路由next: Function: 肯定要调用该办法来 resolve 这个钩子。在我的项目中,个别在beforeEach这个钩子函数中进行路由跳转的一些信息判断。判断是否登录,是否拿到对应的路由权限等等。
为什么须要浏览器缓存?
对于浏览器的缓存,次要针对的是前端的动态资源,最好的成果就是,在发动申请之后,拉取相应的动态资源,并保留在本地。如果服务器的动态资源没有更新,那么在下次申请的时候,就间接从本地读取即可,如果服务器的动态资源曾经更新,那么咱们再次申请的时候,就到服务器拉取新的资源,并保留在本地。这样就大大的缩小了申请的次数,进步了网站的性能。这就要用到浏览器的缓存策略了。
所谓的浏览器缓存指的是浏览器将用户申请过的动态资源,存储到电脑本地磁盘中,当浏览器再次拜访时,就能够间接从本地加载,不须要再去服务端申请了。
应用浏览器缓存,有以下长处:
- 缩小了服务器的累赘,进步了网站的性能
- 放慢了客户端网页的加载速度
- 缩小了多余网络数据传输
原型链
原型链实际上在下面原型的问题中就有波及到,在原型的继承中,咱们继承来多个原型,这里再提一下实现完满继承的计划,通过借助寄生组合继承,PersonB.prototype = Object.create(PersonA.prototype)这是当咱们实例化PersonB失去实例化对象,拜访实例化对象的属性时会触发get办法,它会先在本身属性上查找,如果没有这个属性,就会去__proto__中查找,一层层向上直到查找到顶层对象Object,这个查找的过程就是原型链来。
如何高效操作DOM
1. 为什么说 DOM 操作耗时
1.1 线程切换
- 浏览器为了防止两个引擎同时批改页面而造成渲染后果不统一的状况,减少了另外一个机制,这
两个引擎具备互斥性
,也就是说在某个时刻只有一个引擎在运行,另一个引擎会被阻塞
。操作系统在进行线程切换的时候须要保留上一个线程执行时的状态信息并读取下一个线程的状态信息,俗称上下文切换。而这个操作相对而言是比拟耗时的 - 每次 DOM 操作就会引发线程的上下文切换——从 JavaScript 引擎切换到渲染引擎执行对应操作,而后再切换回 JavaScript 引擎继续执行,这就带来了性能损耗。单次切换耗费的工夫是非常少的,然而如果频繁地大量切换,那么就会产生性能问题
比方上面的测试代码,循环读取一百万次 DOM 中的 body 元素的耗时是读取 JSON 对象耗时的 10 倍。
// 测试次数:一百万次const times = 1000000// 缓存body元素console.time('object')let body = document.body// 循环赋值对象作为对照参考for(let i=0;i<times;i++) { let tmp = body}console.timeEnd('object')// object: 1.77197265625msconsole.time('dom')// 循环读取body元素引发线程切换for(let i=0;i<times;i++) { let tmp = document.body}console.timeEnd('dom')// dom: 18.302001953125ms
1.2 从新渲染
另一个更加耗时的因素是元素及款式变动引起的再次渲染,在渲染过程中最耗时的两个步骤为重排(Reflow)与重绘(Repaint)
。
浏览器在渲染页面时会将 HTML 和 CSS 别离解析成 DOM 树和 CSSOM 树,而后合并进行排布,再绘制成咱们可见的页面。如果在操作 DOM 时波及到元素、款式的批改,就会引起渲染引擎从新计算款式生成 CSSOM 树,同时还有可能触发对元素的从新排布和从新绘制
可能会影响到其余元素排布的操作就会引起重排,继而引发重绘
- 批改元素边距、大小
- 增加、删除元素
- 扭转窗口大小
引起重绘
- 设置背景图片
- 批改字体色彩
- 扭转
visibility
属性值
理解更多对于重绘和重排的款式属性,能够参看这个网址:https://csstriggers.com/ (opens new window)。
2. 如何高效操作 DOM
明确了 DOM 操作耗时之后,要晋升性能就变得很简略了,反其道而行之,缩小这些操作即可
2.1 在循环外操作元素
比方上面两段测试代码比照了读取 1000 次 JSON 对象以及拜访 1000 次 body 元素的耗时差别,相差一个数量级
const times = 10000;console.time('switch')for (let i = 0; i < times; i++) { document.body === 1 ? console.log(1) : void 0;}console.timeEnd('switch') // 1.873046875msvar body = JSON.stringify(document.body)console.time('batch')for (let i = 0; i < times; i++) { body === 1 ? console.log(1) : void 0;}console.timeEnd('batch') // 0.846923828125ms
2.2 批量操作元素
比如说要创立 1 万个 div 元素,在循环中间接创立再增加到父元素上耗时会十分多。如果采纳字符串拼接的模式,先将 1 万个 div 元素的 html 字符串拼接成一个残缺字符串,而后赋值给 body
元素的 innerHTML
属性就能够显著缩小耗时
const times = 10000;console.time('createElement')for (let i = 0; i < times; i++) { const div = document.createElement('div') document.body.appendChild(div)}console.timeEnd('createElement')// 54.964111328125msconsole.time('innerHTML')let html=''for (let i = 0; i < times; i++) { html+='<div></div>'}document.body.innerHTML += html // 31.919921875msconsole.timeEnd('innerHTML')
实现有并行限度的 Promise 调度器
题目形容:JS 实现一个带并发限度的异步调度器 Scheduler,保障同时运行的工作最多有两个
addTask(1000,"1"); addTask(500,"2"); addTask(300,"3"); addTask(400,"4"); 的输入程序是:2 3 1 4 整个的残缺执行流程:一开始1、2两个工作开始执行500ms时,2工作执行结束,输入2,工作3开始执行800ms时,3工作执行结束,输入3,工作4开始执行1000ms时,1工作执行结束,输入1,此时只剩下4工作在执行1200ms时,4工作执行结束,输入4
实现代码如下:
class Scheduler { constructor(limit) { this.queue = []; this.maxCount = limit; this.runCounts = 0; } add(time, order) { const promiseCreator = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log(order); resolve(); }, time); }); }; this.queue.push(promiseCreator); } taskStart() { for (let i = 0; i < this.maxCount; i++) { this.request(); } } request() { if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) { return; } this.runCounts++; this.queue .shift()() .then(() => { this.runCounts--; this.request(); }); }}const scheduler = new Scheduler(2);const addTask = (time, order) => { scheduler.add(time, order);};addTask(1000, "1");addTask(500, "2");addTask(300, "3");addTask(400, "4");scheduler.taskStart();
字符串模板
function render(template, data) { const reg = /\{\{(\w+)\}\}/; // 模板字符串正则 if (reg.test(template)) { // 判断模板里是否有模板字符串 const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段 template = template.replace(reg, data[name]); // 将第一个模板字符串渲染 return render(template, data); // 递归的渲染并返回渲染后的构造 } return template; // 如果模板没有模板字符串间接返回}
测试:
let template = '我是{{name}},年龄{{age}},性别{{sex}}';let person = { name: '布兰', age: 12}render(template, person); // 我是布兰,年龄12,性别undefined
src和href的区别
src和href都是用来援用内部的资源,它们的区别如下:
- src: 示意对资源的援用,它指向的内容会嵌入到以后标签所在的地位。src会将其指向的资源下载并应⽤到⽂档内,如申请js脚本。当浏览器解析到该元素时,会暂停其余资源的下载和解决,直到将该资源加载、编译、执⾏结束,所以⼀般js脚本会放在页面底部。
- href: 示意超文本援用,它指向一些网络资源,建设和以后元素或本文档的链接关系。当浏览器辨认到它他指向的⽂件时,就会并⾏下载资源,不会停⽌对以后⽂档的解决。 罕用在a、link等标签上。