js数据类型
原始值类型(俗称:值类型,基本数据类型)
- number
- string
- boolean
- null
- undefined
- symbol
- 给对象设置唯一值属性(对象属性名的类型:字符串,symbol)
- 给
Symbol.hasInstance/toStringTag/toPrimitive
- bigint
Number.MAX_SAFE_INTEGER/MIN_SAGE_INTEGERJS中的最大/最小安全数字- 数字后面加n就是bigint类型中,例如90071992547400991n,bigint值保证我们超过安全数字,计算也可以准确
- 服务器返回超大数字,我们可以把其转换为bigint再进行运算;运算完结果,变成字符串传递给服务器即可。。 对象类型(引用数据类型)
- 标准普通对象 {name:'zanlan'}
- 标准特殊对象 数组、正则、日期、错误。。。
- 非标准特殊对象 原始值类型的值,基于构造函数模式,new出来的实例对象
- 可调用/执行对象(函数对象) 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);//打印结果如上
})
- typeof
- 检测的结果是一个字符串,字符串中包含了对应的数据类型,但是也有局限性
- typeof null -> "object"
- typeof 检测对象类型,除函数对象返回"function",其余返回都是"object"不能细分对象
- typeof检测一个未被声明的变量,不会报错,而是返回 "undefined"
- 底层原理机制是,所有的数据类型值,在计算机底层都是按照"二进制"来存储的(64位),typeof检测数据类型,就是按照存储的二进制值来进行检测的,前三位是000的,都被认为是对象,如果对象内部实现了[[call]]方法,则认为是函数,返回 "function" ,否则返回"object",typeof的处理性能相对较好
- 按照计算机底层存储的二进制进行检测,效率高
- 计算机科学:计算机原理,进制转换,计算机网络,数据结构和算法。。。
- 000对象
- 1整数
- 010浮点数
- 100字符串
- 110布尔值
- 000000... null
- -2^30 undefined
- instanceof
- constructor
- 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进制),依次存储对象的键值对,把空间地址存储给变量
- 函数:内存空间中存储三部分信息
- 作用域:当前所处山下文
- 函数体中的代码字符串
- 当做普通对象存储的静态属性和方法
- 第二步:声明变量 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
例子:
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]
解释:
例子:
var a = {
n:1
}
var b = a;
a.x = a = {
n:2
}
console.log(a.x)
console.log(b)
解释:
运算符优先级 - 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
解释:
例子:
// 变量提升 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])
- 一般用于浏览器的隐式转换中
- 数字运算
- isNaN检测
- == 比较
- 规则
- 字符串转换为数字:空字符串变为0,如果出现任何非有效数字字符,结果都是NaN
- 把布尔换为数字:true -> 1 false->0
- null -> 0 undefined -> NaN
- Symbol无法转换为数字,会报错:Uncaught TypeError:cannot convert a Symbol value to a number
- BigInt 去除"n" (超过安全数字的,会按照科学计数法处理)
- 把对象转换为数字
- 先调用对象的Symbol.toPrimitive 这个方法,如果不存在这个方法
- 再调用对象的valueof 获取原始值,如果获取的值不是原始值
- 再调用对象的toString 把其变成字符串
- 最后再把字符串基于Number方法转换为数字
parseInt([val],[radix]) parseFloat([val])
- 一般用于手动转换
- 规则:[val]值必须是一个字符串,如果不是则先转换为字符串;然后从字符串左侧第一个字符开始找,把找到的有效数字字符转转为数字,如果一个没有找到则返回NaN;遇到一个非有效数字字符,不论后面是否还有有效数字字符,都不再查找了;parseFloat可以多识别一个小数点;
- parseInt([val],[radix])方法支持第二个参数(进制radix),从左侧(val)字符串中,查找出符合(radix)进制的字符,把找到的字符看做(radix)进制,最后转换为10进制
- radix 不设置或者设置为0,默认值是10(特殊:如果左侧字符串是以"0x"开始的,默认值是16)
- 取值范围:2~36,如果不在这个范围内(排除0),则结果一定是NaN
- 其他进制转换为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+{}- 检测对象的Symbol.toPrimitive方法,如果有,则执行这个方法,传递hint->'default'
- 没有上述的办法,再基于valueOf获取原始值
- 如果获取的不是原始值,则基于toString把其转换为字符串
- 此时'+'遇到了字符串,则变为字符串拼接 "1[object Object]"
1 + new Date()- 1+
new Date()[Symbol.toPrimitive]('default') - => "1Sun Jun 06 2022"
- 1+
1+new Number(10)- 1+
new Number(10).valueOf()基于valueOf获取的是原始值 - => 11
- 1+
- "+" 只出现在左边,他实现的是把一个值转换为数字
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],会有一套自己的处理逻辑:
- 检测Symbol.toPrimitive,如果有这个方法,浏览器会把方法执行,传递hint(根据场景不一样,浏览器默认传递的值也不一样 'number' / 'string' / 'default');如果没有这个方法,则进行下一步;
- 基于valueOf获取原始值,如果获取的不是原始值,则运行下一步;
- 基于toString获取字符串
- 如果需要转的是数字,则再次把字符串转为数字即可 但是如果直接对象.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 -> truenull或者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
- 十进制转换为二进制的计算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
- js使用Number类型表达数字(整数和浮点数),遵循IEEE-754标准,通过64为二进制值来表示一个数字
- 第0位:符号位,0表示正数,1表示负数 S
- 第1位 到 第11位:存储指数部分 E
- 第12位到第63位:存储效数部分 F
- 最大安全数字16位,Number.MAX_SAFE_INTEGER === Math.pow(2,53) - 1
- 怎么解决精度问题
- 将数字转换为整数
- 第三方库: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:
- dom.addEventListener('click',function(){})
- dom.attachEvent('onclick',function(){}) 给当前元素的某个事件绑定放法(此时是创建方法,方法没执行),当事件行为触发,浏览器会把绑定的函数执行,此时函数中的this 指向当前元素对象本身
- 特殊:基于attachEvent实现事件绑定,方法执行,方法中的this是window
函数执行
- 正常的普通函数执行:看函数执行前是否有点,有点前面是谁,this就是谁;如果没有点,this就是window,严格模式下是undefined
- 匿名函数:
- 函数表达式:等同于普通函数或者事件绑定等机制
- 自执行函数:this一般都是window/undefined
- 回调函数:一般都是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
- 浏览器垃圾回收方案:
- 标记清除(谷歌)
- 应用计数(IE):可能导致内存泄露
- 栈内存:执行上下文
全局执行上下文:页面打开时生成,页面关闭时释放。
函数、块级私有上下文:函数执行、代码执行的时候产生的,一般情况下代码执行完,会自动出栈释放,但是有特殊情况(当前私有变上下文中的某些内容或者关联的内容,被上下文以外的事物占用,不仅关联的东西不能被释放,私有上下文也不能被释放),我们把函数执行的这种机制称之为
"闭包"- 保证私有上下文的私有变量和外界的变量没有任何关联
- 可以把私有变量和对应的值保存起来,供它的下级上下文使用
函数返回一个函数,只是闭包一种形式 例如:
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循环内部是如何运行的
直到了上面方法后,我们再解决一个需求问题,要求每一秒打印一次结果
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的变化过程
例子:
函数内作用域(A) 和 形参作用域(B) 都声明了变量x,刚开始A会同步一份x给B,然后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 = 1,y()改变的是形参作用域中的变量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
匿名函数具名化
例子:
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
-
浏览器(webkit/blink,trident,gecko..)可以运行js
-
webview(webkit) 可以运行js ----特点是:有window全局对象,支持es6Module规范,不支持commonjs规范
-
node可以运行js ----特点:没有window全局对象,它的全局对象是global,支持commonjs,不支持es6Module规范
-
webpack可以运行js ----特点:webpack是基于node去打包js代码,最后会把打包的js代码交给浏览器(或者webview)运行,所以存在window全局对象,支持commonjs规范,也支持es6Module规范,而且内部实现了CommonJs和Es6Module规范的相互转换,因为webpack内部实现了模块规范的处理机制。
改写wxsdk.js
lintOnSave
就可以在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}:在用户频繁触发某个行为的时候,我们只识别一次触发
- 频繁触发条件时我们自己可以设定的,例如:我们设置的时500MS,这样只要用户在这段时间内操作两次及以上,就属于频繁触发,但是如果时500MS之前触发一次,之后触发一次,这不算频繁触发。。
- 我们可以控制时在开始触发还是结束触发
-
函数的节流{throttle}:在用户频繁触发某个行为的时候,原本的频率太快了(例如:5MS触发一次),此时我们经过节流处理,可以降低这个频率(例如:我们降低到500MS一次)
-
帕金森症犯了,我的手一直哆嗦点击了一个小时{3600000MS},而且每隔5MS就点击一次(我们设定的频率时间是500MS)
-
防抖:一个小时内,只触发一次,因为一直在频繁操作。。
-
节流:如果按照原来的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)
事件绑定
事件
事件是浏览器赋予给浏览器的默认行为,只要代码在的浏览器存在,就已经具有事件方法,当某些行为触发的时候,相关的事件都会被触发浏览器赋予元素的事件行为
- 鼠标事件
- click 点击事件(PC:点击n次,触发n次点击事件),单击事件(移动端:300ms内没有发生第二次点击操作,算作单击事件行为,所以click在移动端有300ms延迟)
- dblclick 双击事件
- contextmenu 鼠标右键点击触发
- mousedown 鼠标按下
- mouseup 鼠标抬起
- mousemove 鼠标移动
- mouseover 鼠标划入
- mouseout 鼠标划出
- mouseleave 鼠标离开
- mousewheel 鼠标滚轮滚动
- 键盘事件
- keydown 键盘按下
- keyup 键盘抬起
- keydown 长按(处理Shift/Fn/CapsLock键之外)
- 手指事件
- touchstart 手指按下
- dblclick 手指移动
- touchend 手指松动
- 表单事件
- focus 获取焦点
- blur 失去焦点
- submit 表单焦点(前提:表单元素都包含在form中,并且点击的按钮是submit)
- reset 表单重置(前提:表单元素都包含在form中,并且点击的按钮是reset)
- select 下拉框内容选中
- change内容改变
- input 移动端经常使用的,监控文本框中内容随着输入的改变而触发
- 资源事件
- load加载成功 (window.onload / img.onload)
- error加载失败
- beforeunload 资源卸载之前(window.onbeforeunload页面关闭之前触发)
- css3动画事件
- transitionend transition动画结束
- transitionstart transition动画开始
- transitionrun transitin动画运行中
- 视图事件
- resize 窗口大小改变
- scroll 滚动条滚动 事件绑定
给元素默认的事件行为绑定方法,这样可以在行为触发的时候,执行这个方法
- DOM0事件绑定
- 语法:[元素].on[事件] = [函数]
- document.body.onclick = function(){}
- 移除绑定:赋值为null或者其他非函数值都可以
- document.body.onclick = null
- 原理:每一个DOM元素的私有属性上有很多类似于'onxxx'的私有属性,我们给这些代表事件的私有属性赋值,就是DOM0事件绑定
- 1.如果没有对应事件的私有属性值(例如:DOMContentLoaded)则无法基于这种方法实现事件绑定
- 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)这样是可以的)
- 可以给当前元素的某个事件类型绑定多个不同的方法进入到事件池,这样当事件行为触发,会从事件池中依次按照绑定的顺序取出对应的方法然后执行。
事件池
事件对象和阻止默认事件
事件对象
存储当前事件操作及触发的相关信息的(浏览器本身记录的,记录的是当前这次操作的信息,和在哪个函数中无关)
- 鼠标事件对象MouseEvent
- clientX/clientY 鼠标触发点距离当前窗口的X/Y轴坐标
- pageX/pageY鼠标触发点距离body的X/Y轴坐标
- type事件类型
- path传播路径
- e.preventDefault() / ev.returnValue = false 阻止默认行为
- e.stopPropagation() / e.cancelBubble = true 阻止冒泡传播
- 键盘事件对象 KeyboardEvent
- key / code 存储按键名字
- which / keyCode 获取按键的键盘码
- 方向键 (左37 上38 右39 下40)
- Space 32
- BackSpace 8
- Del 46 (Mac电脑中没有 BackSpace,delete键是8)
- Enter 13
- Shift 16
- Ctrl 17
- Alt 18
- altKey 是否按下alt键(组合按键)
- ctrlKey 是否按下ctrl键(组合按键)
- shiftKey 是否按下shift键(组合按键)
- 手指事件对象 TouchEvent
- changedTouches / touches 都是用来记录手指信息的,平时常用的是changedTouches
- 手指按下,移动,离开屏幕changedTOuches都存储了对应的手指信息,哪怕离开屏幕后,存储的也是最后一次手指在屏幕中的信息,然而touches在手指离开屏幕后,就没有任何的信息了,两种获取的结果都是TouchList集合,记录每一根手指的信息
- 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值)
- 阻止默认行为方案
- href="javascript:;"
- dom.onclick = function(e){e.preventDefault();}
- 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>
事件传播机制
-
捕获阶段:从最外层元素一直向内层逐级查找,直到找到事件源为止,目的是为冒泡阶段的传播提供路径 =>e.path存放的就是捕获阶段收集的传播路径
-
目标阶段:把当前事件源的相关事件行为触发
-
冒泡阶段:按照捕获阶段收集的传播路径,不仅仅当前事件源的相关事件行为被触发,而且从内到外,其祖先所欲元素的相关事件行为也都会被触发,如果做了事件绑定,绑定的方法也会执行。
<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则打印出
如何让事件在捕获阶段触发
使用 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)区别
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]);
}
}
拖拽
代码实现
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拖拽方法,可以学习一下,真实项目中用的少,主要是用上面这种方法
原型链
原型链理解
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);
继承方法三优化 组合继承优化
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');
例子
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 函数一定会返回一个 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