JS基础总结

215 阅读26分钟

js数据类型

原始值类型(俗称:值类型,基本数据类型)

  1. number
  2. string
  3. boolean
  4. null
  5. undefined
  6. symbol
    • 给对象设置唯一值属性(对象属性名的类型:字符串,symbol)
    • Symbol.hasInstance/toStringTag/toPrimitive
  7. bigint
    • Number.MAX_SAFE_INTEGER/MIN_SAGE_INTEGER JS中的最大/最小安全数字
    • 数字后面加n就是bigint类型中,例如90071992547400991n,bigint值保证我们超过安全数字,计算也可以准确
    • 服务器返回超大数字,我们可以把其转换为bigint再进行运算;运算完结果,变成字符串传递给服务器即可。。 对象类型(引用数据类型)
  8. 标准普通对象 {name:'zanlan'}
  9. 标准特殊对象 数组、正则、日期、错误。。。
  10. 非标准特殊对象 原始值类型的值,基于构造函数模式,new出来的实例对象
  11. 可调用/执行对象(函数对象) function
let sy = Symbol("BB"),a={}
let obj = {
    [Symbol('AA')]:100,
    [sy]:200
}
obj[a] = 'zanlan';// obj["[object object]"] = "zanlan"
console.log(obj[Symbol("AA")]);//undefined
console.log(obj[sy]);//200
console.log(object.getOwnPropertySymbols(obj))// [Symbol(AA),Symbol(BB)]获取当前对象所有Symbol类型的私有属性

大数字测试

console.log(13452784567892345672)//13452784567892345000
console.log(13452784567892345672+123) // 13452784567892345000
console.log(BigInt(13452784567892345672)) // 13452784567892344832n
console.log(BigInt(13452784567892345672)+123n)//13452784567892344955n
console.log((13452784567892344955n).toString())//'13452784567892344955'

验证某浏览器是否兼容一个方法

js数据类型检测

var arr = [
    2020,//number
    'zanlan',//string
    true,//boolean
    null,//object
    undefined,//undefined
    Symbol('123'),//symbol
    BigInt(13452784567892345672),//bigint
    {},//object
    [],//object
    /[1]{1,}/,//object
    new Date(),//object
    new Number(1),//object
    new String(1),//object
]
arr.forEach(item => {
    console.log(typeof item);//打印结果如上
})
  1. typeof
    • 检测的结果是一个字符串,字符串中包含了对应的数据类型,但是也有局限性
    • typeof null -> "object"
    • typeof 检测对象类型,除函数对象返回"function",其余返回都是"object"不能细分对象
    • typeof检测一个未被声明的变量,不会报错,而是返回 "undefined"
    • 底层原理机制是,所有的数据类型值,在计算机底层都是按照"二进制"来存储的(64位),typeof检测数据类型,就是按照存储的二进制值来进行检测的,前三位是000的,都被认为是对象,如果对象内部实现了[[call]]方法,则认为是函数,返回 "function" ,否则返回"object",typeof的处理性能相对较好
    typeof检测特点
    • 按照计算机底层存储的二进制进行检测,效率高
    • 计算机科学:计算机原理,进制转换,计算机网络,数据结构和算法。。。
    • 000对象
    • 1整数
    • 010浮点数
    • 100字符串
    • 110布尔值
    • 000000... null
    • -2^30 undefined
  2. instanceof
  3. constructor
  4. Object.prototype.toString.call()
/**
    toString.call([val])原理
    先检测[val][Symbol.toStringTag]属性,如果有这个属性,属性值是啥 例如"Fn",最后检测的结果就是"[object Fn]",也可以在val的原型链中找[Symbol.toStringTag];如果没有这个属性,则按照自己所属的内置类进行处理
*/
let toString = Object.prototype.toString;
class Fn{
    constructor(){
        this.name = 'zanlan';
        this[Symbol.toStringTag] = 'Fn'
    }
}
let f = new Fn();
console.log(toString.call(f)) // "[object Fn]"

toString.call(new Number(1)) // "[object Number]"  很完美
typeof new Number(1) // "object"
toString.call(Math) // "[object Math]"  因为Math["Symbol(Symbol.toStringTag)"]是Math
toString.call(function *(){})// "[object GenerationFunction]" 因为 函数.__proto__["Symbol(Symbol.toStringTag)"] 为 "GenerationFunction"

v8 引擎中的堆栈内存

EC(Execution Context) 执行上下文:区分代码执行的环境

  • 常见上下文分类:
    • 全局上下文 EC(G)
    • 函数私有上下文EC(?)
    • 块级私有上下文EC(BLOCK)
  • 产生私有上下文 -> 进栈执行 -> 出栈释放(可能释放)
  • 变量对象:当前上下文中,用于存储声明的变量的地方
    • VO(Varible Object):VO(G) 或者 VO(BOLCK)
    • AO(Active Object):AO(?) GO(Global Object)全局对象
  • window指向GO对象
  • 全局上下文中,基于var/function声明的变量是直接存储到GO对象上的;而基于let/const声明的变量才是存放在VO(G)中的

let 变量 = 值得操作步骤 第一步:创建值

  • 原始值类型:直接存储在栈内存中,按值操作
  • 对象类型值:按照堆内存地址来操作
    • 对象:开辟一个堆内存空间(16进制),依次存储对象的键值对,把空间地址存储给变量
    • 函数:内存空间中存储三部分信息
      1. 作用域:当前所处山下文
      2. 函数体中的代码字符串
      3. 当做普通对象存储的静态属性和方法
  • 第二步:声明变量 declare
  • 第三部:变量和值关联一起 defined

例子

debugger;// 打断点 一步步调试过程
var x = 12
let y = 13
z = 14

解释:

/**
 * EC(G)全局执行上下文 
 *    VO(G)全局变量对象,基于let / const 声明的变量
 *      y -> 13
 *    window -> GO全局对象,基于var/function 声明的变量
 *      x:12
 *      z:14
*/
var x = 12;
let y = 13;
z = 14;//window.z = 14 直接设置在GO中,相当于省略了'window'

console.log(x);//12 首先看VO(G)是否存在,如果不存在再去看GO中是否存在,如果都没有则报错:x is not defined
console.log(window.x);//12 直接到GO中找到这个属性,如果不存在这个属性,值是undefined
console.log(y);//13
console.log(window.y);//undefined
console.log(z);//14
console.log(window.z);//14

image.png

例子:

let x = [12, 23]
function fn(y) {
    y[0] = 100;
    y = [100]
    y[1] = 200
    console.log(y);
}
fn(x) // [100, 200]
console.log(x);// [100, 23]

解释:

image.png

例子:

var a = {
    n:1
}
var b = a;
a.x = a = {
    n:2
}
console.log(a.x)
console.log(b)

解释:

image.png

运算符优先级 - JavaScript | MDN (mozilla.org)

例子:

console.log(fn);// 函数2
function fn() {console.log(1);}
console.log(fn);// 函数2
var fn = 12
console.log(fn);// 12
function fn() {console.log(2);}
console.log(fn);// 12

解释:

image.png

例子:

// 变量提升 var a; window.a
console.log(a)// undefined
if(!('a' in window)){// 'a' in window 为 true
    var a = 13//条件不成立  内部不执行
}
console.log(a) // undefined

前端必备数据结构

栈结构 十进制转换为二进制

class Stack {
    container = []
    enter(element) {
        this.container.unshift(element)
    }
    leave() {
        return this.container.shift()
    }
    size() {
        return this.container.length
    }
    value() {
        return this.container.slice(0)
    }
}
Number.prototype.decimal2binary = function decimal2binary() {
    let decimal = +this,
        sk = new Stack()
    if (decimal === 0) return '0'
    while (decimal > 0) {
        sk.enter(decimal % 2);
        decimal = Math.floor(decimal / 2)
    }
    return sk.value().join('')
}
console.log((10).toString(2));//"1010"
console.log((10).decimal2binary());//"1010"

队列 击鼓传花

n个人一起玩游戏,围成一圈,从1开始数数,数到m的人自动淘汰,最后剩余的人会胜利,问最后剩余的是原来的哪一位???

class Queue {
    container = []
    enter(element) {
        this.container.push(element)
    }
    leave() {
        return this.container.shift()
    }
    size() {
        return this.container.length
    }
    value() {
        return this.container.slice(0)
    }
}

const game = function game(n, m) {
    let qe = new Queue, i = 1;
    for (; i <= n; i++) qe.enter(i)
    while (qe.size() > 1) {
        i = 1;
        for (; i <= m - 1; i++) qe.enter(qe.leave())
        qe.leave()
    }
    return qe.value()[0]
}
console.log(game(8, 5));
console.log(game(3, 2));

探索js中的数据类型转换

Number([val])

  • 一般用于浏览器的隐式转换中
    1. 数字运算
    2. isNaN检测
    3. == 比较
  • 规则
    1. 字符串转换为数字:空字符串变为0,如果出现任何非有效数字字符,结果都是NaN
    2. 把布尔换为数字:true -> 1 false->0
    3. null -> 0 undefined -> NaN
    4. Symbol无法转换为数字,会报错:Uncaught TypeError:cannot convert a Symbol value to a number
    5. BigInt 去除"n" (超过安全数字的,会按照科学计数法处理)
    6. 把对象转换为数字
      • 先调用对象的Symbol.toPrimitive 这个方法,如果不存在这个方法
      • 再调用对象的valueof 获取原始值,如果获取的值不是原始值
      • 再调用对象的toString 把其变成字符串
      • 最后再把字符串基于Number方法转换为数字

