js笔记合集
包含内容:
history API
location API
事件绑定
事件对象
通过JS获取元素位置和大小
表单事件
事件流、事件委托、默认事件
日期对象
常用DOM操作
jQuery简介
原型
Function和Object的原型
instanceof
函数执行上下文
typeof关键字
闭包
事件轮询
Object对象
Function对象
new关键字
与或运算符、相等性
使用与或运算符减少程序错误
let const关键字
解构赋值
字符串模板
可变参数、三点运算符
箭头函数
可迭代对象
Promise
async和await
class
深复制和浅复制
Map容器
Promise的静态方法
数组和字符串互转
微任务
基础篇
—————————————————————————————————————————————————
history:
用来控制网页前进和后退,根据的是网页历史纪录
history.back(); //后退
history.forward(); //前进
无刷新更改URL:
history.pushState(data:json,title:string,url:string); // 会存储在url历史中
history.replaceState(data:json,title:string,url:string); // 不会存储。。。
data是你要存放的数据,可以使用history.state获取,title是标题,为空则不改变,url是新url
location:
用来控制页面跳转
location.replace("xx"); //跳转
location.href = 'xxxx'; //同上
location.reload(); //刷新页面
定时器:
- var id = setInterval(callback,ms); //每隔ms毫秒执行一次函数(回调函数只写函数名)
- var id = setTimeout(callback,ms); //在ms毫秒后执行一次函数
- clearInterval(timer); //清理掉setInterval定时器
- clearTimeout(timeout); //让setTimeout定时器失效
- window.requestAnimationFrame(callBack); //专门为动画设置的定时器(效果比setInterval流畅,每秒执行60次,大部分浏览器中,每秒执行次数和显示器刷新率一致)
事件绑定的方法:
直接使用元素的onclick属性:
<button onclick="btnhandler(this)"> click me </button>
绑定一个事件:
getElementById("xx").onclick = function(e){ xxxxxxxx; }
事件监听:
document.getElementById("xxx").addEventListener('click',function(e){ xxxxxx; })
注意:事件名称前不加on
区别:
使用内联onclick属性似乎只能传递自身而不能传递事件对象
事件监听可以在一种事件上绑定多个方法
注意:
当元素是动态生成的时候,在元素生成前绑定的事件无效,如:
$('.btn').click(function (e){ ...... }); $(‘body’).append('<button class="btn">...</button>'); // 单击事件不能绑定到新的元素上面
手动调用事件:
element.onclick() || element.onclick.call() ;
jquery方式:$(sel).click();
onchange 当表单的值改变时触发(需鼠标抬起,不是即时的)
oninput 当表单控件受到改变时触发(即时的)
事件对象:
事件对象自动传递给回调函数 element.onclick = function(e){}; // e就是事件对象
e的常见属性:
e.target; //获取触发此事件的元素(不一定是绑定元素)
e.currentTarget //获取触发此事件的元素(一定是绑定元素)
e.offsetX ||e.offsetY ; //获取鼠标基于target元素内部的偏移x和y
e.clientX ||e.clientY ; //获取鼠标基于浏览器视窗的偏移x和y
e.keyCode ||e.which; //返回键盘上的字符的代码
事件回调中的this:指向事件的触发元素
----如果事件处理函数的绑定在元素生成之前,则此元素不能绑定事件处理函数,需重新设置
获得和设置元素的行内样式(只针对行内样式):
var a = document.getElementById("xxx").style.width;
document.getElementById("xxx").style.width = '500px' //注意加上单位
获得作用在元素身上的样式:
原生方式:
element.currentStyle.width || getComputedStyle( element,null).width ;
前者是ie专用,后者是w3c标准,注意:用这种方法只能获取不能设置
jquery 方式:
$(sel).css(styleName); // 注意数据类型以及单位
注:行内样式一定是起作用的样式,所以用js设置元素样式都是设置行内样式
js获取元素位置及大小:
- element.offsetTop; // 获取元素相对于定位父元素(left,top属性基于的元素)的顶部的距离
- element.scrollTop // 元素的滚动距离(该元素需要有滚动条,如果没有则为0)【可写】
- element.clientTop // 不常用
// 使用变换(transform)不会改变上述属性的值
// 它们受left,top,margin等css属性的影响,但不一定等于它们
- element.offsetHeight // 元素的高度(包括边框,滚动条),返回无单位的number值
- element.clientHeight // 不包括边框滚动条的高度,如果边框无滚动条,则同上
- element.scrollHeight // 如果有滚动条,则获取滚动内容的高度,没有滚动条则同上【可写】
// 建议使用offsetHeight,因为其他元素排列位置时是会考虑边框和滚动条的
offsetParent // 用于定位的基元素(默认为最近的设置过position的父元素)
滚动动态加载内容:
window.onscroll = function(e){ // 页面滚动事件(一般加给window) // 页面被卷起来的高度距离顶部或底部的距离 var juan = document.documentElement.scrollTop; // 获取页面被卷起来的高度,documentElement相当于html标签 var total = document.documentElement.scrollHeight; // 获取页面总高度 var visul = window.innerHeight; // 获取可见区的高度(即浏览器显示出来的高度) var bot = total - juan - visul; // bot就是可见区下面的高度(这是我们需要的) ........ // 当bot小于某值时,加载新元素 }
注:document的类型是Document,document.documentElement才是常规的Element类型的DOM元素
注:onscroll事件触发次数比较频繁,可以考虑节流(一段时间内只执行一次)
js实现拖动:
需要给被拖动元素和window同时添加mousemove事件,因为即使拖动时鼠标在元素之外,也应该能实现拖动。
具体实现:
1.鼠标按下目标元素时,存储clientX,clientY,以及被拖动元素的引用
2.鼠标移动时,设置被拖动元素的left为当前clientX - 预先存储的clientX,clientY同理
3.释放鼠标时,清除之前存储的数据
获取body标签对应的DOM :document.body
获取html标签对应的DOM :document.documentElement
图片懒加载:
就是先不设置src,而是将路径放到其他属性中(如data-src),等到图片处于显示区或接近显示区时,再设置src
HTML DOM事件在:http://www.runoob.com/jsref/dom-obj-event.html
补充:现在浏览器原生支持图片懒加载了,参考:<img>:图像嵌入元素 - HTML(超文本标记语言) | MDN (mozilla.org)
右键点击事件
oncontextmenu
表单绑定事件的考虑:
- onkeydown // 按下按键时立即触发,该事件一般绑定在document/window上,因为即使被绑定的表单没有获得焦点,该事件也会执行
- onkeypress // 按下按键时立即触发,只有被绑定的元素获得焦点了,才会执行事件(适用于动态search)
- onchange // 表单值改变时执行,按下按键时不是立即触发,而是等到输入完毕时才会触发(输入完毕指的是按下回车或表单失去焦点)
- oninput // 表单值改变时立即触发
实例:
window.keydown = function(e){ //键盘事件一般和窗口绑定 var ev = window.event || e; //获得事件对象(IE和其他浏览器均可用) var word = String.fromCharCode(ev.keyCode); //将ascii码转换成字符 alert(word); }
动画事件:
事件 描述
- animationend 该事件在 CSS 动画结束播放时触发
- animationiteration 该事件在 CSS 动画重复播放时触发
- animationstart 该事件在 CSS 动画开始播放时触发
- transitionend 该事件在 CSS 完成过渡后触发。
目前,需要根据浏览器种类加前缀
事件流:
当元素位置重叠的时候,点击重叠的位置,每个元素的事件会按照一定的顺序触发。
若只想让第一个事件触发,则可在那个事件的方法体中加入以下代码:
e.stopPropagation(); //中断此次事件的后续操作
若显示层级位于表层的元素没有事件而里层元素有绑定事件,那么事件不会触发,因为事件流不是一个通路。这在给父元素加鼠标事件时很常用
事件相互影响的问题:
例如:如按空格让视频暂停,在文本框中输入空格也可能会让视频暂停,这是因为事件冒泡到上级,只需要在文本框上的键盘事件上中断事件流即可
事件委托:
例如,在一个ul中为要每个li设置点击事件,只需给ul设置点击事件即可,li的事件会冒泡至ul上,通过this|e.target获取li的DOM对象
浏览器默认事件:
如:点击a标签后,浏览器会默认跳转到指定页面;点击鼠标右键,会弹出上下文菜单等
阻止默认事件:
建立onclick事件方法,加入var ev=window.event; ev.preventDefault();
阻止a标签的默认事件:
<a href="javascript:void(0)">链接</a>
在事件处理函数中this的用法:
this在事件处理函数中指向事件源对象(触发事件的元素对象)
当元素被批量选中时,this指针对这些元素的事件处理非常有用
使用e.target也能获取事件源对象 // e表示事件对象
e.currentTarget表示最原始的触发这个事件的对象(根据冒泡机制,只要条件符合,子元素也会触发父元素身上的事件)
深入理解事件机制:
事件的传递分成三个阶段:
PHASE1.捕获:事件从祖先元素开始(从window开始)向内传递
PHASE2.目标:事件已经到达目标(最内层的命中事件的元素)
PHASE3.冒泡:事件向外(祖先)冒泡至window
默认的侦听器是侦听冒泡阶段的事件。
以点击事件为例,鼠标点击后,会从window对象开始,检索其子对象的区域是否包含点击点,html,body...直到某个元素的所有子元素的区域都不包含该点但自身包含该点,则此元素为目标元素,接着,从目标元素开始,依次执行点击事件回调,直到window对象。下面的c++代码解释了事件传递机制可能的实现:
ClickHandler(window,new MouseEvent(x,y)); bool ClickHandler(EventTarget * dom, MouseEvent * e){ List<EventTarget*> children = dom->children; bool hit = false; for(int i=0;i<children.length();i++){ hit = ClickHandler(children[i],e) ; if(hit){ e->phase = 3; e->currentTarget = children[i]; dom->eventListener('click').call(e); return true; } } if(dom->area.include(e->x,e->y)){ e->target = e->currentTarget = dom; e->phase = 2; dom->eventListener('click').call(e); return true; } else { return false; } }
日期对象:
js中时间日期是以对象的形式实现的
var time = new Date(); //获得客户端当前的时间,返回一堆字符串,还可以用时间戳构造(注意:客户端的时间可能是不准确的)
var time = new Date(年,月,日,时,分,秒); //创建一个具体的时间
方法:
- getTime(); //获得从1970年1月1日到对象时间的毫秒数(时间戳)
- get年月日时分秒的英文(); //获得对应的时间橙分,如getDate()获得日数
- Date.now() 返回毫秒级时间戳
- toLocalDateTimeString() // 返回本地化的日期时间字符串(对于北京时间,会变成12小时制)
抽取字符串中的数字:
parseInt(str); // 会提取字符串中的整数部分,遇到非整数会立即停止提取;适合去掉css中的单位
parseFloat(str) // 同上,可以提取小数
Number.toFixed(n) // 保留n位小数,为0则只保留整数
Number.round() // 返回最接近的整数(相当于四舍五入)
Number.floor() // 向小取整
定义一个匿名函数,自动执行:
(function (){ //代码块 }());
或者
(function (){ // 代码块 })();
可以在前面加上 ; 提升可靠性
常用DOM操作:
- appendChild(e) // 尾插
- insertBefore(e,ch) // 在子节点ch前插入e
- removeChild(e) // 删除子节点
- replaceChild(new,old) // 替换子节点
- ------------------------------------------------------
- parentNode // 父节点
- children // 子节点集合
- childNodes // 子节点集合
- firstChild // 第一个子节点
- lastChild // 最后一个子节点
- previousSibling // 前一个兄弟节点
- nextSibling // 下一个兄弟节点
- -------------------------------------------------------
- setAttribute(name,value) // 设置元素的属性
- getAttribute(name) // 获取属性
- hasAttribute(name) // 属性是否存在
- -------------------------------------------------------
- dataset // 获取以data-开头的属性(返回对象,只读)
- classList // 获取类名(类数组对象,只读)
jquery篇
—————————————————————————————————————————————————
获取元素:
兄弟:$(sel).siblings(sel);
父级:$(sel).parent(sel); // 只能抓上一级
前辈:$(sel).parents(sel); // 可能是多个
第一个:$(sel).first();
第n个:$(sel).eq(n);
孩子:$(sel).chlidren(sel); // 注意只能抓下一级
取序号:$(sel).index();
迭代器:
$(sel).each(function (i,e)){ // i是序号,e是元素,只有一个参数时,表示序号 // 代码 } $.each(obj,function (i,e)){ // i是序号,e是元素,只有一个参数时,表示序号 // 代码 }
注:js中数组API的回调函数通常是function(e,i){},即序号在后
DOM节点操作:
- append('xxxx'); // 在节点文本后添加内容,返回原来的jQuery对象,而不是添加的
- appendTo(jQuery) // 将元素节点添加至指定jQuery对象的尾部
- prepend('xxxx'); // 在节点文本前添加内容,返回原来的jQuery对象,而不是添加的
- before('xxxx'); // 在节点之前添加内容(一般是新节点)
- after('xxxx'); // 在节点之后添加内容(一般是新节点)
关于表单元素:注意table标签内会自动加一个tbody标签,获取<tr>元素:$('table tbody tr');
jquery动画:
hide(time,callback) show(...) fadeIn(...) fadeOut(...)
toggle动画:toggle() toggleFade() toggleSlide() 指根据display来判断做什么动画
animate(json,timer,[timeFunc,callback]);
obj:格式是{attrName:styleValue, ...},表示元素要达到的样式
timer:int类型,表示动画的毫秒数
timeFunc:速度函数,有ease,linear等
callback: 回调函数
获取内容:
html() // 获取或设置innerHTML
text() // 获取或设置innerTEXT,会自动转义
val(); // 获取或设置value(只能用于表单)
获取宽高和位置:
position(); //获取匹配元素相对父元素的偏移,返回的对象包含两个整型属性:top 和 left
height();
width();
返回数字,单位为px
jquery对象转化为DOM元素对象:
jquery对象不能使用DOM对象的属性和方法,jquery是对dom对象的封装,使用jquery的0号元素即可获得原来的DOM对象
$(sel)[0].setEventListener(....);
定义一个在页面加载完成后立即执行的函数:
$(function (){ // 函数体 });
高级篇
—————————————————————————————————————————————————
函数中的this:
指向主调对象,或者window,其值是在运行时可知的
函数对象和实例对象:
函数对象:即Function类型的对象
实例对象:new 函数名()后生成的对象
为函数对象添加方法:
function Foo(){ this.func = function (){ // 这是为实例添加方法:var obj = new Foo(); obj.func() ... } function func(){ // 没有为任何对象添加方法,该函数仅在foo内部可用,这是错的:foo.func() (×) ... } } Foo.func = function (){ // 这是为函数添加方法:foo.func() ... }
js原型:
·所有函数都有prototype对象
·prototype对象是一个Object类型的对象
·该object对象中有一个constructor对象,指向该函数对象
·可以为prototype对象设置属性,这些属性实际是给实例对象访问的
示例:
var func = function (){ } var f = new func(); func.prototype.print = function (){ console.log("print..."); } f.print(); // 控制台输出print... console.log(func.prototype.constructor === func) // true
所有实例对象都有一个__proto__属性,也是object类型的,该属性和其函数原型(构造函数)的prototype属性是一样的,它们共享一份Object对象,即函数对象.prototype = 实例对象.__proto__,在上述例子中就是func.prototype === f.__proto__
使用对象属性(或方法)时,先测试对象本身有无此方法,若没有,则在__proto__中查找,直到找到,或不存在。这种查找链就是原型链(原型链使用的是__proto__而不是prototype)。
实例(接上例):
f.toString();
// func中无toString方法,其原型中也无,于是通过__proto__到Object的原型中找,在Object中找到了toString方法,。
Function和Object的原型:
·Function和Object是系统内置的函数。
·所有函数对象都是Function类型的实例(通过new Function()得到)
·Object是内置的函数对象,也是Function类型的实例对象
·Function也是函数对象,它也是Function类型的实例对象
由以上三点可知:
·所有的函数对象都有prototype和__proto__两个属性,有prototype是因为所有函数都有prototype对象,有__proto__是因为它是Function类型的实例
·所有函数对象的__proto__都等于Function对象的prototype,因为所有函数对象都是Function对象的实例
·Function对象的prototype和__proto__是相等的
·prototype和__proto__的类型是Object,而Object本身也有prototype和__proto__属性,Object的__proto__属性等于Function对象的prototype(前面说过),Object对象的prototype属性中有很多内置的方法:
- constructor: Object()
- hasOwnProperty: hasOwnProperty()
- isPrototypeOf: isPrototypeOf()
- toString: toString()
- valueOf: valueOf()
Object对象的prototype属性不是Object类型的,而且该属性的__proto__属性为null,它是原型链的终点。
恒成立(假设用户定义了一个函数函数,名为Func):
Func instanceof Function // 因为Func.__proto__ == Function.prototype Func instanceof Object // 因为Func.__proto__.__proto__ == Object.prototype Function instanceof Function // 因为Function.__proto__ == Function.prototype Object instanceof Function // 因为Object.__proto__ == Function.prototype Function instanceof Object // 因为Function.__proto__.__proto__ == Object.prototype Object instanceof Object // 因为Object.__proto__.__proto__ == Object.prototype
instanceof探幽:
L instanceof R当且仅当
L.__proto__......__proto__ === R.prototype
至少有一个__proto__
函数执行上下文:
函数(或全局代码)执行前,会初始化上下文,包括:
- 确定this
- 变量提升:var定义的变量会在同级代码执行前先初始化为undefined
- 函数提升:函数定义会在同级代码执行前先初始化
- 变量提升先于函数提升
例:
function foo(){ console.log(c) // undefined var c = 1; }
注:如果没有定义变量,就直接使用,会在作用域链上查找,而不是在自身作用域上创建。
例1(以下是全局代码):
function foo(){ username = 'zhangshan'; }
会设置window.username为'zhangshan',与函数中的this是谁无任何关系
例2:
function foo(){ this.username = 'zhangshan'; }
直接调用foo()时,效果同上(直接调用某个函数时,调用者一定是windows)
typeof关键字:
typeof可以用来判断类型
typeof的返回值只会是下列值之一:
'string','number','boolean','undefined','object','function'
请不要使用typeof判断一个对象的具体类型;它也无法判断null
闭包:
js支持函数的嵌套定义,内部的函数叫子函数,外部的函数叫父函数。
当子函数引用了父函数中的变量,就会在子函数中产生一个闭包,包含了被引用的变量。
来看这个例子:
function foo(){ var msg="hello"; return function(){ return msg + " world"; } } var a = foo(); console.log(a()); // a能否正确使用msg?
foo()执行完后,变量msg应该被释放,但是子函数引用了msg,产生了闭包,所以msg的生命周期变长,不会被释放,所以执行a()可以正确输出msg的值。
产生闭包需要同时满足:
1.存在函数嵌套
2.子函数中使用了父函数的变量
3.调用父函数
注意:闭包中用到了外部作用域中的变量,只会使该变量的生命周期变长,而不会生成该变量的副本:
在上例中父函数是forEach的匿名回调,子函数是setTimeout的匿名回调,在子函数中引用了变量tick,但不会保存其副本,tick始终从第一行定义tick的地方取值,因此会输出五个最终的tick值(15),若要使输出的tick值递增,可以保存一个tick的“快照”:
let tick = 10; [2,3,4,5,6].forEach(e => { tick = tick + 1; const snaptick = tick; setTimeout(() => { console.log(snaptick); },e*1000); })
此时,setTimeout闭包变量变为snaptick,5个闭包分别引用5个值不相同的snaptick,最终的输出是:
11 12 13 14 15
也可以使用forEach中提供的索引变量:
let tick = 10; [2,3,4,5,6].forEach((e, i) => { setTimeout(() => { console.log(tick+i); },e*1000); })
本质都是创建新的变量来替代原来的闭包变量。
也可以在setTimeout回调中即时取tick的值:
let tick = 10; [2,3,4,5,6].forEach(e => { setTimeout(() => { tick=tick+1; console.log(tick); },e*1000); })
多数情况下,闭包函数需要的是一个外部变量的“快照”,这种即时取值的方式需要判断是否符合功能要求。
事件轮询:
js是单线程的。
定时器回调,DOM事件回调,Ajax回调都会放在回调队列中,待程序顺序执行完毕时,才会执行。
注:并不是回调都是异步任务,比如Promise()的参数function会同步执行
Object对象:
Object.create(obj,[property])
//使用指定对象作为__proto__产生一个新对象。
Object.defineProperty(obj,propname,conf)
// 给obj定义属性propname,conf为配置项。
// 该函数可以监视某个对象的改变,这是很多MVVM框架实现数据绑定的原理
Object.assign(target,source);
// 复制source的所有属性到target并返回
详询:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object
Function对象:
Function.property.call(obj,param);
Function.property.apply(obj,[params]);
Function.property.bind(obj,param);
//均是 给函数指定this,其中bind不会执行,而是返回该函数。
new探幽:
使用某个构造函数new一个对象A实际上就是设置对象A的__proto__为构造函数的prototype,然后将this设置为A来执行构造函数。
手动实现new的方式:
function NEW(f){ // f为构造函数 var obj = Object.create(f.prototype); f.call(obj); return obj; }
函数对象赋值注意:
var $ = document.querySelector; $(...) // Illegal invocation
原因: document.querySelector()的this是document而$()的this是不确定的.
解决:var $ = document.querySelector.bind(document);
与或运算符、相等性:
如果某个变量(或表达式的结果)的值为undefined,null,'',0,false,则为假值,非上述值则为真值
即js的假值有多种,但!假值都是true,同理真值有无数种,但!真值都是false
空对象{}和空数组[]为真值
js的与或运算(&&,||)并不产生true或false,而是:
在处理或运算时,返回第一个为真的值,若全为假,则返回最后一个假值
在处理与运算时,返回第一个为假的值,若全为真,则返回最后一个真值
在处理非运算时,一定返回true或false
关于相等性的研究请参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness
例:
var obj = createSomething() || {}; // 若createSomething()返回假值,则将obj初始化为{}
例(将双等==改成全等===的结果也是一样的):
[undefined] == [undefined] // false,实际上无论数组里面是什么,该表达式始终返回false undefined == false // false undefined || false // false !undefined == true // true undefined && false // undefined false && undefined // false undefined || null // null '11.000' == 11 // true
使用||和&&减少程序错误:
var asd = unknownImplement(); yourImplement(asd[0].text);
存在的问题:
1.asd为假值
2.asd非数组
3.asd无0号元素
使用&&运算符解决:asd && asd[0] && yourImplement(asd[0].text);
解释:
只有asd 和 asd[0] 为真,才会执行函数调用
const list = getSomeList();
list.foreach(...);
存在的问题:
1.list为假值
使用||运算符解决:const list = getSomeList() || [];
注:
在非数组上使用[]运算符是安全的(返回undefined)
使用变量上不存在的属性是安全的(返回undefined)
在假值上使用任何属性都是危险的(ReferenceError)
执行对象上不存在的方法是危险的(TypeError)
ES6:
let关键字:
和var一样,定义变量,区别是:
1.支持块级作用域
2.不能变量提升
const关键字:
定义常量,不能修改
解构赋值:
假定obj为 {name:'zhangshan',age:20}
例1:
let {name,age} = {'zhangshan',15} // 创建name,age两个对象并依次赋值 let {name,age} = obj // 创建name,age两个变量,并按obj中的字段名赋值 let [a,b,c] = [1,2,3] // 创建a,b,c三个变量,按索引号赋值
例2:
function foo({name,age}){....} // 同第二个,按字段名赋值 foo(obj)
字串模板:
使用``包含,并使用${name}标记变量,自动拼接。
var str = `我叫${obj.name},今年${obj.age}岁`;
对象内的属性和方法可以简略:
let obj = { name, // 直接将外部作用于的name赋给它,下同 age, setName(name){ // 相当于function setName(name){} this.name = name } }
数组/对象の赋值(使用...):
let a1 = ['a','b','c']; let a2 = [...a1]; let a3 = [...a2,...a1]; let o1 = {a:123,b:456}; let o2 = {...o1}
可变参数(rest):
function foo(...value); // value会变成参数数组 function foo(arg1,...value); // value会变成除arg外的参数数组
...运算符:
...expression // 如果...后接一个可迭代对象,则此语句的作用是将该对象的所有可迭代属性变成当前对象内的属性(即释放一个对象)
例:
var obj = { ...func(); }
如果func()函数返回一个可迭代对象,则上述语句表示将func()返回的对象中的所有属性变成obj的
默认参数:
function point(x = 0,y = 0){ };
箭头函数(lambda):
let foo = (params)=>{
statment;
}
foo(123);
注:lambda表达式中的this为它所在作用域中的this,且不能用call()更改,而一般函数的this需要在运行时确定.
当只有一个参数和一行语句时可简略:param => expression;
双箭头:
let bar = param1 =>param2 => { statment;}
表示外层的箭头函数的返回值是一个函数对象,也就是内层的箭头函数
即执行bar()会得到内层的函数对象
iterator和for...of:
for...of语句可以遍历实现了Iterator接口的对象(可迭代对象)。
例:
let arr = [2,3,4,5,5,6]; for(let e of arr){ // using e; }
数组,伪数组(类数组),set,map实现了Iterator接口,Object没有实现该接口,但可以手动实现。
手动实现Iterator的方式:需要实现next方法,该方法返回此格式的对象{value:dataType,done:boolean},value是元素的值,done表示是否是最后一个元素
let it = Symbol.iterator; Object.prototype[it] = function (){ let next = ()=>{ return {value:....,done:....} } return {next}; }
注:使用for...in也可以迭代Object对象,使用Object.keys(obj)可获取对象的key数组
补充:
for...of和for...in及Object.keys之间的区别:
for...of用来迭代容器中的元素,只有容器才能用(数组,Set,Map等)
for...in用来迭代对象中的属性,只要是对象就能用,但通常不能用来迭代容器中的元素
Object.keys和for...in类似,区别是其不会迭代原型链上的(即继承来的)属性
promise:
是一种异步操作的解决方案,假设op1()和op2()是异步操作(回调函数),op2()需要依赖op1()先执行。则op1和op2不能顺序执行,op2应该位于op1的函数体中,如下例:
setTimeout(function op1(){ // do something... setTimeout(function op2(){ // do something... },2000); },2000);
如果回调层数过多,则会给编程带来很大麻烦。使用promise解决方法如下:
// 定时器1的业务代码
function func1(){ console.log('func1 do something'); }
// 定时器2的业务代码
function func2(){ console.log('func2 do something'); }
function timeoutTask(timeoutFunc,delay){ // 返回一个Promise对象,其初始化参数为一执行体(函数),俩参数分别表示执行成功和失败 return new Promise((success,error)=>{ setTimeout(function (){ timeoutFunc(); success(); // 执行成功 },delay); }) }
// then方法接收两个执行体,分别对应上一步执行成功和失败的回调,then方法可以链式调用
timeoutTask(func1,2000).then(()=>timeoutTask(func2,2000),null);
async和await关键字:
是最常用异步操作的解决方案, async是配合promise使用的。
await后可以跟一个promise对象,只有promise对象resolve了,此表达式才会向下执行
实例:Ajax异步获取用户个人信息,和用户的文件列表,而且获取文件列表的前提是已获取用户信息。
(async function (){ // 使用async修饰函数 let user = await getUser(123); // 只有await关键字后的Promise对象为resolve状态,才会向下执行,await表达式会返回resolve()的参数(即promiseValue) let files = await getFiles(user); // use files data... })(); // 若不手动返回promise对象,async函数会强制返回一个promise对象,原本的返回值会作为promiseValue
await只能用在async块中。
class关键字:
定义类
class Foo{ static msg = "Foo class"; // 静态属性 static getMsg = () => msg; // 静态方法 constructor(){ // 构造函数 this.name = 'foo'; } setName(name){ // 普通函数 this.name = name } }
在类定义中的普通函数会自动原型化,再也不用手动操作原型了;静态属性相当于直接在类对象(非实例对象)本身添加
const foo = new Foo(); foo.setName('bar'); // ok Foo.setName('bar'); // Foo.setName is not a function Foo.msg = 'abc class'; // ok foo.getMsg(); // foo.getMsg is not a function
extends继承:
class Bar extends Foo {
}
浅复制和深复制:
浅复制:一层复制,复制单层元素的值
深复制:多层递归复制
注:只有进行了成员复制才能算拷贝,一般的对象间赋值只是指针的传递,根本不算拷贝。
例:
let a = [1,{name:"zhangshan"}]; let b = a.concat(); // concat是浅复制 b[0] = 2; b[1].name:"lishi"; console.log(a); // [1, {name: "lishi"}]
注:concat是浅复制,分别复制每一个元素的值,对于值类型的元素,复制其值,对于对象,复制其地址。
深复制的实现:
function deepCopy(data){ let ret; if(data instanceof Array){ ret = []; } else if(data instanceof Object){ ret = {}; } else { return data; } for(let i in data){ // i为key或索引,如果是for...of,则i为值 ret[i] = deepCopy(data[i]); } return ret; }
使用JSON也可实现深复制
Map容器:
用来存储K-V的数据结构
获取实例:let map = new Map;
获取元素:get(k); // 不存在则返回undef
设置元素:set(k); // 返回值为map而非元素
判断存在性:has(k);
迭代元素:forEach((v,k)=>{...})
注:Map容器不应以数组形式设置和访问:map['abc'] = 123; (错,尽管语法没问题)
Promise的静态方法:
Promise.any(iterable)
返回:Promise
若容器中有Promise有对象resolve,将其返回。
Promise.race(iterable)
返回:Promise
若容器中有Promise对象resolve或reject,将其返回。
Promise.all(iterable)
返回:Promise
若容器中的所有对象解决(resolve),返回的Promise为resolve,其值为一数组,对应iterable中的Promise value
若容器中有对象拒绝(reject),返回的Promise即为该对象
Promise.allSettled(iterable)
返回:Promise
若容器中的所有对象都解决(resolve)或都拒绝(reject),返回的Promise的值为一数组,对应iterable中的Promise,
否则,返回的Promise为单一值
Promise.resolve(value)
返回:Promise
返回一个状态为resolve,PromiseValue为value的promise对象。
Promise.prototype.catch(onreject)
适用于promise的catch语句,在链式调用then().then()....catch()时,任何一个then返回的Promise对象被设置为reject状态时,中止后续调用,并调用onreject
注:上述方法都是异步方法,会立即返回,不会等待iterable中的Promise解决(resolve)或拒绝(reject)
数组和字符串互转:
1.数组转字符串:Array.prototype.join(sp) // sp是分隔符
2.字符串转数组:String.prototype.split(sp) // sp是分隔符,可以是正则
Array的初始化:
1. new Array(5)
2. new Array('5')
结果:
1. [empty × 5] // length = 5
2. ["5"] // length = 1
微任务:
参考文章:https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth
一个任务就是指计划由标准机制来执行的任何 JavaScript,如程序的初始化、事件触发的回调等。你还可以使用 setTimeout() 或者 setInterval() 来添加任务。
同步执行的JS代码,定时器,事件回调,Ajax回调等是宏任务,使用queueMicrotask()可以添加微任务。
任务队列和微任务队列的区别很简单,但却很重要:
·当执行来自任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。
·在每次迭代开始之后加入到队列中的任务需要在下一次迭代开始之后才会被执行.
·每次当一个任务退出且执行上下文为空的时候,微任务队列中的每一个微任务会依次被执行。
·不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,并在下一个任务开始执行之前且当前事件循环结束之前执行完所有的微任务。
·Promise.prototype.then中的回调在微任务中执行,使用Promise.resolve().then()同样可以添加微任务。
queueMicroTask(callback: void => any); // 将过程callback添加到微任务队列
Promise.resolve().then(callback: any => any); // 将过程callback添加到微任务队列
例:
console.log(1); setTimeout(()=>console.log(2)); queueMicrotask(()=>console.log(3)) console.log(4);
输出结果: 1 4 3 2
上述示例验证了微任务是和宏任务交替进行的
queueMicrotask(()=> { console.log(1) setTimeout(()=>console.log(2)); queueMicrotask(() => console.log(3)); })
输出结果:1 3 2
上述示例验证了微任务结束前,如果又加入了新的微任务,会立即执行
setTimeout(()=> { console.log(1) setTimeout(()=> console.log(2)); queueMicrotask(()=> console.log(3)) queueMicrotask(()=> console.log(4)) });
输出结果:1 3 4 2
上述示例验证了宏任务结束前,如果又加入了新的宏任务和微任务,先执行完微任务,然后执行宏任务