箭头函数不同于传统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);
输出结果如下:
3
7
4
1
2
5
Promise{<resolved>: 1}
代码的执行过程如下:
resolve(6)
不会再执行;console.log(p)
打印出Promise{<resolved>: 1}
;因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。
Event Loop 执行顺序如下所示:
事件是用户操作网页时发生的交互动作,比如 click/move, 事件除了用户触发的动作外,还可以是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,包含了该事件发生时的所有相关信息( event 的属性)以及可以对事件进行的操作( event 的方法)。
事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型:
实现:利用 XMLHttpRequest
// get
const 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();
});
}
// post
const 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 核心原理: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 start
async1 start
promise1
script 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(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,6
console.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 执行的操作是相反的。
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
解析:
<!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>
八股文我不想写了自己百度去
JavaScript中Number.MAX_SAFE_INTEGER表示最⼤安全数字,计算结果是9007199254740991,即在这个数范围内不会出现精度丢失(⼩数除外)。但是⼀旦超过这个范围,js就会出现计算不准确的情况,这在⼤数计算的时候不得不依靠⼀些第三⽅库进⾏解决,因此官⽅提出了BigInt来解决此问题。
优化前的准备工作
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 代码,将没有使用到的无用代码 "摇" 下来 (删除)。即 消除那些被 引用了但未被使用 的模块代码。
tree-shaking
webpack-deep-scope-plugin
,可以进行作用域分析,减少此类情况的发生,但仍需要注意;code-spliting: 代码分割技术 ,将代码分割成多份进行 懒加载 或 异步加载,避免打包成一份后导致体积过大,影响页面的首屏加载;
Webpack
中使用 SplitChunksPlugin
进行拆分;scope hoisting : 作用域提升,将分散的模块划分到同一个作用域中,避免了代码的重复引入,有效减少打包后的代码体积和运行时的内存损耗;
编译性能优化:
webpack
,能有效提升编译性能;dev-server
/ 模块热替换 (HMR
) 提升开发体验;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
对打包后的文件进行分析,寻找可优化的地方source-map
:cheap-module-eval-source-map
hidden-source-map
;优化webpack打包速度
loader
的 test
,include & exclude
Webpack4
默认压缩并行Happypack
并发调用babel
也可以缓存编译Resolve
在构建时指定查找模块文件的规则DllPlugin
,不用每次都重新构建externals
和 DllPlugin
解决的是同一类问题:将依赖的框架等模块从构建过程中移除。它们的区别在于externals
更简单,而 DllPlugin
需要独立的配置文件。DllPlugin
包含了依赖包的独立构建流程,而 externals
配置中不包含依赖框架的生成方式,通常使用已传入 CDN 的依赖包externals
配置的依赖包需要单独指定依赖模块的加载方式:全局对象、CommonJS、AMD 等DllPlugin
无须更改,而 externals
则会将子模块打入项目包中优化打包体积
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,它们都会返回一个布尔值来告诉你是否存在。
const son = 'haha'
const father = 'xixi haha hehe'
father.includes(son) // true
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
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;
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 start
async1 start
promise1
script end
这里需要注意的是在async1
中await
后面的Promise是没有返回值的,也就是它的状态始终是pending
状态,所以在await
之后的内容是不会执行的,包括async1
后面的 .then
。
TCP 的可靠传输机制是基于连续 ARQ 协议和滑动窗口协议的。
TCP 协议在发送方维持了一个发送窗口,发送窗口以前的报文段是已经发送并确认了的报文段,发送窗口中包含了已经发送但 未确认的报文段和允许发送但还未发送的报文段,发送窗口以后的报文段是缓存中还不允许发送的报文段。当发送方向接收方发 送报文时,会依次发送窗口内的所有报文段,并且设置一个定时器,这个定时器可以理解为是最早发送但未收到确认的报文段。 如果在定时器的时间内收到某一个报文段的确认回答,则滑动窗口,将窗口的首部向后滑动到确认报文段的后一个位置,此时如 果还有已发送但没有确认的报文段,则重新设置定时器,如果没有了则关闭定时器。如果定时器超时,则重新发送所有已经发送 但还未收到确认的报文段,并将超时的间隔设置为以前的两倍。当发送方收到接收方的三个冗余的确认应答后,这是一种指示, 说明该报文段以后的报文段很有可能发生丢失了,那么发送方会启用快速重传的机制,就是当前定时器结束前,发送所有的已发 送但确认的报文段。
接收方使用的是累计确认的机制,对于所有按序到达的报文段,接收方返回一个报文段的肯定回答。如果收到了一个乱序的报文 段,那么接方会直接丢弃,并返回一个最近的按序到达的报文段的肯定回答。使用累计确认保证了返回的确认号之前的报文段都 已经按序到达了,所以发送窗口可以移动到已确认报文段的后面。
发送窗口的大小是变化的,它是由接收窗口剩余大小和网络中拥塞程度来决定的,TCP 就是通过控制发送窗口的长度来控制报文 段的发送速率。
但是 TCP 协议并不完全和滑动窗口协议相同,因为许多的 TCP 实现会将失序的报文段给缓存起来,并且发生重传时,只会重 传一个报文段,因此 TCP 协议的可靠传输机制更像是窗口滑动协议和选择重传协议的一个混合体。
实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信,因此我们可以找一个中介者,让标签页和中介者进行通信,然后让这个中介者来进行消息的转发。通信方法如下:
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;
}
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由以下字段组成:
/test
,那么只有/test
路径下的页面可以读取此cookie。HTTPOnly
属性 ,该属性用来设置cookie能否通过脚本来访问,默认为空,即可以通过脚本访问。在客户端是不能通过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能通过服务端来设置。该属性用于防止客户端脚本通过document.cookie
属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是,HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头。总结: 服务器端可以使用 Set-Cookie 的响应头部来配置 cookie 信息。一条cookie 包括了5个属性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的时间,domain 是域名、path是路径,domain 和 path 一起限制了 cookie 能够被哪些 url 访问。secure 规定了 cookie 只能在确保安全的情况下传输,HttpOnly 规定了这个 cookie 只能被服务器访问,不能使用 js 脚本访问。
设计思路如下:
以下是代码实现,实现了思路,但是可能会存在 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;
}
}
为 JS 添加类型支持,以及提供最新版的 ES 语法的支持,是的利于团队协作和排错,开发大型项目
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 只能处理一个。
1.如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式,而不是对象的形式
2.如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的结果将只得到空对象;
3、如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;
6、如果对象中存在循环引用的情况也无法正确实现深拷贝;
常用的两个路由守卫:router.beforeEach 和 router.afterEach
每个守卫方法接收三个参数:
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。
在项目中,一般在beforeEach这个钩子函数中进行路由跳转的一些信息判断。
判断是否登录,是否拿到对应的路由权限等等。
对于浏览器的缓存,主要针对的是前端的静态资源,最好的效果就是,在发起请求之后,拉取相应的静态资源,并保存在本地。如果服务器的静态资源没有更新,那么在下次请求的时候,就直接从本地读取即可,如果服务器的静态资源已经更新,那么我们再次请求的时候,就到服务器拉取新的资源,并保存在本地。这样就大大的减少了请求的次数,提高了网站的性能。这就要用到浏览器的缓存策略了。
所谓的浏览器缓存指的是浏览器将用户请求过的静态资源,存储到电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载,不需要再去服务端请求了。
使用浏览器缓存,有以下优点:
原型链实际上在上面原型的问题中就有涉及到,在原型的继承中,我们继承来多个原型,这里再提一下实现完美
继承的方案,通过借助寄生组合继承,PersonB.prototype = Object.create(PersonA.prototype)
这是当我们实例化PersonB得到实例化对象,访问实例化对象的属性时会触发get方法,它会先在自身属性上查
找,如果没有这个属性,就会去__proto__中查找,一层层向上直到查找到顶层对象Object,这个查找的过程
就是原型链来。
1. 为什么说 DOM 操作耗时
1.1 线程切换
两个引擎具有互斥性
,也就是说在某个时刻只有一个引擎在运行,另一个引擎会被阻塞
。操作系统在进行线程切换的时候需要保存上一个线程执行时的状态信息并读取下一个线程的状态信息,俗称上下文切换。而这个操作相对而言是比较耗时的比如下面的测试代码,循环读取一百万次 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.77197265625ms
console.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.873046875ms
var 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.964111328125ms
console.time('innerHTML')
let html=''
for (let i = 0; i < times; i++) {
html+='<div></div>'
}
document.body.innerHTML += html // 31.919921875ms
console.timeEnd('innerHTML')
题目描述: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都是用来引用外部的资源,它们的区别如下:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。