parseInt([val],[radix]) parseFloat([val])

  • 一般用于手动转换
  • 规则:[val]值必须是一个字符串,如果不是则先转换为字符串;然后从字符串左侧第一个字符开始找,把找到的有效数字字符转转为数字,如果一个没有找到则返回NaN;遇到一个非有效数字字符,不论后面是否还有有效数字字符,都不再查找了;parseFloat可以多识别一个小数点;
  • parseInt([val],[radix])方法支持第二个参数(进制radix),从左侧(val)字符串中,查找出符合(radix)进制的字符,把找到的字符看做(radix)进制,最后转换为10进制
    1. radix 不设置或者设置为0,默认值是10(特殊:如果左侧字符串是以"0x"开始的,默认值是16)
    2. 取值范围:2~36,如果不在这个范围内(排除0),则结果一定是NaN
    3. 其他进制转换为10进制:按权展开求和
  • '1001'以二进制身份转换为十进制
    • 1*2^3 + 0*2^2 + 0*2^1 + 1*2^0 -> 8 + 0 + 0 + 1 -> 9
  • '0.12'以三进制身份转换为十进制
    • 0*3^0 + 1*3^-1 + 2*3^-2 -> 0 + 0.3333... + 0.1111... -> 0.55555....
let arr = [27.2, 0, '0013', '14px', 123]
arr = arr.map(parseInt)
console.log(arr);// [27, NaN, 1, 1, 27]

解释如下:

  • parseInt(27.2,0)// parseInt('27.2',10) -> '27' -> 27
  • parseInt(0,1) // NaN
  • parseInt('0013',2) // '001'符合二进制 -> 0*2^2 + 0*2^1 + 1*2^0 -> 1
  • parseInt('14px',3) // '1'符合三进制 -> 1*3^0 -> 1
  • parseInt('123',4) // parseInt('123',4) '123'符合四进制 -> 1*4^2 + 2*4*1 + 3*4^0 -> 27
  • 新数组:[27,NaN,1,1,27]

特殊情况:

parseInt(0023) // 19   因为00开头的看做是8进制数
//0b 00 0x

String([val]) 或者 [val].toString() 第一种办法:需要经历Symbol.toPrimitive -> valueOf -> toString 这样的处理步骤 第二种方法:直接调用[val] 所属类原型上的toString,直接把它转换为字符串了(普通对象是检测数据类型)

  1. "+" 除了数学运算,还可能代表的字符串拼接
  2. "+" 有两边(左右两边都有值),一边是字符串,则一定是字符串拼接
  3. "+" 有两边,其中一边是对象,这样可能会出现字符串拼接
    1. 1+{}
      • 检测对象的Symbol.toPrimitive方法,如果有,则执行这个方法,传递hint->'default'
      • 没有上述的办法,再基于valueOf获取原始值
      • 如果获取的不是原始值,则基于toString把其转换为字符串
      • 此时'+'遇到了字符串,则变为字符串拼接 "1[object Object]"
    2. 1 + new Date()
      • 1+new Date()[Symbol.toPrimitive]('default')
      • => "1Sun Jun 06 2022"
    3. 1+new Number(10)
      • 1+new Number(10).valueOf() 基于valueOf获取的是原始值
      • => 11
  4. "+" 只出现在左边,他实现的是把一个值转换为数字
var arr = [
    1,//'1'
    NaN,//'NaN'
    true,//"true"
    null,//"null"
    undefined,//"undefined"
    Symbol(),//"Symbol()"
    10n,//"10"
    [10, 20],//"10,20"
    /^\d+$/,//'/^\\d+$/'  转移了一下
    function(a){},// "function(a){}"
    {},//"[object Object]"
    { name: 'zanlan' }//"[object Object]"
]
arr.forEach(item => {
    console.log(String(item));
})
 var result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false
 console.log(result); //"NaNTencentnull9false"
 
 let obj = {
     [Symbol.toPrimitive](hint){
         console.log(hint);// 'default'
         return 0;
     }
 }
 console.log(1+obj) ;// 1
 
 {}+1 ; //1
 console.log({}+1); // "[object Object]"

特殊 {}+1,前提是没有小括号把它们包起来,或者在对象的左边没有其他的操作(例如:声明变量接受值等),此时浏览器是把当前操作分解为两个部分"{} 代码" 和 "+1 数组运算",两部分之间没有关系,所以结果是1

引用类型转换

当我们把对象影式转换为数字或者字符串的时候[使用的方法:Number/String],会有一套自己的处理逻辑:

  1. 检测Symbol.toPrimitive,如果有这个方法,浏览器会把方法执行,传递hint(根据场景不一样,浏览器默认传递的值也不一样 'number' / 'string' / 'default');如果没有这个方法,则进行下一步;
  2. 基于valueOf获取原始值,如果获取的不是原始值,则运行下一步;
  3. 基于toString获取字符串
  4. 如果需要转的是数字,则再次把字符串转为数字即可 但是如果直接对象.toString,相当于直接调用第三个步骤(直接调用所属类原型上的toString方法),此时直接获取字符串,不会走这四步逻辑的
let obj = {
    name: 'zanlan'
}
console.log(Number(obj)); // NaN
/**
 * obj[Symbol.toPrimitive] -> undefined
 * obj.valueof() -> {name:...}
 * obj.toString() -> '[object Object]'
 * Number('[object Object]') -> NaN
*/
let arr1 = [10], arr2 = [10, 20]
console.log(Number(arr1));//10 
console.log(Number(arr2));//NaN
// 都没有Symbol.toPrimitive -> valueOf获取的都不是原始值 -> toString的结果:"10" / "10,20" -> 10 / NaN

let time = new Date()
console.log(Number(time));
/**
 * time[Symbol.toPrimitive] 的值是个函数,说明日期对象有一个属性
 * 接下来把这个属性执行 time[Symbol.toPrimitive]('number') 执行方法传递值hint:'number' / 'string'/'default'
 * 1622813605545
*/

let time = new Date()
// 日期对象不允许我们重写Symbol.toPrimitive
time[Symbol.toPrimitive] = function (hint) {
    console.log(hint);
    return "@zanlan"
}
console.log(Number(time));// 1622813605545

转换为Boolean

  • 出现情况:Boolean([val]) 或者 !/!!、条件判断
  • 转换规则:除了"0/NaN/空字符串/null/undefined"五个值是false,其余都是true

"==" 比较时候的相互转换规则

  • "=="相等,两边数据类型不同,需要先转为相同类型,然后再进行比较
    • null == undefined -> true null或者undefined 和其他任何值都不相等
    • null === undefined -> false
    • 对象 == 对象 比较的是堆内存地址,地址相同则相等
    • NaN !== NaN -> true
    • NaN == NaN -> false
    • 除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后再进行比较的
  • "===" 绝对相等,如果两边类型不同,则直接返回false,不会转化为数据类型
console.log([] == false)
// 都要转换为数字 
// Number([]) -> 0
// false -> 0
// => true
console.log(![] == false)
// ![] 取反 -> !true -> false
// false == false
// => true

综合练习题 (a == 1 && a == 2 && a == 3)不可以为true?

var a = ?;
if(a == 1 && a == 2 && a == 3){
    console.log('ok')
}

例子

isNaN(parseInt(new Date()) + Number([1]) + typeof undefined)
// parseInt(new Date()) -> NaN
// isNaN(NaN) -> true
// Number([1]) -> 1
// typeof undefined -> 'undefined'
// true + 1 + 'undefined' -> '2undefined'

例子

parseFloat("1.6px")+parseInt("1.2px")+typeof parseInt(null)
// parseInt('null') -> NaN
// typeof NaN -> 'number'
// 1.6 + 1 + 'number' => '2.6number'

例子

Boolean(Number(""))+!isNaN(Number(null))+Boolean("parseInt([])") + typeof !null;
// false + true + true + 'boolean' => '2boolean'

例子

isNaN(Number(!!Number(parseInt("0.8"))))
// parseInt("0.8") -> 0
// !!0 -> false
// Number(false) -> 0
// isNaN(0) -> false

例子

!typeof parseFloat("0")
// parseFloat('0') -> 0
// typeof 0 -> 'number'
// !'number' -> false

例子

Number("") // => 0
// 16 + {} // => '16[object Object]'
// {} + 16 // => 16

例子

var n = 6;
console.log((n++) + n-- + 5 + --n + --n)
console.log(n)
/**
 * n++ // 6  n=7
 * n-- // 7  n=6
 * 5   // 5  n=6
 * --n // 5  n=5
 * --n // 4  n=4
 * 6+7+5+5+4 = 27
*/

例子

var str = 'abc123'
var num = parseInt(str)
if (num == NaN) {
    alert(NaN)
} else if (num == 123) {
    alert(123)
} else if (typeof num == 'number') {
    // 成立 输出 'number'
    alert('number')
} else {
    alert('str')
}

例子

//isNaN(NaN) -> true
//true == '' -> 1==0
// => '222'
if(isNaN(NaN) == ''){
    console.log('111')
}else{
    console.log('222')
}

js之 0.1 + 0.2 !== 0.3

进制转换存储

  1. 十进制转换为二进制的计算n.toString(2)
    • 整数部分:除以2取余数 ,直到商为0结束
    • 小数部分:乘以2取整数部分,余下的继续乘以2.。直到整数为1,没有小数部分了(特殊:很多浮点数转换为二进制,结果都是无限循环的,但是计算机底层最多只能存储64位,超出部分裁掉 => 我们存储到计算机底层的浮点数,对应的二进制值,本身可能就是不准确的) 浏览器中的十进制值,是有长度限制的[一般是16-17位],所以0.1 + 0.2计算机底层处理完的结果0.300000000000000040000121..,浏览器在截取的时候,把超过的部分裁掉;裁掉之后,如果后面都是0则省略掉,但凡有一个不是0,则不会省略,所以0.1+0.2 = 0.30000000000000004
  2. js使用Number类型表达数字(整数和浮点数),遵循IEEE-754标准,通过64为二进制值来表示一个数字
    • 第0位:符号位,0表示正数,1表示负数 S
    • 第1位 到 第11位:存储指数部分 E
    • 第12位到第63位:存储效数部分 F
  3. 最大安全数字16位,Number.MAX_SAFE_INTEGER === Math.pow(2,53) - 1
  4. 怎么解决精度问题
    • 将数字转换为整数
    • 第三方库:Math.js、decimal.js、big.js
