一、异步及术语
- 并行: 指同一时刻内多任务同时进行;
- 并发: 指在同一时间段内,多任务同时进行着,但是某一时刻,只有某一任务执行;
- 堆: 内存中某一未被阻止的区域,通常存储对象(引用类型);
- 栈: 后进先出的顺序存储数据结构,通常存储函数参数和基本类型值变量(按值访问);
- 队列: 先进先出顺序存储数据结构。
- 消息队列: 也叫任务队列,存储待处理消息及对应的回调函数或事件处理程序;
- 执行栈: 也可以叫执行上下文栈,JavaScript执行栈,顾名思义,是由执行上下文组成,当函数调用时,创建并插入一个执行上下文,通常称为执行栈帧(frame),存储着函数参数和局部变量,当该函数执行结束时,弹出该执行栈帧;
PS:通常所说的并发连接数,是指浏览器向服务器发起请求,建立TCP连接,每秒钟服务器建立的总连接数,而假如,服务器处10ms能处理一个连接,那么其并发连接数就是100。
1.1 事件循环和Promise 和 setTimeout
- 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。
- 任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
- macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
- micro-task大概包括: process.nextTick, Promise, MutationObserver(html5新特性)
- 执行栈内执行上下文的同步任务按序执行,执行完即退栈,而当异步任务执行时,该异步任务进入等待状态(不入栈),同时通知线程:当触发该事件时(或该异步操作响应返回时),需向消息队列插入一个事件消息;
- 当事件触发或响应返回时,线程向消息队列插入该事件消息(包含事件及回调);
- 当栈内同步任务执行完毕后,线程从消息队列取出一个事件消息,其对应异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被丢弃,执行完任务后退栈;
- 当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息,首先去拉取micro-task(微任务),直到所有执行完毕,然后循环再次从macro-task(宏任务)开始,这样一直循环下去。
- 主线程不断重复上面的第4步。
setTimeout(function() {
console.log('timeout1');
new Promise(function(resolve) {
resolve();
}).then(function() {
console.log('timeout1_then')
})
})
new Promise((a) => {
a('ffff')
}).then(_ => {
console.log("promise outer")
})
// 输出顺序
promise outer
timeout1
timeout1_then
2 toString()检测数据类型
通过call调用toString()方法,可以用来区分对象数组或其他数据类型。如果直接调用toString()数组会直接转化成字符串
console.log(toString.call([])) // [object Array]
console.log(toString.call(undefined)) //[object Undefined]
console.log(toString.call(null)) // [object Null]
console.log(toString.call(this)) // [object Window]
console.log(toString.call(function () {})) // [object Function]
console.log({a: 3}.toString()) // [object Object]
console.log([1, 3].toString()) // 1,3
console.log(function () {}.toString()) // function () {}
3. sort方法强化
// 数字字符等正确排序
function s(a, b) {
if (a > b) return 1
return -1
}
// 分开浮点数和整数
function m(a, b) {
if (a > Math.floor(a)) return 1
if (b > Math.floor(b)) return -1
}
let str = ['a', 'c', 'A', 'b', 'C', 'B', 1, 3, 2, 9, 4, 3, 5].sort(s). // [1, 2, 3, 3, 4, 5, 9, "A", "B", "C", "a", "b", "c"]
let floor = [1, 4, 2, 4.22, 1.21, 2.44].sort(m). //1, 4, 2, 4.22, 1.21, 2.44]
console.log(str, floor)
3. this通过属性调用指向对象,函数调用指向window
var a = 2
function method() {
console.log(this.a)
}
var obj = {
a: 3,
d: method
}
var obj2 = {
a: 4
}
obj.d() // 3
// 作为对象属性调用时,this指向对象。
obj2.me = obj.d //
obj2.me() // 4
// 作为方法调用时this指向window,函数时引用类型,当执行赋值操作时,只是指针给到了fun变量,然后调用,通过变量直接调用,而不是通过对象的方法。
var fun = obj.d // 2
fun()
4. reflow和repaint
- repaint:重绘,在改变 DOM 元素的视觉效果时触发,即不涉及任何排版布局的问题时触发。
- 回流,在某一个 DOM 元素的位置发生改变后触发,而且它会重新计算所有元素的位置和在页面中的占有的面积,所以reflow的影响比repaint更大,因为它将会影响它所有的children、ancestors及siblings。所以影响是针对整个页面的,整个页面都需要重新渲染。
优化:
- 用display隐藏,然后进行相关操作,完成后展示。
- 用class代替行内样式,将 reflow和repaint的次数缩减为一次。
5. 模块机制
var myModule = (function aa () {
// 由于闭包的原因,不会被销毁,所以每个定义的心模块,都能添加到modules上。
//
var modules = {}
function define (name, deps, impl) {
//遍历执行各个依赖的相关函数。
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]]
}
// 将模块的api存储在一个根据名字来管理的模块列表
//
modules[name] = impl.apply(impl, deps)
}
function get (name) {
return modules[name]
}
// 将方法暴露出去,供调用
return {
define: define,
get: get
}
})()
// 使用
myModule.define('bar', [], function () {
function hello(arg) {
return 'hello ' + arg
}
return {
hello: hello
}
})
myModule.define('foo', ['bar'], function (bar) {
var world = 'world'
function dest() {
console.log(bar.hello(world))
}
return {
dest: dest
}
})
//
var bar = myModule.get('bar')
var foo = myModule.get('foo')
console.log(bar.hello('world'))
foo.dest();
6. HTML DOM 的加载流程
- 解析HTML结构
- 加载外部脚本和样式表文件
- 解析并执行脚本
- 构造HTML DOM模型
- 加载图片等外部文件
- 页面加载完毕
7. toFixed()
toFixed() 保留几位小数,四舍五入。该方法是不准确的。所以在计算价格时,一定要小心。但是处理33.00这种价格显示时(确保后两位是0,也就是价格不会出现角,分),则ok。
1.35.toFixed(1) // 1.4
1.335.toFixed(2)。// 1.33
解决方法:
function mineToFixed (num, s) {
if (Number.isInteger(num)) {
return num.toFixed(s)
}
var times = Math.pow(10, s);
// parseInt 会舍掉小数位,所以0.4 + 0.5 会被舍掉,这样就模拟了四舍五入。
var des = num * times + 0.5;
des = parseInt(des, 10) / times;
return des + ''
}
8. 整数判断方式
1、任何正整数都会被1整除,即余数是0。
function isInteger(obj) {
return typeof obj === 'number' && obj%1 === 0
}
2、整数取整后还是等于自己
function isInteger(obj) {
return Math.floor(obj) === obj
}
3、通过位运算判断
function isInteger(obj) {
return (obj | 0) === obj
}
9. 判断字节数
function indentifyByte(str) {
return str.charCodeAt(0).toString(16).length;
}
console.log(indentifyByte('?')) // 4
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
is32Bit("?") // true
问题:
1、 stringObject.charCodeAt(index) charCodeAt方法只能分别返回前两个字节和后两个字节的值。
var s = "?";
s.charCodeAt(0) // 55362
s.charCodeAt(1) // 57271
2、'?'.length // 2
使用length并不准确。
3、es6 codePointAt方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。但是字符串包含二个字节和四个字节时。输入的索引并不能按照理想的方式处理。a通过索引2才能访问到。
let s = '?a';
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271
s.codePointAt(2) // 97
用ES6 的for of处理,
let s = '?a';
for (let ch of s) {
console.log(ch.codePointAt(ch));
}