# js 总结
# new 操作符
四步:
- 创空对象
var obj=new object()
- 设置原型链
obj._proto_ = fn.prototype
- fn 的 this 指向 obj ,执行 fn 函数体
var result = fn.call(obj)
- 判断 fn 的返回值类型,如果是值类型,返回 obj。如果是引用类型,就返回这个引用类型的对象。
function create() {
// 创建一个空的对象
let obj = new Object()
// 获得构造函数
let Con = [].shift.call(arguments)
// 链接到原型
obj.__proto__ = Con.prototype
// 绑定 this,执行构造函数
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}
# js 对象
Object 是 JavaScript 中所有对象的父对象
数据封装类对象:Object、Array、Boolean、Number 和 String
其他对象:Function、Arguments、Math、Date、RegEx、Error
# width
- offsetWidth/offsetHeight 返回值包含 content + padding + border,效果与 e.getBoundingClientRect()相同
- clientWidth/clientHeight 返回值只包含 content + padding,如果有滚动条,也不包含滚动条
- scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸
# mouseover/out
- mouseover/mouseout 是标准事件,所有浏览器都支持;mouseenter/mouseleave 是 IE5.5 引入的特有事件后来被 DOM3 标准采纳,现代标准浏览器也支持
- mouseover/mouseout 是冒泡事件;mouseenter/mouseleave 不冒泡。需要为多个元素监听鼠标移入/出事件时,推荐 mouseover/mouseout 托管,提高性能
- 标准事件模型中 event.target 表示发生移入/出的元素,vent.relatedTarget 对应移出/如元素;在老 IE 中 event.srcElement 表示发生移入/出的元素,event.toElement 表示移出的目标元素,event.fromElement 表示移入时的来源元素
# 函数节流
函数节流(throttle)是指阻止一个函数在很短时间间隔内连续调用。 只有当上一次函数执行后达到规定的时间间隔,才能进行下一次调用。 但要保证一个累计最小调用间隔
应用场景:
- DOM 元素的拖拽功能实现(mousemove)
- 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
- 计算鼠标移动的距离(mousemove)
- Canvas 模拟画板功能(mousemove)
- 搜索联想(keyup)
- 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次
# 简单实现:
//定时器
function throttle(method, context) {
clearTimeout(method.tId);
method.tId = setTimeout(function(){
method.call(context);
}, 100); // 两次调用至少间隔 100ms
}
//另一种
function throttlePro(delay, action) {
let tId;
return function () {
let context = this;
let arg = arguments;
if (tId) return;
tId = setTimeout(function () {
action.apply(context, arg);
clearTimeout(tId);
// setTimeout 返回一个整数,clearTimeout 之后,tId还是那个整数,setInterval同样如此
tId = null;
}, delay);
}
}
// 调用
window.onresize = function(){
throttle(myFunc, window);
}
//时间戳
function throttle(delay, action) {
let last = 0;
return function() {
let curr = new Date();
if (curr - last > delay) {
action.apply(this, arguments);
last = curr;
}
}
}
# 函数去抖
去抖 (debounce) 去抖就是对于一定时间段的连续的函数调用,只让其执行一次 思路:创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。 应用场景:
- 每次 resize/scroll 触发统计事件
- 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)
实现:
const debounce = (fn, wait = 500) => {
return function() {
clearTimeout(fn.timer)
fn.timer = setTimeout(fn.bind(this, ...arguments), wait)
}
}
//another
function debounce(method, context) {
clearTimeout(method.tId)
method.tId = setTimeout(function() {
method.call(context)
}, 1000)
}
function print() {
console.log('hello world')
}
window.onscroll = function() {
//使用
debounce(print)
}
改动:
function debounce(delay, action) {
let tId
return function() {
let context = this
let arg = arguments
if (tId) clearTimeout(tId)
tId = setTimeout(function() {
action.apply(context, arg)
}, delay)
}
}
window.onscroll = debounce(1000, print)
# JavaScript Event Loop
# JavaScript 是单线程
进程和线程的概念和关系:
- 进程: 系统资源分配一个独立单位,一个程序至少有一个进程,运行的程序就是一个进程,任一时刻,只能有一个进程在运行
- 线程: CPU 调度和分派的基本单位,程序中独立运行的代码段,一个进程 由单个或多个 线程 组成,线程是负责执行代码的
单线程和多线程:
- 单线程 : 从头执行到尾,一行一行执行,如果其中一行代码报错,那么剩下代码将不再执行。容易代码阻塞。
- 多线程 : 代码运行的环境不同,各线程独立,互不影响,避免阻塞 为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM,并没有改变 JavaScript 单线程的本质。
主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部 API,它们在"任务队列"中加入各种事件(DOM Event,ajax,setTimeout...)。只要栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些事件所对应的回调函数,"任务队列"是一个先进先出的数据结构
# 同步任务、异步任务、宏任务、微任务
- 栈(stack)中的元素采用 LIFO (Last In First Out),即后进先出。
- 队列(queue)采用先进先出,即 FIFO(First in First Out)。
JavaScript 所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
可以将任务进行更精细的定义,分为宏任务与微任务:
- 宏任务(macro-task):
script,setTimeout,setInterval,ajax,dom,I/O ,UI rendering
操作 - 微任务(micro-task):
Promise,process.nextTick,Object.observe,MutationObserver
ES6 规范中,microtask
称为jobs
,macrotask
称为task
宏任务与微任务执行的运行机制如下:
- 将"执行栈"最开始的所有同步代码(宏任务)执行
- 检查是否有微任务,如有则执行所有的微任务;
- 取出"任务队列"中事件所对应的回调函数(宏任务)进入"执行栈"并执行完成;
- 再检查是否有微任务,如有则执行所有的微任务;
- 主线程不断重复上面的 3 4 步。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为 Event Loop(事件循环)。
# task 分析
setTimeout()、setInterval()
产生的任务是 异步任务,也属于 宏任务,第二个参数设置为 0 或者不设置,会指定一个具体的毫秒数(node 为 1ms,浏览器为 4ms),意思 并不是立即执行,而是指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。promise
new Promise()
中传入的回调函数是会 立即执行 的,但是它的 then() 方法是在 执行栈之后,任务队列之前 执行的,它属于 微任务process.nextTick
Node.js 提供的一个与"任务队列"有关的方法,它产生的任务是放在 执行栈的尾部,并不属于 宏任务 和 微任务,因此它的任务 总是发生在所有异步任务之前setImmediate
是 Node.js 提供的另一个与"任务队列"有关的方法,它产生的任务追加到"任务队列"的尾部,它和 setTimeout(fn, 0) 很像,但优先级都是 setTimeout 优先于 setImmediate。
# web worker
HTML5 新标准加入了 web worker 的多线程技术,但是 web worker 只能用于计算,并且 JS 的多线程 worker 无法操作 DOM
主线程传给子线程的数据是通过拷贝复制,同样子线程传给主线程的数据也是通过拷贝复制,而不是共享同一个内存空间。
以上说明,JS 不存在线程同步,所以还是可以把 JS 看做单线程模型,把 web worker 当做 JS 的一种回调机制。
# 总结
代码执行的优先级: 同步代码(宏任务) > process.nextTick > Promise(微任务)> setTimeout(fn)、setInterval(fn)(宏任务)> setImmediate(宏任务)> setTimeout(fn, time)、setInterval(fn, time),其中 time>0
# DOM
- DOM0 事件 btn.onclick=func;
- DOM2 事件
btn.addEventListener('click', func, false)
btn.removeEventListener('click', func, false)
btn.attachEvent('onclick', func)
btn.detachEvent('onclick', func)
- DOM3 事件
eventUtil.addListener(input, 'textInput', func)
// eventUtil 是自定义对象,textInput 是DOM3级事件
# 字符串转数字
parseInt(num) // 默认方式 (没有基数)
parseInt(num, 10) // 传入基数 (十位数)
parseFloat(num) // 浮点数
Number(num) // Number 构造器
~~num //按位非
num / 1 // 除一个数
num * 1 // 乘一个数
num -
0 + // 减去0
num // 一元运算符 "+"
# 千分位
function numberWithCommas(x = 0) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
//最简单的
const num = 1244545334
num.toLocaleString() // "1,244,545,334"
# 原型
- 每个函数都有 prototype 属性,除了 Function.prototype.bind(),该属性指向原型
- 每个对象都有
__proto__
属性,指向了创建该对象的构造函数的原型 - 对象可以通过
__proto__
来寻找不属于该对象的属性,__proto__
将对象连接起来组成了原型链
# 0.1 + 0.2 != 0.3
JS 采用 IEEE 754 双精度版本(64 位),并且只要采用 IEEE 754 的语言都有该问题,解决方法
parseFloat((0.1 + 0.2).toFixed(10))
# 自执行函数
!(function() {
console.log('IIFE')
})()
!!(function() {
//function codes here
})()
;-(function() {
//code
})()
~(function() {
//code
})()
;(function() {
//code
})()
;(function() {
//code
})()
void (function() {
//code
})()
//用了运算符优先级的这一特性,因为这几个运算符在JavaScript中优先级最高,会将紧紧相邻的js语句计算出结果,
//在计算的过程中,就会执行定义的匿名函数,就达到了自执行的效果
//感叹号在js中有两个作用,1.取反2.将值转换为布尔值:true或false。
# array 方法总结
for while for of
可以 break, 退出循环 forEach map
无法跳出
详细
# 数组扁平化
let arr = [1, [2, [3, [4, 5]]], 6] // -> [1, 2, 3, 4, 5, 6]
let str = JSON.stringify(ary)
//1
arr.flat(Infinity)
//2
ary = str.replace(/(\[|\])/g, '').split(',')
//3
str = str.replace(/(\[|\]))/g, '')
str = '[' + str + ']'
ary = JSON.parse(str)
//4
function flatten(ary) {
return ary.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur)
}, [])
}
//5
while (ary.some(Array.isArray())) {
ary = [].concat(...ary)
}
常见面试问题 →