获取系数
const coefficient = function coefficient(num){
    num = num + '';
    let [,char = ''] = num.split('.'),
    len = char.length;
    return Math.pow(10,len)
}
求和操作
const plus = function plus(num1,num2){
    num1 = +num1;
    num2 = +num2;
    if(isNaN(num1) || isNaN(num2)) return NaN;
    let coeffic = Math.max(coefficient(num1),coefficient(num2));
    return (num1 * coeffic + num2 * coeffic) / coeffic
}

块级私有上下文

除了"函数和对象"的大括号外(例如:判断体、循环体、代码块。。。),如果在大括号中出现了let/const/function/class 等关键词声明变量,则当前大括号会产生一个块级私有上下文,它的上级上下文是所处的环境;var不产生,也不受块级上下文的影响;

  • 函数是个渣男
  • 循环中的块级上下文

假设忽略报错阻塞代码执行

console.log(a);//undefined
console.log(b);//报错 
var a = 12
let b = 13
if (1 == 1) {
    console.log(a);//12   var出来的a不会受到块级作用域影响,这里用的是全局的
    console.log(b);//报错   let声明的b变量,会影响b的求值,如果在声明前使用,直接报错
    var a = 100
    let b = 200
    console.log(a);// 100
    console.log(b); // 200
}
console.log(a);// 100
console.log(b);// 200

例子:

console.log(a);//undefined 全局只是声明
if (true) {
    console.log(a);//ƒ a() { } 在局部作用域内是声明+赋值
    a = 1
    console.log(a);//1
    function a() { } // 下面再次声明了,这里声明的可以忽略
    a = 2
    console.log(a);//2
    function a() { } // 在局部作用域内是声明+赋值,对于全局只是声明
    a = 3
    console.log(a);//3 在声明函数a的下方,做的操作,对于全局无效
}
console.log(a);//2  在声明函数a的上方,做了任何操作,对于全局都是有效的

例子:

console.log(a);// undefined
if (false) {
    function a() { } // 不管代码是否执行,这里都会在全局 只是声明一下变量a
}
console.log(a);// undefined

例子:

f = function () { return true }
g = function () { return false }
;(function () {
    if (g() && [] == ![]) {// Uncaught TypeError: g is not a function
        f = function () { return false }
        function g() { return true }//变量提升 在匿名函数体内只声明 所以调用g()会报错
    }
})()

this指向

事件绑定

  • DOM0:dom.onclick = function(){}
  • DOM2:
    1. dom.addEventListener('click',function(){})
    2. dom.attachEvent('onclick',function(){}) 给当前元素的某个事件绑定放法(此时是创建方法,方法没执行),当事件行为触发,浏览器会把绑定的函数执行,此时函数中的this 指向当前元素对象本身
  • 特殊:基于attachEvent实现事件绑定,方法执行,方法中的this是window

函数执行

  • 正常的普通函数执行:看函数执行前是否有点,有点前面是谁,this就是谁;如果没有点,this就是window,严格模式下是undefined
  • 匿名函数:
    1. 函数表达式:等同于普通函数或者事件绑定等机制
    2. 自执行函数:this一般都是window/undefined
    3. 回调函数:一般都是window/undefined,但是如果另外函数执行中,对回调函数的执行做了特殊处理,以自己处理为主
  • 括号表达式:小括号中包含多项,这样也只取最后一项,但是this受到影响(一般是window/undefined)
"use strict"//开启js严格模式,默认不严格
function fn() {
    console.log(this);
}
let obj = {
    name: 'zanlan',
    fn
}
fn() // this -> window/undefined
obj.fn()// this -> obj
(10, obj.fn);// this -> window/undefined

//自执行函数
;(function(){
    console.log(this)// this -> window/undefined
})()

//回调函数
function fn(cb){
    cb.call(obj)
}
fn(function(){
    console.log(this)// this -> obj
})


//forEach
let arr = [1]
arr.forEach(function (item, index) {
    console.log(this);//this -> window
})
arr.forEach(function (item, index) {
    console.log(this);//this -> obj
}, obj)

// setTimeout
setTimeout(function (...x) {
    console.log(this);//window
    console.log(x);//[1,2]
}, 1000, 1, 2)

例子:

var x = 1;
var obj = { x: 2 }
obj.fn = (function () {
    this.x *= ++x;
    return function (y) {
        this.x *= (++x) + y
        console.log(x);
    }
})()
var fn = obj.fn;
obj.fn(3)
fn(4)
console.log(obj.x, x);
// 3
//24
// 12 24

解释:

var x = 1;
var obj = { x: 2 }
this.x *= ++x;//window.x = window.x * (++window.x) => window.x = 2 且 obj.x = 2
obj.fn = function w(y) {
    this.x *= (++x) + y
    console.log(x);
}
obj.fn(3)// obj.x = obj.x * ( (++window.x) + 3 ) => window.x = 3 且 obj.x = 12 =>打印出3
var fn = obj.fn;
fn(4)// window.x = window.x * ( (++window.x) + 4 ) => window.x = 24 且 obj.x = 12 =>打印出24
console.log(obj.x, x);// 打印出 12 和 24

闭包作用域和浏览器垃圾回收机制

例子:

let x = 5;
function fn(x) {
    return function w(y) {
        console.log(y + (++x));
    }
}
let f = fn(6) //形成闭包 变量x = 6,供函数w使用,且x不能被外层函数使用
f(7)//14 开始执行w的函数作用域代码 打印 (7 + (++6)) = 14 且 x = 7,这时候f并没有被释放
fn(8)(9)//18 fn(8)形成闭包,同时执行fn(8)(9)时执行了w函数作用域, 打印(9 + (++8)) = 18
f(10)//18 函数f虽然被执行了一次,但是闭包没有被释放,所以这里继续接着用,打印(10 + (++7))=18
console.log(x);//5  这里用的是let声明的5

解释:

  • 堆内存:有其他事物关联到堆内存的地址,浏览器不会回收
  • 手动清除占用:变量 = null
  • 浏览器垃圾回收方案:
    1. 标记清除(谷歌)
    2. 应用计数(IE):可能导致内存泄露
  • 栈内存:执行上下文 全局执行上下文:页面打开时生成,页面关闭时释放。 函数、块级私有上下文:函数执行、代码执行的时候产生的,一般情况下代码执行完,会自动出栈释放,但是有特殊情况(当前私有变上下文中的某些内容或者关联的内容,被上下文以外的事物占用,不仅关联的东西不能被释放,私有上下文也不能被释放),我们把函数执行的这种机制称之为"闭包"
    1. 保证私有上下文的私有变量和外界的变量没有任何关联
    2. 可以把私有变量和对应的值保存起来,供它的下级上下文使用

函数返回一个函数,只是闭包一种形式 例如:

let a = 0;
let b = 0;
function f(a) {
    f = function (b) { // 形成闭包
        console.log(a + b++)
    }
    console.log(a++)
}
f(1) // 1
f(2) // 4

使用闭包解决问题

例子

<button class="btn">111</button>
<button class="btn">222</button>
<button class="btn">333</button>
<script>
    var doms = document.getElementsByClassName("btn");
    for (var i = 0; i < doms.length; i++) {//声明的i变量总是被重新赋值
        doms[i].onclick = function w() {//每一个子dom都绑定了函数w,但是在执行的时候,函数里面的变量i用的是全局的i,然而每次循环全局变量i都会被重新赋值,所以内部都是用的最终值3
            console.log(e.target.onclick, i);// 点击各个按钮,打印的都是3
        };
    }
</script>

方法一使用闭包解决:

var doms = document.getElementsByClassName("btn");
for (var i = 0; i < doms.length; i++) {
    (function (i) {
        doms[i].onclick = function (e) {
            console.log(e.target.onclick, i);
        };
    })(i);//变量存储,用于下级上下文
}

闭包还可以再改写成这样

var doms = document.getElementsByClassName("btn");
for (var i = 0; i < doms.length; i++) {
    doms[i].onclick = (function (i) {
        return function (e) {//这里是我们熟悉的函数返回函数,看似是闭包的标准定义,其实并不是,只是闭包一种表现形式而已
            console.log(e.target.onclick, i);
        };
    })(i);
}

document.querySelectorAll(".btn")获取到的类数组具有forEach,keys,values等方法,使用forEach解决这种问题,然而getElementsByClassName获取的不具备

var doms = document.querySelectorAll(".btn");
doms.forEach((item, i) => {
// 迭代集合中每一项,都把这个回调函数执行,产生一个闭包,因为上下文中建设的小函数,被外层的按钮对象onclick占用了
    item.onclick = function (e) {
        console.log(e.target.onclick, i);
    };
});

三种方法,前两种是命令式编程,专注点是how,如何去做,第三种方法时函数式编程,专注点是what结果是啥

  • 命令式编程:自己写循环,这样我们可以把控循环的具体步骤,管控循环过程,灵活但代码繁琐
  • 函数式编程:把循环的步骤封装成一个函数,我们无需知道内部是如何迭代的,我们只需要直到,它会迭代每一项,每一次会把的、回调函数执行,我们在回调函数中做自己的事情即可。

自定义属性:

闭包太多会影响性能,所以这里使用自定义属性解决问题,但是也会有一些性能消耗(元素对象 & 节点集合 & 绑定的方法 都是开辟了堆内存)

var doms = document.getElementsByClassName("btn");
let i = 0;
for (; i < doms.length; i++) {
    doms[i].j = i;
    doms[i].onclick = function (e) {
      console.log(e.target.onclick, this.j);
    };
}

事件委托:终极方法

<button class="btn" data-index="1">111</button>
<button class="btn" data-index="2">222</button>
<button class="btn" data-index="3">333</button>
<script>
    var doms = document.getElementsByClassName("btn");
    document.body.onclick = function (e) {
        let target = e.target;
        if (target.tagName === "BUTTON" && target.className === "btn") {
            console.log(target.getAttribute("data-index"));
        }
    };
</script>

