# js 总结

# new 操作符

四步:

  1. 创空对象var obj=new object()
  2. 设置原型链obj._proto_ = fn.prototype
  3. fn 的 this 指向 obj ,执行 fn 函数体var result = fn.call(obj)
  4. 判断 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

  1. offsetWidth/offsetHeight 返回值包含 content + padding + border,效果与 e.getBoundingClientRect()相同
  2. clientWidth/clientHeight 返回值只包含 content + padding,如果有滚动条,也不包含滚动条
  3. 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)是指阻止一个函数在很短时间间隔内连续调用。 只有当上一次函数执行后达到规定的时间间隔,才能进行下一次调用。 但要保证一个累计最小调用间隔
应用场景:

  1. DOM 元素的拖拽功能实现(mousemove)
  2. 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  3. 计算鼠标移动的距离(mousemove)
  4. Canvas 模拟画板功能(mousemove)
  5. 搜索联想(keyup)
  6. 监听滚动事件判断是否到页面底部自动加载更多:给 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) 去抖就是对于一定时间段的连续的函数调用,只让其执行一次 思路:创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。 应用场景:

  1. 每次 resize/scroll 触发统计事件
  2. 文本输入的验证(连续输入文字后发送 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...)。只要栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些事件所对应的回调函数,"任务队列"是一个先进先出的数据结构

# 同步任务、异步任务、宏任务、微任务

  1. 栈(stack)中的元素采用 LIFO (Last In First Out),即后进先出。
  2. 队列(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 称为 jobsmacrotask 称为 task 宏任务与微任务执行的运行机制如下:
  1. 将"执行栈"最开始的所有同步代码(宏任务)执行
  2. 检查是否有微任务,如有则执行所有的微任务;
  3. 取出"任务队列"中事件所对应的回调函数(宏任务)进入"执行栈"并执行完成;
  4. 再检查是否有微任务,如有则执行所有的微任务;
  5. 主线程不断重复上面的 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

  1. DOM0 事件 btn.onclick=func;
  2. DOM2 事件
btn.addEventListener('click', func, false)
btn.removeEventListener('click', func, false)
btn.attachEvent('onclick', func)
btn.detachEvent('onclick', func)
  1. 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 无法跳出 详细 array

# 数组扁平化

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)
}