es6的let解决,但是也是产生了闭包

    var doms = document.getElementsByClassName("btn");
    for (let i = 0; i < doms.length; i++) {
            console.log(e.target.onclick, i);
        };
    }

解释:let声明的for循环内部是如何运行的

image.png

直到了上面方法后,我们再解决一个需求问题,要求每一秒打印一次结果

let i = 0;
for (; i < 3; i++) {
    setTimeout(
        (n) => {
            console.log(n);
        },
        i * 1000,
        i
    )// setTimeout 可以传递第三个参数,用于第一个参数函数的回调参数
}

为了避免不要的闭包,我们可以将let声明放在for循环体外面

let i = 0 ;
for(;i<3;i++){
    (function(i){
        setTimeout(function(){
            console.log(i)
        },i*1000)
    })(i)
}

改成大多数人认为的闭包的标准定义(函数返回函数),其实是错误的,只是一种表现形式

let i = 0;
for(;i<3;i++){
    setTimeout(function(i){
        return function(){
            console.log(i)
        }
    }(i),i*1000)
}

闭包题目,你会做吗?

function fn() {
    let a = 2;// 报错  不能重复声明a
    function a() {}
}
fn() 
var a = 1;
function fn(a) { // 在这里变量提升 已经声明了a,下面函数在变量提升只需要定义即可
    console.log(a); // 打印function a() {}
    var a = 2;
    function a() {} // 因为在这里已经变量提升啦,在全局声明,在局部声明+定义
}
fn(a);

你会做吗?

例子:

function foo(){}对全局是声明,对于局部是声明+定义

console.log(foo);// undefined
{
    console.log(foo);// f foo() {}
    function foo(){}
    foo = 1
    console.log(foo);//1
}
console.log(foo);// f foo(){}

例子:

局部的声明重复了,以最后面为准,前面声明的可以直接删除,且在函数声明之前对同一个变量foo做了操作,需要给外层的也同样来一份

console.log(foo);// undefined
{
    console.log(foo);// f foo(){}
    function foo(){}// 
    console.log(foo);//f foo(){}
    foo = 1
    console.log(foo);//1
    function foo(){}
    console.log(foo);//1
}
console.log(foo);//1

例子:

debugger
{
    function foo(){1}
    foo = 1
    function foo(){2}
    foo = 2
}
console.log(foo);// 1

打断点可以看出,全局变量foo和局部变量foo的变化过程

image.png

例子:

函数内作用域(A) 和 形参作用域(B) 都声明了变量x,刚开始A会同步一份xB,然后B中执行的z()y()执行的都是A中的代码,改变的也是A中变量x的值,这里的改变不会影响之前同步的x,且在B中又声明了变量x,且赋值x = 4,最终打印的是 4,也就是说B我自己有值了,我就不在用你的

var x = 1;
function func(
  x,
  y = function () {
    x = 2;
  },
  z = function () {
    x = 3;
  }
) {
  z();
  var x = 4;
  y();
  console.log(x); //4
}
func(x);
console.log(x); //1

这里用的是之前同步的x = 1y()改变的是形参作用域中的变量x值,这里的改变是慢于同步操作的

function func(
  x = 1,
  y = function () {
    x = 2;
  }
) {
  var x;
  y();
  console.log(x); //1
}
func();

刚开始同步一份,后面都是用我自己声明的变量x

function func(
  x = 1,
  y = function () {
    x = 2;
  }
) {
  var x;
  console.log(x); //1
  y();
  console.log(x); //1
  x = 3;
  console.log(x); //3
}
func();

在花括号内的作用域 和 新参作用域 合并为一个,于是按照一般逻辑走即可

function func(
  x = 1,
  y = function () {
    x = 2;
  }
) {
  console.log(x); //1
  y();
  console.log(x); //2
  x = 3;
  console.log(x); //3
}
func();

打断点调试

可以看到同步操作

debugger
var x = 1;
function func(
  x,
  y = function () {
    x = 2;
  }
) {
  var x = 3;
  var y = function () {
    x = 4;
  };
  y();
  console.log(x); //4
}
func(x);
console.log(x); //1

image.png

匿名函数具名化

例子:

var b = 1;
(function b() {
  b = 2;
  console.log(b);// ƒ b() {b = 2; console.log(b)}   具名函数一旦声明在内部使用,但是在内部修改是无效的
})();
console.log(b);// 1

例子:

var b = 1;
(function b() {
  console.log(b);// undefined   这里是用的重新什么的b 变量提升得到的undefined
  var b = 2; // 如果不想用匿名函数值b,可以重新声明b
  console.log(b);// 2   
})();
console.log(b);//1

例子:

var fn = function sum() {};// 只能在sum函数内使用sum,不可以在外面使用
console.log(sum); // sum is not defined

综合例子:可以打断点调试一下

var num = 1;
var obj = {
  num: 2,
};
obj.fn = (function (num) { // 这里形成了闭包
  this.num = num * 3;
  num++;
  return function (n) {
    this.num += n;
    num++;
    console.log(num); // 4  5
  };
})(2);
var fn = obj.fn;
fn(5);// 这里的this 指向window
obj.fn(10);// 这里this指向 obj
console.log(num, obj.num); // 11 12

例子:

let obj = {
  fn: (function () {
    return function () {
      console.log(this);
    };
  })(),
};
obj.fn(); // 打印 fn函数
let fn = obj.fn;
fn(); // 打印window

例子:

var fullName = "language";
var obj = {
  fullName: "javascript",
  prop: {
    getFullName: function () {
      return this.fullName;
    },
  },
};
console.log(obj.prop.getFullName()); // undefined
var f = obj.prop.getFullName();// undefined
console.log(f);// undefined

例子:

var name = "window";

var Tom = {
  name: "tom",
  show: function () {
    console.log(this.name);
  },
  wait() {
    var fun = this.show;
    fun();
  },
};
Tom.wait(); // window

分析Jquery

(function (global, factory) {
	"use strict";        
	if (typeof module === "object" && typeof module.exports === "object") {
        // 支持Commonjs规范:node或webpack
		module.exports = global.document ?
                //只有webpack环境才有document,执行factory函数,返回Jquery对象,所以let jquery = require('jquery')
                //如果在node环境下运行,则下面的匿名函数,如果你不传一个对象w,且w.document得存在否则报错
			factory(global, true) :
			function (w) {
				if (!w.document) {
					throw new Error("jQuery requires a window with a document");
				}
				return factory(w);
			};
	} else {
        //不支持commonjs规范,浏览器,webview
		factory(global);
	}
})(typeof window !== "undefined" ? window : this,
	function (window, noGlobal) {
                // 暴露Api
                // 支持amd模块规范 导入了require.js插件
        	if (typeof define === "function" && define.amd) {
                        define("jquery", [], function () {
                                return jQuery;
                        });
                }
        	if (typeof noGlobal === "undefined") {// 只有在浏览器下noGlobal才是undefined,所以在浏览器下 jQuery和$是相等的,且在window上挂载了
                    window.jQuery = window.$ = jQuery;
                }
		return jQuery;// 返回jQury对象
	}
)

typeof window !== "undefined" ? window : this,typeof检测一个未被声明的变量,它的值时undefined而不会报错,检测当前环境下运行jq,是否存在window这个全局对象,如果存在则结果为window,否则为this

哪些环境可以运行js

  1. 浏览器(webkit/blink,trident,gecko..)可以运行js

  2. webview(webkit) 可以运行js ----特点是:有window全局对象,支持es6Module规范,不支持commonjs规范

  3. node可以运行js ----特点:没有window全局对象,它的全局对象是global,支持commonjs,不支持es6Module规范

  4. webpack可以运行js ----特点:webpack是基于node去打包js代码,最后会把打包的js代码交给浏览器(或者webview)运行,所以存在window全局对象,支持commonjs规范,也支持es6Module规范,而且内部实现了CommonJs和Es6Module规范的相互转换,因为webpack内部实现了模块规范的处理机制。

改写wxsdk.js

lintOnSave

微信开放文档

image.png

就可以在commonjs中运行 也可以在浏览器中导入运行

! function (e, n) {
    if (typeof module === 'object' && typeof module.exports === 'object') {
        // 可以使用commonjs规范了,  let wx = require('./wx')
        // 然后在webpack环境下,可以混用,也可以用import wx from './wx'
        module.exports = n(e)
    } else {
        window.wx = n(e) // 这种是可以在浏览器中运行的
    }
}(typeof window !== "undefined" ? window : this, function (o, e) {})

编写自己的工具库,暴露模板

(function (global, factory) {
    // 验证是否支持AMD
    if (typeof define === 'function' && define.amd) {
        define('utils', [], function () {
            return factory()
        })
    }
    // 验证是否支持CommonJS规范
    if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = factory()
    } else {
        // 浏览器直接导入
        global.utils = factory()
    }
})(typeof window !== 'undefined' ? window : this, function factory() {
    const isWindow = function () { }
    const isPlainObject = function () { }
    return {
        isWindow,
        isPlainObject
    }
})

简便方法:

(function (global, factory) {
    const isWindow = function () { }
    const isPlainObject = function () { }
    let utils = {
        isWindow,
        isPlainObject
    }
    // 验证是否支持AMD
    if (typeof define === 'function' && define.amd) define('utils', [], () => utils)
    // 验证是否支持CommonJS规范
    if (typeof module === 'object' && typeof module.exports === 'object') module.exports = utils
    // 浏览器直接导入
    if (typeof window !== 'undefined') window.utils = utils
})()

jquery多库共存

已知问题:

<script src="node_modules/zepto/dist/zepto.js"></script>
<script>
    console.log($)//zepto
</script>
<script src="node_modules/zepto/dist/jquery.min.js"></script>
<script>
    console.log($)//jQuery
</script>

var
    // Map over jQuery in case of overwrite
    _jQuery = window.jQuery,

    // Map over the $ in case of overwrite
    _$ = window.$; // // 在声明window.$前,其他库用到了window.$,我们就用_$备份一下

jQuery.noConflict = function (deep) {
    if (window.$ === jQuery) {
        window.$ = _$;// 如果确实是jquery占用了,我们直接还原给它用
    }
    if (deep && window.jQuery === jQuery) {
        window.jQuery = _jQuery;
    }
    return jQuery;// 返回jQuery自己的方法
};
if (typeof noGlobal === "undefined") {
    window.jQuery = window.$ = jQuery;// 在这里声明window.$
}

于是有

<script src="node_modules/zepto/dist/zepto.js"></script>
<script src="node_modules/zepto/dist/jquery.min.js"></script>
<script>
    console.log($) //jQuery
    let x = jQuery.noConflict()
    console.log($) //zepto
    console.log(x) //jQuery
</script>

柯里化函数

柯里化函数思想:预先存储,利用闭包的保护机制,我们把一些值事先给存储起来,供给下级上下文使用。

例子:

const fn = (...params) => {
    return (...args) => {
        params = params.concat(args);
        return params.reduce((result, item) => result + item)
    }
}
let res = fn(1, 2)(3)
console.log(res);

例子:函数隐士转换

// 把函数基于alert或者console.log输出的时候,都需要先把隐式转换为字符串(Symbol.toPrimitive -> valueOf -> toString),然后
//再输出,在控制台输出,控制台会在输出的结果前面,加上一个f,代表这是一个函数字符串
const fn = function fn(){
    console.log('zanlan');
}
fn[Symbol.toPrimitive] = function (hint){
    return 100
}
alert(fn) // 先将fn隐式转换为字符串,然后再输出(Symbol.toPrimitive -> valueOf -> toString)
console.log(Number(fn));// 100  

例子:对于未知个参数的求和,且存在多次传参

const curring = function curring() {
    let arr = []
    const add = (...args) => {
        arr = arr.concat(args)
        return add
    }
    add[Symbol.toPrimitive] = function (hint) {
        var res = arr.reduce((result, item) => {
            return result + item
        })
        return res
    }
    return add
}

let add = curring()
let res = add(1)(2)(3)
console.log(Number(res));//6

add = curring()
res = add(1, 2, 3)(4)
console.log(Number(res));//10

add = curring()
res = add(1)(2)(3)(4)(5)
console.log(Number(res));//15

例子:compose实现

function f1(v) {
    return 'f1-' + v + '-f1'
}
function f2(v) {
    return 'f2-' + v + '-f2'
}
function f3(v) {
    return 'f3-' + v + '-f3'
}
var r = f3(f2(f1('demo')))
console.log(r)
var re = compose(f1, f2, f3)('demo')
console.log(re)
function compose(...params) {
    return function (...str) {
        return params.reduce((accumulator, onec, index) => {
            return onec(accumulator)
        }, ...str)
    }
}

另外一种简单的方法 和 简写方式

function compose(...params) {
    return params.reduce(
        (accumulator, onec) => {
            return (...args) => accumulator(onec(...args))
        }
    )
}
//reduce 不传第二个参数则 accumulator是数组第一项,onec从第二项开始直到最后一项,内部函数的返回值就是accumulator后续值
const compose = (...arr) => arr.reduce((a, b) => (...v) => a(b(...v))) // 简写

还需要处理极端情况哦

function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg;
        // return () => {};
    }
    if (funcs.length === 1) {
        return funcs[0];
    }
    return funcs.reduce((accumu, f) => (...args) => accumu(f(...args)));
}

高阶编程之惰性思想

function getCss(element, attr) {
    // 第一次执行,根据兼容情况,重构getCss函数
    if (window.getComputedStyle) {
        getCss = function (element, attr) {
            return window.getComputedStyle(element)[attr]
        }
    } else {
        getCss = function (element, attr) {
            return element.currentStyle[attr]
        }
    }
    // 第一次把重构的函数执行一次,获取对应的结果
    return getCss(element, attr)
}
console.log(getCss(document.body, 'width'));
console.log(getCss(document.body, 'margin'));
console.log(getCss(document.body, 'padding'));

防抖节流

最简单的防抖处理

let submit = document.querySelector('.submit'),count = 0,isRun = false;
const handle = function handle(ev){
    if(isRun) return;
    isRun = true;
    submit.className = 'submit disable'
    setTimeout(()=>{
        console.log(++count);
        isRun = false;
        submit.className = 'submit'
    },1000)
}
submit.onclick = handle
  • 函数防抖{debounce}:在用户频繁触发某个行为的时候,我们只识别一次触发

    1. 频繁触发条件时我们自己可以设定的,例如:我们设置的时500MS,这样只要用户在这段时间内操作两次及以上,就属于频繁触发,但是如果时500MS之前触发一次,之后触发一次,这不算频繁触发。。
    2. 我们可以控制时在开始触发还是结束触发
  • 函数的节流{throttle}:在用户频繁触发某个行为的时候,原本的频率太快了(例如:5MS触发一次),此时我们经过节流处理,可以降低这个频率(例如:我们降低到500MS一次)

  • 帕金森症犯了,我的手一直哆嗦点击了一个小时{3600000MS},而且每隔5MS就点击一次(我们设定的频率时间是500MS)

    1. 防抖:一个小时内,只触发一次,因为一直在频繁操作。。

    2. 节流:如果按照原来的5MS点击一次,会触发72万次,如果我们根据节流降低了触发频率,让其间隔500MS才触发一次,所以最后只触发7200次。。。。

防抖代码{debounce}

/**
 * debounce()第一个参数必须是函数,否则报错
 * debounce(handle) wait=500 immediate = false
 * debounce(handle,300) wait=300 immediate = false
 * debounce(handle,true) wait=500 immediate = true
 * 
*/
const debounce = function debounce(func, wait, immediate) {
    if (typeof func !== 'function') throw new TypeError('func must be an function')
    if (typeof func === 'boolean') immediate = wait
    if (typeof func !== 'number') wait = 500
    if (typeof immediate !== 'boolean') immediate = false
    let timer = null;
    return function operate(...params) {
        let now = !timer && immediate, result;
        timer = clearTimer(timer);
        timer = setTimeout(() => {
            timer = clearTimer(timer)
            if (!immediate) func.call(this, ...params) // 使用call是让handle函数的this指向dom
        }, wait)
        if (now) result = func.call(this, ...params)
        return result
    }
}

使用:

let submit = document.querySelector('.submit'),count = 0;
const handle = function handle(ev){
    console.log(++count)
}
submit.onclick = _.debounce(handle)

节流代码{debounce}

/**
 * 5ms operate 把func立即执行
 * 10ms operate 第二次相对第一次间隔5ms,此时设置定时器,等待495ms后再触发
 * 15ms operate 此时不需要设置新的定时器,继续等待之前定时器到时间即可
 * 20ms operate
 * ...
 * 500ms operate 执行
 * 505ms operate 设置定时器等待495ms
 * 
*/
const throttle = function throttle(func, wait) {
    if (typeof func !== 'function') throw new TypeError('func must be an function')
    if (typeof func !== 'number') wait = 500
    let timer = null, previous = 0;//使用previous记录上一次操作的时间
    return function operate(...params) {
        let remaining = wait - (+new Date() - previous), result;
        if (remaining <= 0) {
            timer = clearTimeout(timer)
            // 两次时间超过了500ms{或者第一次执行},则立即执行函数
            result = func.call(this, ...params)
            previous = +new Date()
            return result
        }
        // 间隔没有超过500ms,并且还没有设置定时器时,此时设置定时器,等待执行即可
        if (!timer) {
            timer = setTimeout(() => {
                timer = clearTimeout(timer)
                func.call(this, ...params)
                previous = +new Date()
            }, remaining);
        }
    }
}

使用:

let submit = document.querySelector('.submit'),count = 0;
const handle = function handle(ev){
    console.log(++count)
}
submit.onclick = _.throttle(handle)

事件绑定

事件

事件是浏览器赋予给浏览器的默认行为,只要代码在的浏览器存在,就已经具有事件方法,当某些行为触发的时候,相关的事件都会被触发浏览器赋予元素的事件行为

  1. 鼠标事件
    • click 点击事件(PC:点击n次,触发n次点击事件),单击事件(移动端:300ms内没有发生第二次点击操作,算作单击事件行为,所以click在移动端有300ms延迟)
    • dblclick 双击事件
    • contextmenu 鼠标右键点击触发
    • mousedown 鼠标按下
    • mouseup 鼠标抬起
    • mousemove 鼠标移动
    • mouseover 鼠标划入
    • mouseout 鼠标划出
    • mouseleave 鼠标离开
    • mousewheel 鼠标滚轮滚动
  2. 键盘事件
    • keydown 键盘按下
    • keyup 键盘抬起
    • keydown 长按(处理Shift/Fn/CapsLock键之外)
  3. 手指事件
    • touchstart 手指按下
    • dblclick 手指移动
    • touchend 手指松动
  4. 表单事件
    • focus 获取焦点
    • blur 失去焦点
    • submit 表单焦点(前提:表单元素都包含在form中,并且点击的按钮是submit)
    • reset 表单重置(前提:表单元素都包含在form中,并且点击的按钮是reset)
    • select 下拉框内容选中
    • change内容改变
    • input 移动端经常使用的,监控文本框中内容随着输入的改变而触发
  5. 资源事件
    • load加载成功 (window.onload / img.onload)
    • error加载失败
    • beforeunload 资源卸载之前(window.onbeforeunload页面关闭之前触发)
  6. css3动画事件
    • transitionend transition动画结束
    • transitionstart transition动画开始
    • transitionrun transitin动画运行中
  7. 视图事件
    • resize 窗口大小改变
    • scroll 滚动条滚动 事件绑定

给元素默认的事件行为绑定方法,这样可以在行为触发的时候,执行这个方法

  1. DOM0事件绑定
    • 语法:[元素].on[事件] = [函数]
    • document.body.onclick = function(){}
    • 移除绑定:赋值为null或者其他非函数值都可以
    • document.body.onclick = null
    • 原理:每一个DOM元素的私有属性上有很多类似于'onxxx'的私有属性,我们给这些代表事件的私有属性赋值,就是DOM0事件绑定
      • 1.如果没有对应事件的私有属性值(例如:DOMContentLoaded)则无法基于这种方法实现事件绑定
      • 2.只能给当前元素的某个事件行为绑定一个方法,绑定多个方法,最后一个操作会覆盖以往的
  2. DOM2事件绑定
  • document.body.onclick = function(){},大部分人认为是给body绑定一个点击事件,其实是错误的说法,标准说法是给body的点击事件行为绑定方法
  • 语法:[元素].addEventListener([事件],[方法],[捕获/冒泡])
    • document.body.addEventListener('click',fn1,false)
  • 移除:[元素].removeEventListener([事件],[方法],[捕获/冒泡]),但是需要参数和绑定的时候一样
    • document.body.removeEventListener('click',fn1,false)
  • 原理:每一个DOM元素都会基于__proto__,查找到EventTarget.prototype上的addEventListener/removeEventListener等方法,基于这些方法实现事件的绑定和移除;DOM2事件绑定采用事件池机制
  • DOM2事件绑定,绑定的方法一般不是匿名函数,主要目的是方便移除事件绑定的时候使用
  • 凡是浏览器提供的事件行为,都可以基于这种模式完成事件的绑定和移除(例如:window.onDOMContentLoaded是不行的,因为没有这个私有的事件属性,但是我们可以使用window.addEventListener('DOMContentLoaded',func)这样是可以的)
  • 可以给当前元素的某个事件类型绑定多个不同的方法进入到事件池,这样当事件行为触发,会从事件池中依次按照绑定的顺序取出对应的方法然后执行。

事件池

image.png

事件对象和阻止默认事件

事件对象

存储当前事件操作及触发的相关信息的(浏览器本身记录的,记录的是当前这次操作的信息,和在哪个函数中无关)

  • 鼠标事件对象MouseEvent
    1. clientX/clientY 鼠标触发点距离当前窗口的X/Y轴坐标
    2. pageX/pageY鼠标触发点距离body的X/Y轴坐标
    3. type事件类型
    4. path传播路径
    5. e.preventDefault() / ev.returnValue = false 阻止默认行为
    6. e.stopPropagation() / e.cancelBubble = true 阻止冒泡传播
  • 键盘事件对象 KeyboardEvent
    1. key / code 存储按键名字
    2. which / keyCode 获取按键的键盘码
      • 方向键 (左37 上38 右39 下40)
      • Space 32
      • BackSpace 8
      • Del 46 (Mac电脑中没有 BackSpace,delete键是8)
      • Enter 13
      • Shift 16
      • Ctrl 17
      • Alt 18
    3. altKey 是否按下alt键(组合按键)
    4. ctrlKey 是否按下ctrl键(组合按键)
    5. shiftKey 是否按下shift键(组合按键)
  • 手指事件对象 TouchEvent
    1. changedTouches / touches 都是用来记录手指信息的,平时常用的是changedTouches
    2. 手指按下,移动,离开屏幕changedTOuches都存储了对应的手指信息,哪怕离开屏幕后,存储的也是最后一次手指在屏幕中的信息,然而touches在手指离开屏幕后,就没有任何的信息了,两种获取的结果都是TouchList集合,记录每一根手指的信息
    3. e.changedTouches[0] 第一根手指的信息
      • clientX / clientY
      • pageX / pageY
  • 普通事件对象 Event

默认行为

  • 浏览器赋予元素很多默认的行为操作
  • 鼠标右键菜单
  • 点击a标签实现页面的跳转
  • 部分浏览器会记录输入记录,在下一次输入的时候有模糊匹配
  • 键盘按下会输入内容
  • 我们可以基于e.preventDefault()来禁用这些默认行为

禁用右键菜单,创建自己的菜单

window.oncontextmenu = function (e) {
    e.preventDefault()
    let contextmenu = document.querySelector('.contextMenu')
    if (!contextmenu) {
        contextmenu = document.createElement('div')
        contextmenu.className = 'contextmenu'
        contextmenu.innerHTML = `
            <ul>
                <li>菜单一</li>
                <li>菜单二</li>
            </ul>
        `
        document.body.appendChild(contextmenu)
    }
    contextmenu.style.left = `${e.clientX + 10}px`
    contextmenu.style.top = `${e.clientY + 10}px`
}

window.onclick = function (e) {
    let target = e.target,
        targetTag = target.tagName;
    if (targetTag === 'LI') {
        target = target.parentNode;
        targetTag = target.tagName
    }
    if (targetTag == 'UL' && target.parentNode.className == 'contextmenu') {
        return
    }
    let contextMenu = document.querySelector('.contextmenu')
    if (contextmenu) {
        document.body.removeChild(contextMenu)
    }
}

a标签的默认行为

  • 页面跳转
  • 锚点定位(定位到当前页面指定ID的盒子位置,URL地址中会加入HASH值)
  • 阻止默认行为方案
    1. href="javascript:;"
    2. dom.onclick = function(e){e.preventDefault();}
    3. dom.onclick = function(e){return false;}
//锚点定位
<a href="#box1">chapter one</a>
<a href="#box2">chapter two</a>
<div id="box1">...</div>
<div id="box2">...</div>

image.png

事件传播机制

  • 捕获阶段:从最外层元素一直向内层逐级查找,直到找到事件源为止,目的是为冒泡阶段的传播提供路径 =>e.path存放的就是捕获阶段收集的传播路径

  • 目标阶段:把当前事件源的相关事件行为触发

  • 冒泡阶段:按照捕获阶段收集的传播路径,不仅仅当前事件源的相关事件行为被触发,而且从内到外,其祖先所欲元素的相关事件行为也都会被触发,如果做了事件绑定,绑定的方法也会执行。

image.png

image.png

image.png

<body>
    <div class="box">
        box
        <div class="outer">
            outer
            <div class="inner">inner</div>
        </div>
    </div>
    <script>
        let box = document.querySelector('box'),
            outer = document.querySelector('outer'),
            inner = document.querySelector('.inner')
        inner.onclick = function (ev) {
            console.log('INNer==>', ev);
        }
        outer.onclick = function (ev) {
            console.log('Outer==>', ev);
        }
        box.onclick = function (ev) {
            console.log('BODY==>', ev);
        }
        document.body.onclick = function (ev) {
            console.log('BODY==', ev);
        }
    </script>
</body>

点击inner则打印出

image.png

如何让事件在捕获阶段触发

使用 dom.addEventLister(事件,方法,false/true) 默认是false,代表冒泡阶段触发,true代表捕获阶段触发

阻止事件冒泡

// 兼容IE6 - 8 的写法
e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true

事件委托 因为点击事件行为存在冒泡传播机制看,所以不论点击INNER/OUTER/BOX,最后都会传播到BODY上,触发BODY的CLICK事件行为,把为其绑定的方法执行 在方法中执行接收到的事件对象中,有一个target/scrElement属性(事件源),可以知道当前操作的是谁,我们此时方法中完全可以根据事件源的不同,做不同的处理。 上述机制就是 事件委托/事件代理 利用事件的冒泡传播机制,我们可以把一个容器中所有后台上,把给它绑定的方法执行,在方法执行的时候,基于事件源不同做不同的处理

  • 性能高60%左右
  • 可以操作动态绑定的元素
  • 某些需求必须基于它完成

mouseover(mouseout) 和 mouseenter(mouseleave)区别

image.png

onmouseover 和 onmouseout

<div class="outer">
    outer
    <div class="inner">inner</div>
</div>
<script>
    let outer = document.querySelector('.outer'),
        inner = document.querySelector('.inner')
    inner.onmouseover = function (ev) {
        console.log('INNer onmouseover');
    }
    inner.onmouseout = function (ev) {
        console.log('INNer onmouseout');
    }
    outer.onmouseover = function (ev) {
        console.log('Outer onmouseover');
    }
    outer.onmouseout = function (ev) {
        console.log('Outer onmouseout');
    }
</script>

onmouseenter 和 onmouseleave

<div class="outer">
    outer
    <div class="inner">inner</div>
</div>
<script>
    let outer = document.querySelector('.outer'),
        inner = document.querySelector('.inner')
    inner.onmouseenter = function (ev) {
        console.log('INNer onmouseenter');
    }
    inner.onmouseleave = function (ev) {
        console.log('INNer onmouseleave');
    }
    outer.onmouseenter = function (ev) {
        console.log('Outer onmouseenter');
    }
    outer.onmouseleave = function (ev) {
        console.log('Outer onmouseleave');
    }
</script>

实现放大镜效果

css代码

* {
    margin: 0;
    padding: 0;
}

#wrap {
    position: relative;
    height: 600px;
    width: 400px;
    border: 2px solid blue;
}

#wrap #bigContent {
    position: absolute;
    height: 400px;
    width: 400px;
    top: 0;
    left: 500px;
    overflow: hidden;
    display: block;
    border: 3px solid yellow;
}

#wrap #bigContent img {
    height: 1000px;
    width: 1000px;
    display: block;
    position: absolute;
}

#wrap #content {
    position: relative;
    height: 400px;
    width: 400px;
    margin-bottom: 20px;
}

#content #shadow {
    height: 160px;
    width: 160px;
    position: absolute;
    background-color: rgba(66, 166, 166, 0.4);
    left: 0;
    top: 0;
    display: none;
}

#wrap #content img {
    height: 400px;
    width: 400px;
}

#footer {
    height: 100px;
    width: 400px;
    list-style: none;
}

#footer li {
    float: left;
    width: 98px;
    height: 98px;
    border: 1px solid #cdcdcd;
    line-height: 98px;
    text-align: center;
    display: flex;
    justify-content: center;
    align-items: center;
}

#footer li img {
    width: 90px;
    height: 90px;
}

#footer li:hover {
    border: 1px solid black;
}

body部分代码

<div id="wrap">
    <div id="content">
        <img src="img/small1.jpg" id="smallImg" />
        <div id="shadow"></div>
    </div>
    <div id="bigContent">
        <img src="img/big1.jpg" id="bigImg" />
    </div>
    <ul id="footer">
        <li><img src="img/small1.jpg" alt="" data-src="img/big1.jpg"></li>
        <li><img src="img/small2.jpg" alt="" data-src="img/big2.jpg"></li>
        <li><img src="img/small3.jpg" alt="" data-src="img/big3.jpg"></li>
        <li><img src="img/small4.jpg" alt="" data-src="img/big4.jpg"></li>
    </ul>
</div>

js部分代码

var liList = document.querySelector("#footer").querySelectorAll("li");
var smallImg = document.querySelector("#smallImg");
var content = document.querySelector("#content");
var shadow = document.querySelector("#shadow");
var bigImg = document.querySelector("#bigImg");
var wrap = document.querySelector("#wrap");
var bigContent = document.querySelector("#bigContent");
for (var i = 0; i < liList.length; i++) {
    liList[i].onmouseover = function () {
        var img = this.querySelector("img")
        smallImg.src = img.src;
        bigImg.src = img.getAttribute("data-src");
    }
}
content.onmousemove = function (e) {
    var evt = window.event || e;
    var x = evt.clientX - getStyle(shadow, "width") / 2;
    var y = evt.clientY - getStyle(shadow, "height") / 2;
    var maxX = getStyle(content, "width") - getStyle(shadow, "width");
    var maxY = getStyle(content, "height") - getStyle(shadow, "height");
    if (x <= 0) { x = 0; }
    if (y <= 0) { y = 0; }
    if (x >= maxX) { x = maxX; }
    if (y >= maxY) { y = maxY; }
    var bigX = -(getStyle(bigImg, "width") / getStyle(content, "width") * x);
    var bigY = -(getStyle(bigImg, "height") / getStyle(content, "height") * y);
    shadow.style.left = x + "px";
    shadow.style.top = y + "px";
    bigImg.style.left = bigX + "px";
    bigImg.style.top = bigY + "px";
    shadow.style.display = "block";
    bigContent.style.display = "block";
}
content.onmouseout = function () {
    shadow.style.display = "none";
    bigContent.style.display = "none";
}
function getStyle(obj, attr) {
    if (obj.currentStyle) {
        return parseInt(obj.currentStyle[attr]);
    } else {
        return parseInt(window.getComputedStyle(obj)[attr]);
    }
}

image.png

image.png

image.png

拖拽

image.png

image.png

代码实现

css

* {
    margin: 0;
    padding: 0;
}

html,
body {
    height: 200%;
    background: -webkit-linear-gradient(top left, lightblue, orange);
}

.box {
    position: fixed;
    top: 100px;
    left: 200px;
    width: 100px;
    height: 100px;
    background-color: lightcoral;
    cursor: move;
}

js部分

<div class="box"></div>
<script>
    let box = document.querySelector('.box'),
        HTML = document.documentElement,
        minL = 0,
        minT = 0,
        maxL = HTML.clientWidth - box.offsetWidth,
        maxT = HTML.clientHeight - box.offsetHeight
    const down = function down(ev) {
        let {
            top,
            left
        } = this.getBoundingClientRect()
        console.log(top);
        console.log(left);
        this.startT = top;
        this.startL = left;
        this.startX = ev.clientX;
        this.startY = ev.clientY;
        this._move = move.bind(box)
        this._bind = up.bind(box)
        window.addEventListener('mousemove', this._move) // 绑定box 会存在 拖拽bug
        window.addEventListener('mouseup', this._bind)
    }
    const move = function move(ev) {
        let curL = ev.clientX - this.startX + this.startL
        let curT = ev.clientY - this.startY + this.startT
        curL = curL < minL ? minL : (curL > maxL ? maxL : curL)
        curT = curT < minT ? minT : (curT > maxT ? maxT : curT)
        this.style.left = `${curL}px`
        this.style.top = `${curT}px`
    }
    const up = function up(ev) {
        window.removeEventListener('mousemove', this._move)
        window.removeEventListener('mouseup', this._bind)
    }
    box.addEventListener('mousedown', down)
</script>

其他api拖拽方法,可以学习一下,真实项目中用的少,主要是用上面这种方法

dragstart

原型链

原型链理解

function Person(params) {
    this.name = params.name;
    this.gender = params.gender;
}
Person.prototype.family = 'china';
Person.school = '湖理';
var p = new Person({ name: '张良', gender: '男' });
console.dir(p);
// p   ==>   Person.prototype  ==> Object.prototype ==> null
// p可以用 上一级的属性方法  但是通过p.hasOwnProperty("family") 则为false

console.log(Person.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);

// 上一级.isPrototypeOf(自己)
console.log(Object.prototype.isPrototypeOf(Person.prototype));

// 实例 instanceof 构造函数
console.log(p instanceof Person);
console.log(Person.prototype instanceof Object);

// 任何对象的原型构造器指向自己
console.log(Person.prototype.constructor.prototype.constructor === Person);

// 隐形__proto__
console.log(p.__proto__.constructor === p.constructor);

继承方法一,构造函数继承

function Parent(name) {
    this.name = name;
}
Parent.prototype.gender = '111';
function Child(name) {
    Parent.call(this, name);
}
let child = new Child('德玛西亚');
console.log(child.name); // 德玛西亚
console.log(child.gender);//undefined

继承方法二 原型链继承

function Parent(name, gender) {
    this.name = name;
    this.gender = gender;
    this.list = [1, 2, 3];
}
Parent.prototype.eat = "晚餐时间到"
function Child(age) {
    this.age = age;
}
Child.prototype = new Parent('李白', '男');
var child = new Child(20);
var child2 = new Child(30);
console.log('child.eat: ', child.eat);
console.log(child.list, child2.list); // [1,2,3] [1,2,3]
child.list.push(4);
console.log(child.list); // [1,2,3,4]
console.log(child2.list); // [1,2,3,4]

继承方法三 组合继承 (Person.call调用很频繁)

function Person(school) {
    this.school = school;
}
Person.prototype.skill = function () {
    console.log('学习');
};
function Student(school, name, age, gender) {
    Person.call(this, school);
    this.name = name;
    this.age = age;
    this.gender = gender;
}
Student.prototype = Person.prototype;
let student = new Student('广铁一中', '王菲菲', 14, '女');
console.log(Student.prototype === Person.prototype);
console.log(student.__proto__.constructor);

继承方法三优化 组合继承优化

Object.create的理解

function Parent(name, play) {
    this.name = name;
    this.play = play;
    this.arr=[1,2,3,4]
}
function Child(name, play, age) {
    Parent.call(this, name, play);
    this.age = age;
}
Child.prototype = Object.create(Parent.prototype); // Parent.prototype 作为  Child.prototype的__proro__方法
Child.prototype.constructor = Child;
let child = new Child('张三', '玩', 20);
let child2 = new Child('李四', '吃', 10);
child.arr.push(55)
console.log(child.arr);//[1, 2, 3, 4, 55]
console.log(child2.arr);//[1, 2, 3, 4]

例题:

function Fn(){
    this.x = 100
    this.y = 200
    this.getX = function(){
        console.log(this.x);
    }
}
Fn.prototype.getX = function(){
    console.log(this.x);
}
Fn.prototype.getY = function(){
    console.log(this.y);
}
let f1 = new Fn()
let f2 = new Fn()
console.log(f1.getX === f2.getX);// false
f1.getY() //200
Fn.prototype.getY()// undefined

例子

function Foo() {
    getName = function () {
        console.log(1);
    }
    return this
}
Foo.getName = function () {
    console.log(2);
}
Foo.prototype.getName = function () {
    console.log(3);
}
var getName = function () {
    console.log(4);
}
function getName() {
    console.log(5);
}
Foo.getName()//2
getName()//4
Foo().getName()//1
getName()//1
new Foo.getName()//2   把Foo.getName看作一个整体
new Foo().getName()//3     Foo实例下的getName
new new Foo().getName()//3   基于上面获取的getName 通过new触发

ES5 和 ES6 创建构造函数

ES5

function Modal(x, y) {
    this.x = x
    this.y = y
}
Modal.prototype.z = 10
Modal.prototype.getX = function () {
    console.log(this.x);
}
Modal.prototype.getY = function () {
    console.log(this.y);
}
Modal.n = 200;
Modal.setNumber = function (n) {    
    this.n = n
}
let m = new Modal(10, 20)
m.getY() // 20
Modal.prototype.getY() // undefined

ES6

class Modal {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    static n = 200;
    static setNumber(n) {
        this.n = n
    }
    getX() {// 实例对象不可以枚举到
        console.log(this.x);
    }
    getY() {// 实例对象不可以枚举到
        console.log(this.y);
    }
}
Modal.prototype.z = 10 // 实例对象可以枚举到  ES5设置的prototype的属性
let m = new Modal(10, 20)
for (var key in m) {
    console.log(key);// x y z
}

类数组 偷 数组的方法

forEach 三种方案

let doms = document.getElementByTagName("*");
//他是HTMLCollection集合/实例 类数组集合,不能直接使用数组的方法,如果想使用数组的办法
前提条件:类数组结构和数组结构基本一致,操作数据的代码也适用于类数组
把其转换为数组
Array.from(doms).forEach(item=>{
    console.log(item)
})
把数组的方法 赋值给这个集合

doms.forEach = Array.prototype.forEach
doms.forEach(item=>{
    console.log(item)
})
基于call实现this的改变
[].forEach.call(doms,item=>{
    console.log(item)
})

push

Array.prototype.push = function (val){
    this[this.length] = val; // 最后一项插入val值
    this.length++; // 长度递增
    return this.length;
}
let obj = {
    2:3,// 2:1
    3:4,// 3:4
    length:2,//3 4
    push:Array.prototype.push
}
obj.push(1)// obj[obj.length] = 1; obj.length++
obj.push(2)// obj[obj.length] = 2; obj.length++
console.log(obj);// {2: 1, 3: 2, length: 4, push: ƒ}

toArray

let utils = (function () {
    function toArray() {
        // arguments 类数组集合
        // return Array.from(arguments)
        // return [...arguments]
        return [].slice.call(arguments, 0)
    }
    return {
        toArray
    }
})()
let ary = utils.toArray(10, 20, 30)//[10,20,30]
ary = utils.toArray('A', 10, 20, 30)//['A',10,20,30]

数组的方法

push()
pop()
shift()
unshift()
splice()
sort()
reverse()
copyWithin(target,start,end)
Array(9).fill(null)
fill(value,start,end)

lastIndexOf(item) // 返回最后一个满足条件的下表
indexOf(itme) //  返回第一个满足条件的下表
includes(item) //  在内则返回true
slice(start,end) // end不传则代表从start开始的全部  end为-x 代表干掉x个尾巴
join('-') // 串联数组
keys   可以枚举数组 index
values   可以枚举数组 value
entries() // 可以枚举的数组 [index,value]

findIndex(f) // 返回满足条件 第一条项的下标
find(f) // 返回满足条件的 第一条项
fileter(f)
map(f)
forEach(f)
reduce(function(accumulator,item,index,array){},initialValue)
reduceRight 从右边开始
every(f)
some(f)
flat([depth])
[5, 4, -3, 20, 17, -33, -4, 18].flatMap( n =>(n < 0) ? [] :(n % 2 == 0) ? [n] :[n-1, 1])   //[4, 1, 4, 20, 16, 1, 18]
toLocaleString       (35000000).toLocaleString()   //"35,000,000"
toString   [1,3,4,5].toString() //"1,3,4,5"

Object Function Array 关系图

在这里插入图片描述

const 问题

function f(){ f2() }
const f2 = function(){ console.log('zanlan') }
f() // 'zanlan'

js事件循环

例子 例子 例子 例子 例子 例子

new Promise(resolve => {
    console.log(1);
    resolve()
}).then(() => {
    console.log(4);
    new Promise(resolve => {
        console.log(5);
        resolve()
    }).then(() => {
        console.log(8);
    }).then(() => {
        console.log(12);
    }).then(() => {
        console.log(16);
    })
}).then(() => {
    console.log(9);
}).then(() => {
    console.log(13);
})
new Promise(resolve => {
    console.log(2);
    resolve()
}).then(() => {
    console.log(6);
}).then(() => {
    console.log(10);
}).then(() => {
    console.log(14);
})
new Promise(resolve => {
    console.log(3);
    resolve()
}).then(() => {
    console.log(7);
}).then(() => {
    console.log(11);
}).then(() => {
    console.log(15);
})
console.log('outer');

image.png 例子

function func1() {
    console.log(3);
    return new Promise(resolve => {
        resolve(2)
    })
}
function func2() {
    console.log(4);
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(4)
        }, 10);
    })
}
console.log(1);
setTimeout(async () => {
    console.log(9);
    await func1()
    console.log(11);
}, 20);
for (let i = 9; i < 90; i++) { }
console.log(2);
func1().then(v => {
    console.log(6);
})
func2().then(v => {
    console.log(8);
})
setTimeout(() => {
    console.log(7);
}, 0);
console.log(5);

例子:

setTimeout(() => {
    console.log(4);
    Promise.resolve(5).then((v) => {
        console.log(v);
    })
});
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    return Promise.resolve(3).then(v => {
        setTimeout(() => {
            console.log(6);
        });
        console.log(2);
        return v
    })
}).then(v => {
    console.log(v);
})
// 1 2 3 4 5 6

例子:

window.addEventListener('click', function () {// 宏任务1内部嵌套了微任务1
    Promise.resolve().then(() => {
        console.log(2);
    })
    console.log(1);
})
window.addEventListener('click', function () {// 宏任务2内部嵌套了微任务2
    Promise.resolve().then(() => {
        console.log(4);
    })
    console.log(3);
})
// 1 2 3 4

例子:

setTimeout(() => {
    Promise.resolve().then(() => {
        console.log(2);
    })
    console.log(1);
}, 0);
setTimeout(() => {
    Promise.resolve().then(() => {
        console.log(4);
    })
    console.log(3);
}, 0);
// 1 2 3 4

例子

let tm;
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
})
setTimeout(() => {
    Promise.resolve().then(() => {
        console.log(4);
    }).then(() => {
        console.log(5);
    })
    tm = setInterval(() => {
        console.log(6);
    }, 1000);
    console.log(3);
}, 0);
// 1 2 3 4 5 6 6 6 6 6......

例子

setTimeout(() => {
    console.log(7);
}, 20)
console.log(1);
setTimeout(() => {
    console.log(6);
}, 10);
console.log(2);
console.time('AA')
for (let i = 0; i < 900000000; i++) {
    
}
console.timeEnd('AA')
console.log(3);
setTimeout(() => {
    console.log(8);
}, 8);
console.log(4);
setTimeout(() => {
    console.log(9);
}, 15);
console.log(5);
// 1
// 2
// AA: 660.38525390625 ms
// 3
// 4 5 6 7 8 9

例子:

async function async1() {
    console.log(2);
    await async2()
    console.log(6);
}
async function async2() {
    console.log(3);
}
console.log(1);
setTimeout(function () {
    console.log(8);
}, 0)
async1()
new Promise(function (resolve) {
    console.log(4);
    resolve()
}).then(function () {
    console.log(7);
})
console.log(5);
// 1 2 3 4 5 6 7 8

例子:

setTimeout(() => {
    console.log(2)
}, 0);
new Promise(function (resolve) {
    setTimeout(() => {
        resolve(3)
    });
}).then((v) => {
    console.log(v)
})
Promise.resolve(1).then(v => {
    console.log(v)
})
// 1 2 3

例子:

setTimeout(() => {
    console.log(4)
});
Promise.resolve().then(() => {
    console.log(1)
}).then(() => {
    return Promise.resolve(3).then(data => {
        setTimeout(() => {
            console.log(5)
        });
        console.log(2)
        return data
    })
}).then(data => {
    console.log(data)
})
// 1  2  3  4  5

例子:

setTimeout(async () => {
    console.log(10)
    await new Promise(resolve => {            
        resolve('ok')
        console.log(11)
    }).then(()=>{console.log(12)})
    console.log(13)
}, 50)

setTimeout(async () => {
    console.log(17)
    await new Promise(resolve => {
        console.log(18)
        resolve('ok')
    }).then(()=>{console.log(19)})
    console.log(20)
}, 1000)
Promise.resolve(4).then(() => { console.log(4) })
console.log(1)
console.time('zanlan')
for (let i = 0; i < 90000000; i++) {
}
console.timeEnd('zanlan')
console.log(2)
Promise.resolve(5).then(() => { console.log(5) })

new Promise(resolve => {
    Promise.resolve(6).then(() => { console.log(6) })
    resolve('ok')
}).then(result => {
    console.log(7)
})
Promise.resolve(8).then(() => { console.log(8) })
console.log(3)
Promise.resolve(9).then(() => { console.log(9) })


new Promise(resolve => {
    setTimeout(() => {
        resolve('ok')
    }, 111);
}).then(result => {
    console.log(15)
})
setTimeout(() => {
    console.log(16)
}, 112)
setTimeout(() => {
    console.log(14)
}, 110)

例子:

new Promise(v=>v()).then(()=>{
    new Promise(v=>v()).then(()=>{console.log(1)}).then(()=>{console.log(3)})
}).then(()=>{console.log(2)}).then(()=>{console.log(4)})

new Promise(v=>v()).then(()=>{console.log(5)}).then(()=>{console.log(7)})
new Promise(v=>v()).then(()=>{console.log(6)}).then(()=>{console.log(8)})
//1 2 3 4 5 6 7 8

例子:

new Promise(v=>v()).then(()=>{
        console.log(1)
        new Promise(v=>v()).then(()=>{
        	console.log(2);new Promise(v=>v()).then(
        	()=>{console.log(4)}).then(()=>{console.log(7)})
        }).then(()=>{console.log(5)})
}).then(()=>{console.log(3)}).then(()=>{console.log(6)})
// 1 2 3 4 5 6 7

async await

async | MDN

async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。

async function foo() {
   return 1
}
// 等价于:
function foo() {
   return Promise.resolve(1)
}

async 函数的函数体可以被看作是由 0 个或者多个 await 表达式分割开来的。从第一行代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。这样的话,一个不含 await 表达式的 async 函数是会同步运行的。然而,如果函数体内有一个 await 表达式,async 函数就一定会异步执行。

async function foo() {
   await 1
}
// 等价于:
function foo() {
   return Promise.resolve(1).then(() => undefined)
}

在 await 表达式之后的代码可以被认为是存在在链式调用的 then 回调中,多个 await 表达式都将加入链式调用的 then 回调中,返回值将作为最后一个 then 回调的返回值。

var f2 = function () {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(2)
            console.log(1);
        }, 2000);
    })
}
var f1 = function () {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(4)
            console.log(3);
        }, 1000);
    })
}
var f = async function () {
    const slow = await f2()
    console.log(slow);
    const fast = await f1()
    console.log(fast);
}
f()
// 1 2
// 3 4
var f = async function () {
    const slow = f2()
    const fast = f1()
    console.log(await slow);
    console.log(await fast);
}
f()
// 1 
// 2 3 4
var f = async function () {
    const slow = f2()
    const fast = f1()
    slow.then(v => { console.log(v); })
    fast.then(v => { console.log(v); })
}
f()
// 1 2 
//3 4
var f = async function () {
    return Promise.all([f2(), f1()]).then((arr) => {
        console.log(arr[0]);
        console.log(arr[1]);
    })
}
f()
// 1 
// 2 3 4
var f = async function () {
    return Promise.all([
        (async ()=>console.log(await f2()))(), 
        (async ()=>console.log(await f1()))()
    ])
}
f()
// 1 2 
// 3 4
var f = async function () {
    f2().then((v)=>console.log(v))
    f1().then((v)=>console.log(v))
}
f()
// 1 2 
// 3 4