dom
为JS对象,$dom
为jQuery(或Zepto)对象。- 大部分情况下,jQuery内容适用于Zepto。
以纵轴为例。
-
DOM节点
-
节点高度:
-
height+padding:
dom.clientHeight
或
$dom.innerHeight()
-
height+padding+border:
dom.offsetHeight
或
$dom.outerHeight()
-
-
节点内容高度:
dom.scrollHeight
-
节点内滚动距离:
dom.scrollTop
或
$dom.scrollTop()
-
节点顶部相对视口顶部距离:
dom.getBoundingClientRect().top
或
$dom.offset().top + document.documentElement.clientTop - $(window).scrollTop()
dom.getBoundingClientRect().top
:dom的顶部相对视口顶部距离。document.documentElement.clientTop
:<html>
的border-top-width
数值。
-
节点顶部相对文档顶部距离(不包括
文档的border):dom.getBoundingClientRect().top + (document.body.scrollTop || document.documentElement.scrollTop) - document.documentElement.clientTop
或
$dom.offset().top
-
-
文档和视口
-
视口高度:
window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
或
$(window).height()
-
文档内容高度:
document.body.scrollHeight
或
$(document).height()
-
文档滚动高度:
document.body.scrollTop || document.documentElement.scrollTop
或
$(window).scrollTop()
-
Zepto没有
innerHeight
和outerHeight
,改为height
。
-
jQuery
-
节点顶部在视口底部以上
$dom.offset().top <= $(window).scrollTop() + $(window).height()
-
节点底部在视口顶部以下
$dom.offset().top + $dom.outerHeight() >= $(window).scrollTop()
-
节点在视口内
以上
&&
结合。
-
-
原生JS
-
视口高度:
window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
或
$(window).height()
-
节点顶部在视口底部以上
dom.getBoundingClientRect().top <= 视口高度
-
节点底部在视口顶部以下
dom.getBoundingClientRect().bottom >= 0
-
节点在视口内
以上
&&
结合。
IntersectionObserver
判断节点和父级(或视口)相交程度。
-
也可以给底部(或顶部)放置一个标记节点,当这个节点的顶部在容器底部以上(或这个节点的底部在容器顶部以下)时为滚动到底部(或顶部)。
-
DOM节点
-
内容滚动到底部:
dom.offsetHeight + dom.scrollTop >= dom.scrollHeight
-
内容滚动到顶部:
dom.scrollTop === 0
-
-
文档
-
视口高度:
window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
或
$(window).height()
-
文档滚动高度:
document.body.scrollTop || document.documentElement.scrollTop
或
$(window).scrollTop()
-
文档内容高度:
document.body.scrollHeight
或
$(document).height()
-
滚动到底部:
视口高度 + 文档滚动高度 >= 文档内容高度
-
滚动到顶部:
文档滚动高度 === 0
-
-
当变量是jQuery对象时,可用
$
作为开头命名,利于与普通变量区别// e.g. var num = 1; var dom = $('div').get(); var $dom = $('div');
-
选择器
-
$('子','父') === $('父').find('子')
找到所有父级,若子级在此父级后面,则选择。
-
(效率低)$('父 子')
找到所有子级,然后向前找出有父级的,则选择(与CSS相同的查询方式)。
-
-
判断选择器选择到空内容
选择器选取的内容返回数组(空内容返回
[]
),所以if($(...)) {}
永远成立。因此用以下方法:if($(...).length > 0) {} /* 若无则为0*/
if($(...)[0]) {} /* 若无则为undefined*/
-
on
绑定效率(事件代理、事件委托)事件代理:把原本需要绑定的事件委托给祖先元素,让祖先元素担当事件监听的职务。原理:DOM元素的事件冒泡。好处:提高性能,避免
批量绑定。e.g.
$(eventHandler).on(event, selector, func);
- 执行
on
方法的时刻,把所有满足条件的DOM对象安装指定的应用逻辑func,成为eventHandler。有且仅有这些eventHandler绑定成功;之后动态生成的也满足条件的对象不再安装;对已生效的eventHandler处理DOM(如删除类名)也不会使绑定内容失效(除非删除);在eventHandler内动态增删的selector都可以由条件判定是否生效绑定内容。 - 绑定的eventHandler距离selector越近,效率越高。因此虽然把selector都绑定在
$(document)
上能够避免增删节点对事件绑定造成的影响,但效率下降。
- 执行
-
判断是否加载成功,不成功则执行载入本地文件
<script> window.jQuery || document.write('<script src="本地地址"><\/script>'); </script>
-
$.fn.focus()
- 直接调用,光标停留在文本开头或上一次光标停留的地方。
- 先清空文本再
focus
然后再添加文本,光标停留在文本结尾。
-
-
当deferred对象状态改变后,根据相应的状态触发回调
-
状态改变:
.reject()
、.rejectWith()
、.resolve()
、.rejectWith()
。 -
触发行为
- 继续向下传递状态:
.done()
、.fail()
、.always()
。 - 向下传递回调函数的
return
内容:.then()
、.catch()
。
- 继续向下传递状态:
-
改变为Promise对象:
.promise()
。 -
且判断:
jQuery.when()
。
-
-
Promise对象
是Deferred对象
的子集。-
相对于
Deferred对象
,Promise对象
无法改变执行状态:-
屏蔽
与改变执行状态有关的方法:reject
、rejectWith
、resolve
、resolveWith
、notify
、notifyWith
-
开放与改变执行状态无关的方法:
always
、catch
、done
、fail
、progress
、promise
、state
、then
-
-
$.ajax
返回Promise对象
。 -
允许把所有jQuery对象设置为
Promise对象
(如动画方法后接.promise().done(方法)
)。
-
-
- 以点击事件
click
为例。funcAttr
、func0
、funcIe
、func2
为已经定义的方法。
-
原生JS
-
HTML事件处理
<div onclick="funcAttr()"></div>
(不能同个事件监听多个处理程序)-
移除或修改绑定事件:
-
事件处理程序设置为空方法或修改方法:
funcAttr = function () {};
、funcAttr = function () {/* 修改方法*/};
-
移除或修改DOM元素的事件绑定属性:
dom.removeAttribute('onclick');
、dom.setAttribute('onclick', '(function () {/* 修改方法*/} ())');
-
-
-
DOM0级事件处理
本质上,DOM0级事件处理等于HTML事件处理。
dom.onclick = func0;
(不能同个事件监听多个处理程序)-
移除或修改绑定事件:
dom.onclick = null;
、dom.onclick = function () {/* 修改方法*/};
-
-
IE事件处理
dom.attachEvent('onclick', funcIe);
(可监听多个,需参数完全对应才能解绑定;无法解绑匿名函数)-
移除绑定事件:
dom.detachEvent('onclick', funcIe);
-
-
DOM2级事件处理
ie8-不兼容DOM2级事件处理。
dom.addEventListener('click', func2, false);
(可监听多个,需参数完全对应才能解绑定;无法解绑匿名函数)-
移除绑定事件:
dom.removeEventListener('click', func2, false);
-
-
-
jQuery
-
on
(one
类似)-
移除绑定事件:
off
-
-
一系列
on
的事件绑定快捷方法:click
、dblclick
、contextmenu
、keydown
、keyup
、keypress
、mousedown
、mouseup
、mousemove
、mouseenter
、mouseleave
、mouseover
、mouseout
、hover
、blur
、focus
、focusin
、focusout
、select
、change
、submit
、ready
、resize
、scroll
-
由
on
或off
实现的:(废除或不建议)- 绑定:
、bind
、live
delegate
- 解绑:
、unbind
、die
undelegate
- 绑定:
-
-
移除绑定:
-
HTML事件处理和
on+type
,用赋值覆盖解绑。 -
attachEvent/detachEvent
,必须一一对应具体的handle进行解绑。handle是匿名函数无法解绑。ie的handle中
this
指向window
的兼容方式:-
处理handle(传入方法不改变)
function () {funcIe.apply(dom, arguments);}
(无法解绑)。 -
修改
funcIe
-
funcIe.bind(dom)
(需要bind
的polyfill)。 -
使用
window.event
定义thisfunction funcIe(e) { e = e || window.event; //事件处理对象 var _this = e.srcElement || e.target; //触发的DOM /* funcIe代码*/ }
-
-
-
addEventListener/removeEventListener
,必须一一对应具体的handle、布尔值进行解绑。handle是匿名函数无法解绑。 -
jQuery的
on/off
(以及其他绑定解绑方法):- 写具体handle时解绑具体handle。
- 不写handle时解绑对象下某事件的所有方法。
- 还可以对事件添加namespace。
-
-
类型:
-
捕获(capture):
从外层元素到目标元素的过程,
-
冒泡(bubbling):
从目标元素到外层元素的过程。
-
-
DOM标准事件流触发顺序:
-
先捕获。
-
抵达目标
- 进行事件监听器处理(可以设置不再冒泡或不执行浏览器默认行为)。
- 执行浏览器默认行为(如
<a>
跳转)。
-
再冒泡。
ie10-的DOM事件流只有冒泡,没有
捕获。 -
-
WAP端点透bug
- PC端没有
touch事件。 - WAP端有
touchstart
、touchmove
、touchend
、touchcancel
等touch
事件。 - Zepto用
touch
一系列事件封装了tap
事件。
-
点透现象:
使用Zepto的
tap
事件绑定(或原生touchstart
绑定)后,若此元素在触摸事件发生后离开原始位置(CSS或JS),同一位置正好有一个DOM元素绑定了click
事件或<a>
,则会出现“点透”bug(触发底下元素的click
事件)。 -
原因
历史原因:WAP端增加快速双击缩放和恢复功能。由于当用户一次点击屏幕之后,浏览器并不能立刻判断用户是单击还是双击操作。因此,就等待300ms左右,以判断用户是否再次点击了屏幕。
WAP端触摸事件顺序:
touchstart
->touchmove
->touchend
->click
,触摸一瞬间触发touchstart
,触摸结束后触发Zepto封装的tap
事件,触摸结束后300ms左右触发click
事件。 -
解决方法:
-
使用fastclick.js消除
click
的延时(最佳方式)用
click
代替全部tap
事件,这样PC端和WAP端都可以一致用click
事件且不会出现WAP端点透bug。fastclick.js原理:在检测到
touchend
事件时,通过DOM自定义事件立即触发一个模拟click
事件,并阻止浏览器在300ms之后真正触发的click
事件。 -
禁用缩放,设置
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
。 -
布局viewport小于等于可视viewport则浏览器禁用双击缩放,设置
<meta name="viewport" content="width=device-width, initial-scale=1.0">
。 -
CSS属性
touch-action: manipulation;
仅允许在元素上进行触屏操作的平移、缩放,忽略双击。 -
使用缓动动画,过度300ms延迟。
-
中间增加一层接受这个点透事件,然后去除此层。
-
模拟点击事件代替
click
。
-
- PC端没有
-
WAP端使用
:active
-
Android系统的浏览器大部分直接使用CSS伪类即可。
-
iOS系统的浏览器要添加:
document.body.addEventListener('touchstart', function () {}, true);
。 -
JS添加类的方法模拟:<style> .d:active, .d.active { } </style> <script type="text/javascript"> var selector = '.a,.b .c,.d'; /* 选择器字符串*/ $(document.body).on('touchstart', selector, function () { $(this).addClass('active'); }).on('touchmove touchend touchcancel', selector, function () { $(this).removeClass('active'); }); </script>
- 添加
document.body.addEventListener('touchstart', function () {}, true);
即可满足大部分浏览器使用伪类。
-
-
WAP端播放
大部分
<video>
、<audio>
同理。-
播放事件
参考MDN:媒体相关事件。
-
正在播放
timeupdate
事件:当媒体的currentTime
属性改变(拉动进度或播放中)。 -
开始播放
play
事件:初次播放、暂停后恢复。playing
事件:初次播放、暂停后恢复、结束后自动开始(loop
属性模式)。
-
暂停播放
pause
事件 -
完成一轮播放
因为
loop
属性模式无法触发ended
事件,又timeupdate
事件触发时间不确定、无法和媒体.duration
取等判断成功,故无法在loop
属性模式中判定。(非
loop
属性模式下的)ended
事件(伴随pause
事件)。
-
-
自动播放
-
JS代码模拟
因为兼容性,故不使用
autoplay
属性模式,用JS代码模拟。<video id="j-video"> 您的浏览器不支持 video 标签 </video> <script> var video = document.getElementById('j-video') document.addEventListener('DOMContentLoaded', function () { video.setAttribute('src', '视频地址') video.play() }, false) // 根据需求选用 window.ontouchstart = function () { video.play() window.ontouchstart = null } </script>
-
autoplay
属性模式
-
-
循环播放
-
JS代码模拟
ended
事件中触发媒体.play()
。 -
loop
属性模式
-
-
内嵌播放
因为全屏播放完
<video>
会白屏,故可以在父级添加封面图片背景。<video id="j-video" webkit-playsinline playsinline x5-video-player-type="h5"> 您的浏览器不支持 video 标签 </video> <script> var video = document.getElementById('j-video') // QQ浏览器去除 x5-video-player-type 属性 if (/QQBrowser/.test(window.navigator.userAgent)) { video.removeAttribute('x5-video-player-type') } </script>
-
播放控件(内嵌播放)
-
controls
属性模式 -
JS代码模拟
因为无法兼容至所有浏览器,故不推荐。
<style> .video-wrap { position: relative; width: 320px; height: 500px; background: red; } .video-wrap video { max-width: 100%; max-height: 100%; } .icon { position: absolute; top: 0; width: 100%; height: 50%; background: yellow; } </style> <div id="j-video-wrap" class="video-wrap"> <video id="j-video" webkit-playsinline playsinline x5-video-player-type="h5" style="width: 1px;height: 1px;"> 您的浏览器不支持 video 标签 </video> <div id="j-start-icon" class="icon"> 封面图(初始点击) </div> <div id="j-play-icon" class="icon" style="pointer-events: none;display: none;"> 播放按钮 </div> </div> <script> var video = document.getElementById('j-video') var start = document.getElementById('j-start-icon') var play = document.getElementById('j-play-icon') if (/QQBrowser/.test(window.navigator.userAgent)) { // QQ浏览器去除 x5-video-player-type 属性 video.removeAttribute('x5-video-player-type') } function touchstartFunc () { start.style.display = 'none' video.setAttribute('src', '视频地址') video.removeAttribute('style') video.play() setTimeout(function () { /* 点击播放 */ document.getElementById('j-video-wrap').addEventListener('touchstart', function () { if (video.paused) { // 播放 video.play() play.style.display = 'none' } else { // 暂停 video.pause() play.style.display = 'block' } }, false) /* 视频播放结束 */ video.addEventListener('ended', function () { // video.play() play.style.display = 'block' }, false) start.removeEventListener('touchstart', touchstartFunc, false) }, 0) } start.addEventListener('touchstart', touchstartFunc, false) </script>
-
-
无法操作客户端自定义播放控件:
-
一般手机有两种播放方式:
-
全屏模式
-
内联模式(浏览器支持其中一种):
webkit-playsinline playsinline
内联模式。x5-video-player-type="h5"
在底部的全屏内联模式(同层播放)。
-
-
无法操作全屏模式
-
无法改变全屏播放方向以及控件内容
DOM属性
x5-video-orientation="landscape或portraint"
、x5-video-player-fullscreen="true"
无法解决。 -
有些浏览器在全屏模式中,不触发任何video事件
无法自定义loading。
-
-
关闭
controls
模式,部分浏览器依然出现播放按钮 -
无法控制内联模式类型(内联模式/在底部的全屏内联模式)
-
-
-
构造函数
-
普通版:
var OneConstructor = function () { /* 私有的内容*/ var _para = {a: '私有的变量_para'}, _func = function () { console.log('私有的业务逻辑_func', _para); }, _bindEvent = function () { /* 绑定事件*/ }, _init = function () { /* 初始化*/ _func(); _bindEvent(); }; _init(); for (var _arr = [], _i = 0; _i < arguments.length; _i++) { _arr.push(arguments[_i]); } /* 公开的内容*/ this.para = _arr; //形参 this.para_1 = {b: '公开的变量para_1(每个实例不共享)'}; this.func_1 = function () { console.log('公开的业务逻辑func_1(每个实例不共享)'); }; };
-
修改构造函数的原型对象(所有实例都共享):
var OneConstructor = (function () { /* 私有的内容*/ var _para = {a: '私有的变量_para'}, _func = function () { console.log('私有的业务逻辑_func', _para); }, _bindEvent = function () { /* 绑定事件*/ }, _init = function () { /* 初始化*/ _func(); _bindEvent(); }; function Constructor() { _init(); for (var _arr = [], _i = 0; _i < arguments.length; _i++) { _arr.push(arguments[_i]); } /* 公开的内容*/ this.para = _arr; //形参 this.para_1 = {b: '公开的变量para_1(每个实例不共享)'}; this.func_1 = function () { console.log('公开的业务逻辑func_1(每个实例不共享)'); }; } /* 构造函数的原型对象上,每个实例共享*/ Constructor.prototype = { para_2: {c: '公开的变量para_2(每个实例共享)'}, func_2: function () { console.log('公开的业务逻辑func_2(每个实例共享)'); } }; /* 原型对象添加constructor属性*/ if (typeof Object.defineProperty === 'function') { /* 使属性:不可以改变描述符、不可以删除、不可以枚举、不可以被赋值运算符改变*/ Object.defineProperty(Constructor.prototype, 'constructor', { value: Constructor }); } else { Constructor.prototype.constructor = Constructor; } return Constructor; }());
-
-
模块模式(单例模式+私有变量和特权方法)
var singletonObj = (function () { /* 私有变量和私有方法,无法直接访问,只能由return的对象字面量访问*/ var _para = {a: '私有变量'}, _func = function () { console.log('私有方法'); }; /* 单例模式,可以访问私有内容*/ return { get: function () { /* 特权方法*/ _func(); return _para; }, set: function (para) { /* 特权方法*/ _func(); _para = para; }, para: {b: '公开对象'}, func: function () { console.log('公开方法'); } } }());
单例模式:
var singletonObj = { para: {}, func: function () {} };
-
声明
因为JS的预编译。
-
变量声明(
var
)无论语句在何处,无论是否会真正执行到,所有的
var
语句的声明都提升到作用域(函数内部或全局)顶部执行(hoisting),但具体赋值不会被提前。e.g.
(function () { console.log(a); //undefined var a = 2; console.log(a); //2 })(); //等价于: (function () { var a; console.log(a); //undefined a = 2; console.log(a); //2 })();
- 声明变量是所在上下文环境的不可配置属性;非声明变量是可配置的。
- 相同作用域中,对同一个变量进行多次声明,则忽略第一次之后的声明(会执行变量赋值)。
建议:总是把所有变量声明都放在函数顶部,而不是散落在各个角落。
-
函数声明
- 也会被JS引擎提前到作用域顶部声明,因此代码中函数的调用可以出现在函数声明之前。
- 函数声明不应当出现在
语句块内(如条件语句),语句块的函数也会提前声明,导致语义不清容易出错。
-
函数表达式(Function expressions)声明
必须先声明:
var a = function(){...};
才可以使用,声明会被提前,但赋值不会被提前。 -
同名的变量声明和函数声明(不是函数表达式)
同时声明的情况下(顺序不影响结果):
- 变量仅声明不赋值:被赋值为函数。
- 变量赋值:被赋值为变量。
e.g.
var a1 = 1; function a1() {} console.log(a1); //1 function b2() {} var b2 = 1; console.log(b2); //1 var c3; function c3() {} console.log(c3); //function function d4() {} var d4; console.log(d4); //function
建议:先声明再使用;把函数声明紧接着放在变量声明之后。
-
-
严格模式
use strict
可用于全局,也可以用于局部(函数内)。
- 不推荐在全局作用域中使用,因为当有JS文件合并时,一个文件的全局严格模式会导致整个文件都是严格模式。
- 可以用
(function(){'use strict';/* 执行内容*/}());
匿名函数方式使用严格模式。
-
全等
===
(不全等!==
)与等号==
(不等!=
)区别- 当比较的两个值的类型不同时,
==
和!=
都会强制类型转换,再进行转换后值的比较。 - 用
===
和!==
则不会转换,若类型不同则直接返回false
(switch
语句比较值是全等模式比较)。
建议:都用
===
或!==
进行比较。 - 当比较的两个值的类型不同时,
-
三元运算符应当仅仅用在条件赋值语句中,而不要作为
if
语句的替代:var a = condition ? '1' : '2';
condition ? func1() : func2();
-
命名
-
变量命名的前缀应当是名词,函数命名的前缀应当是动词。
-
约定函数名:
can
、has
、is
开头的返回值是布尔型。get
开头的返回是非布尔型。set
开头的执行保存动作。
-
常量用大写字母和下划线分割,如
MAX_COUNT
。 -
构造函数用大驼峰命名法(Pascal Case),首字母大写(以非动词开头),单词首字母大写:
var a = new Person(); /* 构造函数*/
var b = getPerson(); /* 普通函数*/
-
不要用多行的字符串写法
e.g.
/* 不提倡的多行写法*/ var a = 'abc\ def'; /* 一般写法*/ var b = 'abc' + 'def';
-
对象的属性、方法,与变量、方法命名规则相同。
-
若属性、变量、方法在表示其是私有的,可在开头加一个下划线
_
作为区分。
-
-
使用字面量代替构造函数(普通函数)的数据创建方式
好处:
- 代码更少。
- 不需要构造函数的“作用域解析”(scope resolution),提高性能。
- 构造函数可以接收参数,返回的实例不可控、易混淆;字面量简单不出错。
-
对象
e.g.
/* 不提倡的构造函数写法*/ var a1 = new Object(); a.attr1 = '...'; var a2 = new Object({attr1: '...'}); /* 提倡的字面量写法*/ var b = {attr1: '...'};
-
数组
e.g.
/* 不提倡的构造函数写法*/ var arr1 = new Array('a', 'b'); /* 提倡的字面量写法*/ var arr2 = ['a', 'b'];
-
字符串
e.g.
/* 不提倡的构造函数写法*/ var str1 = new String('a'); /* 提倡的字面量写法*/ var str2 = 'a';
- 其他数据类型
-
长字符串拼接使用
Array.prototype.join()
,而不使用+
-
.join()
性能好(推荐方式):e.g.
var arr = [], i; for (i = 0; i < 100; i++) { arr[i] = '字符串' + i + '字符串'; } return arr.join('');
-
+
性能差(不推荐方式):e.g.
var text = '', i; for (i = 0; i < 100; i++) { text = text + '字符串' + i + '字符串'; } return text;
-
-
注释规范
-
单行注释:
//
后不空格-
使用场景:
- 代码上方独占一行,缩进与备注内容一致,注释前必须空一行。
- 代码尾部(至少一个缩进)。
- 被注释的大段代码。
-
-
多行注释:
/*
和*
后空一格-
使用场景:
-
代码上方独占多行,缩进与备注内容一致,注释前必须空一行。
-
作用:
- 难以理解的代码。
- 可能被误认为错误的代码。
- 浏览器特性hack。
-
-
-
函数注释规范(使用部分JSDoc)
/** * 方法描述 * @constructor - (是否构造函数) * @param {Number} param1 - 参数描述,限制值为:1 描述|2 描述|3 描述 * @param {Object|String|Boolean|Function|Array} param2 - 参数描述 * @param {*} [param3] - 可选参数 * @param [param4 = 'default'] - 可选参数拥有默认值 * @param {...Number} param5 - 可重复使用参数 * @returns {Object|Undefined} result - 参数描述 */ function func(param1, param2, param3, param4, param5) { return result; }
-
-
JS编程风格总结(programming style)
- 表示区块起首的大括号,不要另起一行。
- 调用函数的时候,函数名与左括号之间没有空格。
- 所有其他语法元素与左括号之间,都有一个空格。
- 不要省略句末的分号。
- 不要使用
with
语句。 - 不要使用“相等”(
==
)运算符,只使用“严格相等”(===
)运算符(同理仅使用!==
,不使用!=
)。 - 不要将不同目的的语句,合并成一行。
- 所有变量声明都放在函数的头部。
- 所有函数都在使用之前定义。
- 避免使用全局变量;如果不得不使用,用大写字母表示变量名。
- 不要使用
new
命令,改用Object.create()
命令。 - 构造函数的函数名,采用首字母大写;其他函数名,一律首字母小写。
- 不要使用自增(
++
)和自减(--
)运算符,用+= 1
和-= 1
代替。 - 不省略大括号。
-
JS编码规范
绝大部分同意fex-team:tyleguide。
可以设置为IDE的Reformat Code的排版样式。
-
用户体验
-
平稳退化:当浏览器不支持或禁用了JS功能后,访问者也能完成最基本的内容访问。
-
为JS代码预留出退路(
<a>
添加属性链接,用JS事件绑定去拦截浏览器默认行为)<a href="真实地址" class="j-func">...</a>
-
伪协议javascript:
<a href="javascript: func();">...</a>
-
内嵌事件处理函数<a href="#" onclick="func();return false;">...</a>
-
-
渐进增强:先完成基本通用功能,再追加额外功能。
-
向后兼容:确保老版本浏览器基本可用,使之虽不能支持某些功能,但仍能基本访问。
- 能力检测:
if(func){func();}
(最适当方式)。 - 怪癖检测:
try-catch
。 - 浏览器嗅探技术(用户代理
navigator.userAgent
检测)。
- 能力检测:
-
资源分离:把样式表、脚本分离出HTML。
- 使用外部资源。
- 不在HTML内嵌事件处理函数。
- 对只为DOM增添的内容,转移到外部资源中动态创建。
-
性能优化从URL输入之后就开始考虑。
避免
微优化:- 即时编译(Just In Time Compile):JS引擎会在JS运行过程中逐渐重新编译代码,使代码运行更快。
- 微优化(micro-optimizations):尝试写出认为会让浏览器稍微更快速运行的代码。
-
-
UI层的松耦合
-
不要
用JS修改CSS样式,JS仅修改class(任何时刻,CSS中的样式都可以修改,而不必更新JS)。特例:根据页面重新定位,可以用JS设定位置(如
top
、left
等)。 -
将HTML从JS中抽离,避免增加跟踪文本和结构性问题的复杂度。可以使用模板引擎,如handlebars.js。
-
-
避免使用全局变量
任何来自函数外的数据都应当以参数形式传进函数:将函数与其外部环境隔离开。
-
单全局变量
唯一全局对象是独一无二的(不与内置API冲突),并将所有功能代码都挂载到这个全局对象上,因此每个可能的全局变量都成为这个唯一全局对象的属性(或方法)。
-
零全局变量(立即调用的函数表达式)
隔离代码。
(function (win) { 'use strict'; //严格模式可以避免创建全局变量 var doc = win.document; /* 代码*/ }(window));
-
-
事件处理
-
把事件处理(与用户行为相关的代码)与应用逻辑(与应用相关的功能性代码)隔离开。
-
不要分发事件:
让事件处理函数成为接触到
event
对象的唯一函数,在event进入应用逻辑前完成用户相关操作(包括阻止默认事件或阻止冒泡等)。
-
JS的自定义事件建议直接使用jQuery:
$('选择器').on('自定义事件', function () {}); $('选择器').trigger('自定义事件');
-
-
将配置数据从代码中分离
配置数据:URL、展示内容、重复的值、设置、任何可能发生变更的值。
-
代码调试方式
console
(alert
)、debugger
、DevTool的Sources断点(配合SourceMap)。
都是用来限制某个函数在一定时间内执行次数的技巧。
-
防抖(debounce)
一个函数被调用后不执行,在间隔时间内没有再次被调用才执行(或一调用就执行,在间隔时间内没有再次被调用才可以进行下一次执行);若在间隔时间内再次被调用,则刷新等待时间并且继续等待间隔时间结束后才执行。
-
节流(throttle)
一个函数无法在间隔时间内连续执行,当上一次函数执行后过了间隔时间后才能进行下一次该函数的调用。
可用
requestAnimationFrame(func)
代替间隔时间设置为一帧的节流函数:throttle(func, 16)
。
立即调用的函数表达式。
-
写法:
function
关键字当作一个函数声明的开始,函数声明的后面不能跟圆括号;- 将函数声明包含在圆括号中,表示函数表达式,函数表达式的后面可以跟圆括号,表示执行此函数。
/* 推荐方式*/ (function () {}()); (function () {})(); /* 不推荐方式*/ [function () {}()]; ~function () {}(); !function () {}(); +function () {}(); -function () {}(); delete function () {}(); typeof function () {}(); void function () {}(); new function () {}(); new function () {}; var f = function () {}(); 1, function () {}(); 1 ^ function () {}(); 1 > function () {}();
-
传值进自执行匿名函数可以避免闭包导致无法记录变量值的问题:
e.g.
for (var i = 0; i < 3; i++) { //匿名函数 (function (para) { setTimeout(function () { console.log(para); //结果是传入进匿名函数的形参 }, 0); }(i)); } for (var i = 0; i < 3; i++) { //不用匿名函数 setTimeout(function () { console.log(i); //每个结果都是固定的最后一个值(闭包作用) }, 0); }
- 相对于Native APP的高成本、原生体验,Hybrid APP具有低成本、高效率、跨平台等特性,能够不依赖Native的发包更新。
- Hybrid底层依赖Native提供的容器(WebView),上层使用HTML、CSS、JS进行业务开发。
-
Native提供给Hybrid宿主环境
-
互相调用
- Native提供功能:NativeUI组件、Header组件、消息类组件、通讯录、系统设备信息读取接口、H5与Native的互相跳转、支付、分享等。
- Native调用前端JS方法;前端通过调用JS方法或跳转请求调用Native提供的接口。
-
资源访问机制
-
以
file
方式访问Native内部资源。 -
以
url
方式访问线上资源。 -
增量替换机制(不必随发包更新)
- Native本地下载、解压线上的打包资源,再替换旧资源。
manifest。
-
URL限定,跨域问题的解决方案。
-
-
身份验证机制
客户端注入方式:JS伪协议方式
javascript: 代码
。Native创建WebView时,根据客户端登录情况注入跟登录有关的cookie(session_id)或token。
-
Hybrid开发测试
- 提供切换成线上资源请求方式的功能,用代理工具代理成本地资源。
- Chrome的Remote devices调试。
-
-
Hybrid的前端处理
-
WebView环境兼容性:
-
CSS、HTML
除样式适配外。
- 添加厂商前缀(如
-webkit-
)。 - 布局有问题的机型额外调试。
<video>
、<audio>
、<iframe>
调试。
- 添加厂商前缀(如
-
JS
兼容性判断(能力检测等)。
-
-
与Native配合方式:
都是以字符串的形式交互。
-
桥协议:Native注入全局方法至WebView的
window
,前端调用则触发Native行为。客户端注入方式:JS伪协议方式
javascript: 代码
。 -
自定义Scheme:拦截跳转请求(
document.location.href
或<a>
),触发Native行为。客户端可以捕获、拦截任何行为(如
console
、alert
)。相对于注入全局变量,拦截方式可以隐藏具体JS业务代码,且不会被重载,方便针对不可控的环境。
-
前端提供Native调用的全局回调函数。
判断多个回调函数顺序:带id触发Native行为,Native调用回调函数时携带之前的id。
-
-
根据前端的错误处理机制统计用户在Hybrid遇到的bug。
-
-
var a = b = c = 1;/* b、c没有var的声明*/
第二个以后的变量表示没有
var的赋值。等价于:
var a = 1;b = 1;c = 1;
。 -
var a = a || {};
- 声明提前
var a;
。 - 右侧的表达式
a || {}
先执行:根据规则先判断a的值是否为真,如果a为真,则返回a;如果a不为真,则返回{}。 - 最后再将结果赋值给
a
。
等价于:
/* 不是形参情况*/ var a; if (a === 0 || a === "" || a === false || a === null || a === undefined) { a = {}; } /* 形参情况*/ function func(b) { if (b === 0 || b === "" || b === false || b === null || b === undefined) { b = {}; } }
var a = b || {};
与if (c) {}
会因为b或c没有定义而报错,可以用typeof
来使代码健壮:var a = typeof b !== 'undefined' && b !== null ? b : {};
if (typeof c !== 'undefined' && c !== null) {}
- 声明提前
-
if
、while
之类的判断语句中用赋值操作(大部分是误用)赋值的内容Boolean后为假会导致条件判断为假:
if(a = false){/* 不执行*/}
。判断语句内只判断整体返回值是
true
还是false
,与里面执行内容无关(尽管对其语法有所限制)。 -
if(var a = 1, b = 2, c = 3, false){/* 不执行*/}
逗号操作符
,
对每个操作对象求值(从左至右),然后返回最后一个操作对象的值。var
语句中的逗号不是逗号操作符,因为它不存在于一个表达式中。尽管从实际效果来看,那个逗号同逗号运算符的表现很相似。但它是var
语句中的一个特殊符号,用于把多个变量声明结合成一个。 -
var a = [10, 20, 30, 40][1, 2, 3];/* 40*/
[10, 20, 30, 40]
被解析为数组;[1, 2, 3]
被解析为属性调用,逗号操作符取最后一个值为结果。
因此结果为数组
[10, 20, 30, 40]
的[3]
属性值:40
。 -
{a: 'b'} + 1;/* 1*/
大括号视为代码块,没有返回值。需要给大括号加上小括号,表明为一个值:
({a: 'b'}) + 1;/* [object Object]1*/
。 -
浮点数的计算
浮点数值计算会产生舍入误差,因此永远不要用条件语句判断某个特定浮点数值,也不要用JS进行复杂的计算。
避免浮点数运算误差函数:用整数进行小数的四则运算。
-
判断DOM是否支持某属性
若要判定一个属性是否被DOM所支持,新建一个DOM来判断:
if('属性' in document.createElement('某标签')){...}
。在DOM中随意添加一个属性,
此属性 in 此DOM
永远为真,不可以判断是否此DOM存在此属性(或方法)。 -
eval
中直接执行function
声明无效,必须用引号把function
声明包裹起来才有效(尽量不用eval
)eval(function a() {}); //返回function a() {},但没有声明 eval('function b() {}'); //返回undefined,声明成功
if()
中的代码对于function
的声明就是用eval
带入方法做参数,因此虽然返回true,但方法没有被声明。setTimeout
与setInterval
中第一个参数若使用字符串,也是使用eval
把字符串转化为代码。
-
获取数组中最大最小值:
Math.min.apply(null, [1, 2, 3]);/* 1*/
、Math.max.apply(null, [1, 2, 3]);/* 3*/
。 -
设置CSS属性
使用
cssText
返回CSS的实际文本(ie8-返回时不包含最后一个;
)。- 添加:
dom.style.cssText += '; 样式: 属性; 样式: 属性'
- 替换:
dom.style.cssText = '样式: 属性; 样式: 属性'
- 添加:
-
Object.prototype.toString.call(值);
(或apply
)-
没有跨帧问题。
-
放入内置对象,返回
'[object 构造函数的名称]'
的字符串特例:自定义类型返回
'[object Object]'
,undefined
、null
返回对应名字。- 自定义类型实例 ->
'[object Object]'
undefined
或 不填 ->'[object Undefined]'
null
->'[object Null]'
{}
->'[object Object]'
[]
->'[object Array]'
function(){}
(包括匿名函数) ->'[object Function]'
- Number实例 ->
'[object Number]'
- String实例 ->
'[object String]'
- Boolean实例 ->
'[object Boolean]'
- Date实例 ->
'[object Date]'
- RegExp实例 ->
'[object RegExp]'
- Error实例 ->
'[object Error]'
- Map实例 ->
'[object Map]'
- Audio实例 ->
'[object HTMLAudioElement]'
- Image实例 ->
'[object HTMLImageElement]'
window
->'[object Window]'
document
->'[object HTMLDocument]'
arguments
->'[object Arguments]'
Math
->'[object Math]'
JSON
->'[object JSON]'
-
对于没有声明的变量,直接使用此行代码会报引用不存在变量的错误,因此需要:
if (typeof 变量 !== 'undefined' && Object.prototype.toString.call(变量) === '[object 某]') {}
- 自定义类型实例 ->
-
-
typeof 值
-
没有跨帧问题。
-
返回一个表示值类型的字符串。
- 字符型 ->
'string'
- 布尔型 ->
'boolean'
- 数值型 ->
'number'
- Symbol型 ->
'symbol'
undefined
->'undefined'
- 函数 ->
'function'
- 引用对象型 ->
'object'
null
->'object'
- 因为
typeof null
返回'object'
,因此typeof不能判断是否是引用数据类型。 - ie8-的DOM节点的方法返回不是
function,而是object
,因此只能用方法名 in DOM
检测DOM是否拥有某方法。
- 字符型 ->
-
-
对象 instanceof 构造函数
-
不能跨帧(
<iframe>
、window.open()
的新窗口)。/* 跨帧:浏览器的帧(frame)里的对象传入到另一个帧中,两个帧都定义了相同的构造函数*/ A实例 instanceof A构造函数; //true A实例 instanceof B构造函数; //false
-
判断
构造函数.prototype
是否存在于对象的原型链上。 -
不仅检测对象本身,还检测至原型链。
e.g.
new Number() instanceof Object; /* true*/
。 -
检测自定义类型的唯一方法。
-
-
属性 in 对象
- 仅判断属性是否存在检测的对象上,不读取属性值。
- 检测至原型链。
对象.hasOwnProperty(属性)
仅检查在当前实例对象,不检测其原型链。- ie8-的DOM对象并非继承自Object对象,因此没有hasOwnProperty方法。
浏览器同源策略(协议、域名、端口,必须完全相同才能够在脚本中发起请求)限制:
不能通过AJAX去请求不同源中的内容(低版本浏览器不会发起跨域请求,直接拒绝)。
现代浏览器会进行CORS处理,发起请求并与服务器协商(特例:有些浏览器不允许从HTTPS跨域访问HTTP,这些浏览器在请求还未发出的时候就会拦截请求)。
不同源的文档间(文档与
<iframe>
、文档与window.open()
的新窗口)不能进行JS交互操作。
- 可以获取window对象,但无法进一步获取相应的属性、方法。
- 无法获取DOM、
cookie
、Web Storage
、IndexDB
。
-
jsonp(服务端需要设置)
只支持GET请求。
网页通过添加一个
<script>
,向服务器发起文档请求(不受同源政策限制);服务器收到请求后,将数据放在一个指定名字的回调函数里传回网页直接执行。jQuery在
ajax
方法中封装了jsonp
功能:$.ajax({ url: '接口地址', dataType: 'jsonp', jsonp: '与服务端约定的支持jsonp方法', //前端唯一需要额外添加的内容 success: function(data) { //data为跨域请求获得的服务端返回数据 } })
-
document.domain
相同则可以文档间互相操作把不同文档的
document.domain
设置为一致的值(仅允许设置为上一级域),即可双向通信、互相操作(cookie
可以直接操作;localStorage
、IndexDB
只能通过postMessage
通信)。-
与
<iframe>
通信://父窗口调用iframe的window对象 var newIframe = document.getElementById('new-iframe').contentWindow; //或:window.frames[0] //iframe调用父窗口的window对象 var father = parent;
-
与
window.open()
的新窗口通信://父窗口调用新打开窗口的window对象 var newWin = window.open('某URL'); //新打开窗口调用父窗口的window对象 var father = window.opener;
-
-
postMessage
文档间通信ie8、ie9仅支持与
<iframe>
,ie10+支持与<iframe>
、window.open()
的新窗口。不实行同源政策。//发送方 目标window对象.postMessage(message, '目标源地址或*'); //监听的文档 window.addEventListener('message', function(e) { console.log(e); },false);
-
其他方式
-
父窗口改变
<iframe>
的hash,<iframe>
通过监听hash变化的hashchange
事件获取父窗口信息ie8+支持。若只改变hash值,页面不会重新刷新。
//父窗口改变iframe的hash值 document.getElementById('new-iframe').src = '除了hash值,url不变(父级与iframe不需要同源)'; //iframe窗口监听hash变化,以hash变化当做信息的传递 window.onhashchange = function(){ var message = window.location.hash; //... };
-
通过监听
window.name
传递信息同会话(tab窗口)前后跳转的页面都可以读取、设置同一个
window.name
值。- 父窗口打开一个子窗口,载入一个不同源的网页,该网页将信息写入
window.name
属性。 - 子窗口跳回一个与主窗口同域的网址(文档间访问
window.name
遵循同源策略)。 - 主窗口可以读取子窗口的
window.name
值作为信息的传递。
- 父窗口打开一个子窗口,载入一个不同源的网页,该网页将信息写入
-
图片地址
只能发送GET请求,无法访问服务器的响应文本。只能浏览器向服务器单向通信。
常用来统计。
-
- 因为HTTP请求都会携带cookie,因此cookie最好仅用于服务端判定状态。
- 浏览器数据存储方式:cookie、Web Storage、IndexedDB、Web SQL、Manifest、Service Workers。
-
Web Storage(
localStorage
、sessionStorage
)-
客户端保存,不参与服务器通信。
-
对象形式。
-
ie8+支持(ie及FF需在web服务器里运行)。
ie6/7可以用它们独有的
UserData
代替使用。 -
单个对象数据大小5M+。
-
拥有方便的api
调用
localStorage
、sessionStorage
对象会为每个源(每个tab)创建独立的Storage
对象,每个对象都拥有:setItem
、getItem
、removeItem
、clear
、key
方法,length
属性。 -
区别
-
localStorage
- 同源共享。
- 持久化本地存储(关闭浏览器后继续保存;除非被清除,否则永久保存)。
- 应用场景:所有需要长期本地存储的数据。
-
sessionStorage
- 同源且同会话(tab窗口)共享。
- 会话级别存储。跳转页面为同源后仍旧有效(不同tab不共通),关闭浏览器后被清除(重新加载或关闭后恢复,任然存在)。
- 应用场景:需要拆分成多个子页面分别存储的数据。
-
-
-
cookie:
-
客户端保存,始终在HTTP请求中携带(同源同路径),明文传递,服务端接收、操作客户端cookie。
-
字符串形式:
名1=值1[; 名2=值2]
。不能包含任何,
、;
、encodeURIComponent
、decodeURIComponent
)。 -
所有浏览器都支持。
-
单域名内,cookie保存的数据不超过4k,数量(最少)20个。
-
源生的cookie接口不友好,需要程序员封装操作cookie。
-
JS的
document.cookie
:-
新建或更新cookie等同于服务端
Set-Cookie
响应头:一次设置一条cookie的名=值[; expires=绝对时间][; max-age=相对时间][; domain=域名][; path=路径][; secure]
。Set-Cookie
额外可以设置[; HttpOnly]
属性。 -
读取cookie等同于客户端
Cookie
请求头:展示所有cookie的名1=值1[; 名2=值2]
(无法查看其他信息)。
-
-
-
同源且同路径共享。
-
默认(存储在内存)关闭浏览器后失效,设置失效时间(存储在硬盘)则到期后失效。
-
应用场景:服务端确定请求是否来自于同一个客户端(cookie与服务端session配合),以确认、保持用户状态。
僵尸cookie(zombie cookie)是指那些删不掉的,删掉会自动重建的cookie。僵尸cookie是依赖于其他的本地存储方法,如flash的share object、HTML5的local storages等,当用户删除cookie后,自动从其他本地存储里读取出cookie的备份,并重新种植。
-
隐身模式策略:存储API仍然可用,并且看起来功能齐全,只是无法真正储存(如分配储存空间为0)。
- 当JS出现错误时,JS引擎会根据JS调用栈逐级寻找对应的
catch
,如果没有找到相应的catch handler或catch handler本身又有error或又抛出新的error,就会把这个error交给浏览器,浏览器会用各自不同的方式显示错误信息,可以用window.onerror
进行自定义操作。- 在某个JS block(
<script>
或try-catch
的try
语句块)内,第一个错误触发后,当前JS block后面的代码会被自动忽略,不再执行,其他的JS block内代码不被影响。
-
原生错误类型
来自MDN:Error。
-
自定义错误
function MyError(message) { Error.call(this); this.message = message || '默认信息'; this.name = 'MyError'; } MyError.prototype = Object.create(Error.prototype, {constructor: {value: 'MyError'}});
-
手动抛出错误
throw 'Error'; //抛出字符串 throw 100; //抛出数值 throw true; //抛出布尔值 throw {message: 'An Error'}; //抛出对象 throw new Error('An Error'); //抛出Error类型错误
-
处理代码中抛出的错误
-
try-catch-finally
try
内的作用域不为内部异步操作保留:try {setTimeout(function () {err}, 0)} catch (e) {}
,catch
不会捕获异步操作中的错误。- 必须
try-catch
或try-finally
或try-catch-finally
同时出现。 - 如果有
catch
,一旦try
中抛出错误以后就先执行catch
中的代码,然后执行finally
中的代码。 - 如果没有
catch
,try
中的代码抛出错误后,先执行finally
中的语句,然后将try
中抛出的错误往上抛。 - 如果
try
中代码是以return
、continue
或break
终止的,必须先执行完finally
中的语句后再执行相应的try
中的返回语句。 - 在
catch
中接收的错误,不会再向上提交给浏览器。
- 必须
-
window.onerror
- 没有经过
try-catch
处理的错误都会触发window
的error
事件。 - 用方法赋值给
window.onerror
后,但凡这个window中有JS错误出现,则会调用此方法。 window.onerror
方法会传入多个参数:message
、fileName
、lineNumber
、columnNumber
、errorObject
。- 若方法返回
true
,浏览器不再显示错误信息;若返回false
,浏览器还是会提示错误信息。
/** * window错误处理 * @param {String} msg - 错误信息提示 * @param {String} url - 错误出现url * @param {Number} line - 错误出现行数字 * @param {Number} column - 错误出现列数字 * @param {Object} error - 错误对象 * @returns {Boolean} - true:不显示错误信息|false:显示 */ window.onerror = function (msg, url, line, column, error) { /* code*/ return true; //浏览器不再显示错误信息 };
- 没有经过
-
图像的
onerror
事件- 只要图像的src属性中的URL不能返回能被识别的图像格式,就会触发图像的
error
事件。 - 错误不会提交到
window.onerror。 Image
实例或<img>
的error
事件没有任何参数。
-
<img>
的error
事件<img src="错误地址" onerror="func();">
-
Image
实例的属性var img = new Image(); //或document.createElement('img'); img.onerror = function () { /* code*/ }; img.src = '错误地址';
- 只要图像的src属性中的URL不能返回能被识别的图像格式,就会触发图像的
-
-
运用策略
-
非客户端页面
仅需在加载JS之前配置好
window.onerror
。 -
客户端内嵌页面
-
在加载JS之前配置好
window.onerror
。 -
客户端回调函数嵌套一层
try-catch
,提示哪个方法发生错误等额外信息。因为客户端调用前端的方法是直接通过函数运行JS代码,抛出错误时
window.onerror
传入的参数仅有第一个message
参数。 -
(可选)为了避免JS代码还未加载完毕客户端就调用回调函数,需在客户端调用前端JS时嵌套一层
try-catch
(服务端代码中添加),提示哪个方法发生错误等额外信息。
-
捕获错误的目的在于避免浏览器以默认方式处理它们;而抛出错误的目的在于提供错误发生具体原因的消息。
-
-
JS预加载图片
var img = new Image(); //或document.createElement('img'); img.src = '图片地址'; img.onerror = function () { console.log('加载失败'); }; if (img.complete) { console.log('缓存'); } else { img.onload = function () { console.log('新加载'); }; }
-
<link>
预加载-
<link rel="dns-prefetch" href="域名">
解析DNS。
-
<link rel="preconnect" href="域名">
解析DNS,建立TCP握手连接、TLS协议。
-
<link rel="prefetch" href="资源">
(低优先级)请求、下载、缓存资源。
-
<link rel="subresource" href="资源">
(高优先级)请求、下载、缓存资源。
-
<link rel="prerender" href="域名">
就像在后台打开了一个隐藏的tab,下载域名的所有资源、创建DOM、渲染页面、执行JS等等。
-
<link rel="preload" href="资源">
请求、下载、缓存资源。
-
-
CSS背景图片预加载。
obj
为对象实例,arr
为数组实例。
continue
应用在循环(while
、do-while
、for
、for-in
、for-of
),表示跳过当次循环;break
应用在循环、switch
,表示跳出整个循环。forEach
、map
、filter
、some
、every
无法中止循环(return
只结束回调函数)。$.each/$dom.each
跳出循环用return true
(功能等价于continue
)、return false
(功能等价于break
)。
-
原生JS
-
while
、do-while
while (跳出判断) { }
do { } while (跳出判断);
-
for
for (执行一次; 跳出判断; 每执行一次后执行) { }
-
for-in
遍历对象所有的可枚举属性。
/* i为数组当前项的索引或对象当前项的属性名*/ for (var i in obj或arr) { }
-
for-of
遍历可迭代对象的每个元素。
/* i为迭代对象的属性值*/ for (var i of 可迭代对象) { }
-
Array方法
参数均为:
回调函数(当前值, 索引, 数组整体)[, this替代]
。-
Array.prototype.forEach()
对数组的每个元素执行一次提供的函数。
-
Array.prototype.map()
数组中的每个元素调用提供的函数,组成新的数组。
-
Array.prototype.filter()
使用提供的函数测试所有元素,并创建包含所有通过测试的元素的新数组。
-
Array.prototype.every()
测试数组中是否所有元素都通过提供的函数。
-
Array.prototype.some()
测试数组中是否有一个元素通过提供的函数。
-
-
Object.entries
、Object.values
、Object.keys
、Object.getOwnPropertyNames
、Object.getOwnPropertySymbols
-
-
jQuery
-
$.each
/* index为数组当前项的索引或对象当前项的属性名或jQuery对象的索引,item为当前项的值(不是jQuery对象,是DOM对象,与this相同)*/ $.each(obj或arr或$dom, function (index, item) { });
-
$dom.each
/* index为jQuery对象的索引,item为当前项的值(不是jQuery对象,是DOM对象,与this相同)*/ $dom.each(function (index, item) { });
-
$.grep
类似
Array.prototype.filter
-
-
判断对象方法是否可以执行
/* 对象已经定义 && 对象不等于null && 对象方法存在*/ if (typeof obj !== 'undefined' && obj !== null && typeof obj.func === 'function') { /* 对象方法已定义 可执行*/ }
-
判断全局对象方法是否可以执行
/* window的子对象存在 && 对象方法存在*/ if (window.obj && typeof window.obj.func === 'function') { /* 对象方法已定义 可执行*/ }
-
判断是否需要重新定义
/* 对象不存在 || 对象等于null || 对象方法不存在*/ if (typeof obj === 'undefined' || obj === null || typeof obj.func !== 'function') { /* 对象或对象方法没有定义 需重新定义*/ }
-
变量已定义
/* 变量已定义 && 变量不等于null*/ if (typeof a !== 'undefined' && a !== null) { /* 对象已定义 可操作*/ }
-
JS是一门脚本语言,不经过
编译而直接运行,但运行前先进行预编译。 -
JS的预编译是以代码块
<script></script>
为范围,即每遇到一个代码块都会进行:预编译 -> 执行。 -
预编译:在内存中开辟一块空间,用来存放变量和函数。
为使用
var
声明的变量、使用function
声明的函数在内存中开辟一块空间,用来存放两者声明(不会赋值,所有变量的值都是undefined
、函数内容会被预编译);const/let
不允许同名声明。-
在预编译时,
function
的优先级比var
高:- 在预编译阶段,同时声明同一名称的函数和变量(顺序不限),会被声明为函数。
- 在执行阶段,如果变量有赋值,则这个名称会重新赋值给变量。
e.g.
// 预编译阶段a1/b2为函数,运行时a1/b2赋值成为变量 console.log(a1); // function a1 var a1 = 1; function a1() {} console.log(a1); // 1 console.log(b2); // function b2 function b2() {} var b2 = 1; console.log(b2); // 1 // 预编译阶段c3/d4为函数,运行时没有赋值 console.log(c3); // function c3 var c3; function c3() {} console.log(c3); // function c3 console.log(d4); // function d4 function d4() {} var d4; console.log(d4); // function d4 // 预编译阶段e5为变量,运行时被赋值给匿名函数 console.log(e5); // undefined var e5 = function () {}; console.log(e5); // function 匿名
-
-
变量赋值是在JS执行阶段(运行时)进行的。
函数是唯一拥有自身作用域的结构。
-
每个函数都是一个
Function
对象,像普通对象一样拥有属性和方法。- 函数默认有
length
(希望接收的命名参数个数)、prototype
属性。 - 函数内的
arguments.callee
是一个指针:其指向拥有arguments
对象的函数(函数自身)。 函数对象.caller
:保存调用当前函数的函数(嵌套的外一层函数)的引用。- 函数内的
arguments.caller
(值为undefined
,仅为分清arguments.caller
和函数对象.caller
)。 - 函数继承的
toLocaleString
、toString
、valueOf
的返回值为:经过浏览器处理过的函数代码(因浏览器而异)。
- 函数默认有
-
函数总有返回值。
- 执行函数:默认返回
undefined
。 - 实例化(
new
):默认返回this
。
- 执行函数:默认返回
-
创建函数的方式:
-
构造函数
var 名字 = new Function([多个参数, ]函数体字符串);
直接调用
Function
(不使用new
操作符)的效果与调用构造函数一样,区别是this
指向window
。 -
函数声明(函数语句)
function 名字(多个参数) {/* 函数体*/};
-
函数表达式(function expression)
var 名字 = function(多个参数) {/* 函数体*/};
-
命名函数表达式:
var 名字1 = function 名字2() {};
,其中函数名名字2
只能在函数体内部使用:e.g.
var func1 = function func2() { console.log(typeof func1); //function console.log(typeof func2); //function }; func1(); console.log(typeof func1); //function console.log(typeof func2); //undefined
-
- 通过函数声明、函数表达式创建的函数,在加载脚本时和其他代码一起解析;通过构造函数定义的函数,在构造函数被执行时才解析函数体字符串。
- 不推荐通过
构造函数创建函数,因为作为字符串的函数体可能会阻止一些JS引擎优化,也会引起其他问题。
-
-
实例化(new)一个构造函数
new
得到的实例对象,拥有构造函数内用this
定义的属性和方法,且拥有构造函数的原型对象上的属性和方法(因为实例的[[Prototype]]
指向构造函数.prototype
);在构造函数内var
的变量和function
无法被这个对象使用,只能在构造函数里使用(类似私有变量)。相对于单全局变量,构造函数更加灵活,可以生成多个对象进行互相独立的操作。
-
new
一个构造函数执行的步骤e.g.
var newObj = new Func(para);
-
创建一个空对象(假设为obj):
var obj = {};
-
设置obj的
[[Prototype]]
指向构造函数的原型对象:obj.__proto__ = Func.prototype;
或Object.setPrototypeOf(obj, Func.prototype);
第一、二步骤也可以用
var obj = Object.create(Func.prototype);
代替。-
使用obj作为上下文调用构造函数,并传入参数:
Func.call(obj, para);
-
newObj赋值
- 若Func返回引用数据类型,则这个引用数据类型的值赋值给newObj。
- 若Func返回基本数据类型或返回this或无返回,则obj赋值给newObj。
-
-
-
函数调用类型
-
直接函数调用(如
alert();
)、立即调用的函数表达式(如(function () {}());
)this
:全局对象window
-
对象的方法调用(如
console.log();
)this
:上级对象 -
构造函数实例化(如
new RegExp();
)this
:新实例对象 -
间接调用(
alert.call(传入的对象);
或apply
)this
:传入的对象
-
this
——调用函数的那个对象e.g.
var x = 'global'; function test() { console.log(this.x + '|' + _test() + '|' + (function () {return this.x;}())); function _test() { return this.x; } } /* window:方法没有对象调用(直接函数调用、立即调用的函数表达式,且与作用域无关)*/ test(); //global|global|global var obj1 = { x: 1, test: function () { var that = this; return function () { console.log(this.x + '|' + that.x); }; } }; (obj1.test()()); //global|1 /* 上级对象:函数作为某个对象的方法调用*/ var obj2 = {}; obj2.x = 2; obj2.func = test; obj2.func(); //2|global|global /* 新实例对象:构造函数*/ function Test() { this.x = 3; console.log(this.x + '|' + _test() + '|' + (function () {return this.x;}())); function _test() { return this.x; } } var obj3 = new Test(); //3|global|global /* 传入的指定对象:apply或call调用*/ var obj4 = {x: 4}; obj2.func.call(obj4); //4|global|global
-
- 当函数内部定义了其他函数时,就创建了闭包。内部函数总是可以访问其所在的外部函数中声明的内容(链式作用域),即使外部函数执行完毕(寿命终结)之后。
- 闭包:能够读取其他函数内部变量的函数。由两部分构成:函数、上下文环境(链式作用域)。
- 闭包通常用来创建私有变量或方法,使得这些内容不被外部访问,同时又可以通过指定的闭包函数访问。
-
产生效果:
- 可以操作函数内部的私有内容(特权方法)。
- 让被操作的私有内容始终保持在内存中不被垃圾回收。
- 占用较多的内存。
-
构造函数、原型对象、实例、原型链
-
只有函数有
prototype
属性,指向函数的原型对象。互相连接:函数拥有
prototype
属性指向其原型对象,原型对象拥有constructor
属性指向函数。构造函数通过
prototype
为实例存储要共享的属性和方法,可设置prototype
指向其他对象来继承其他对象。 -
当构造函数实例化(
new
),该实例拥有[[Prototype]]
属性,指向构造函数的原型对象。访问对象的
[[Prototype]]
属性:对象.__proto__
(非标准)、Object.getPrototypeOf/setPrototypeOf(对象)
。- 连接存在于实例与构造函数的原型对象之间,而不直接存在于
实例与构造函数之间。 - 内置构造函数的原型上有各种方法和属性,实例对象通过原型链进行调用。
- 连接存在于实例与构造函数的原型对象之间,而不直接存在于
-
每个引用数据类型都有
[[Prototype]]
属性,指向自己的构造函数的prototype
。每个引用数据类型都显式或隐式由某个构造函数创建。
-
不断向上的
[[Prototype]]
属性,构成了原型链。原型链终点是
null
,倒数第二是Object.prototype
。
e.g.
var A = function () {}; var a = new A(); console.log('原型与构造函数:', A.prototype.constructor === A); console.log('实例与原型:', a.__proto__ === A.prototype); console.log('实例与构造函数:', a.__proto__.constructor === A); console.log(a.__proto__.__proto__ === Object.prototype); console.log(a.__proto__.__proto__.__proto__ === null);
-
-
如果重写原型的值(不是添加),可以给原型添加
constructor
属性并指向构造函数var A = function () {}; A.prototype = { other: '...' }; if (typeof Object.defineProperty === 'function') { //使属性:不可以改变描述符、不可以删除、不可以枚举、不可以被赋值运算符改变 Object.defineProperty(A.prototype, 'constructor', { value: A }); } else { A.prototype.constructor = A; }
-
通过原型链实现继承
-
继承原理
- 在子类构造函数内部调用父类构造函数。
- 将一个构造函数的实例(父类)赋值给另一个构造函数的原型(子类)。
-
继承方式
-
寄生组合式继承(最理想方式)
通过借用构造函数来继承属性(在子类构造函数内部调用父类构造函数);通过原型链的混成形式来继承方法。
/* 父类定义:*/ function Father(fatherPara) { /* 私有属性用let;静态私有属性用const*/ /* 父类属性*/ this.fatherPrimitive = fatherPara; this.fatherReference = ['father1', 2, [{4: true}, undefined, null]]; } /* 父类方法*/ Father.prototype.fatherFun = function () { console.log(this.fatherPrimitive, this.fatherReference); }; /* 子类定义:*/ function Son(sonPara1, sonPara2) { /* 子类继承父类属性*/ Father.call(this, sonPara1); /* 子类属性*/ this.sonPrimitive = sonPara2; this.sonReference = ['son1', 2, [{4: true}, undefined, null]]; } /* 子类继承父类原型链*/ Son.prototype = Object.create(Father.prototype, {constructor: {value: Son}}); /* 子类方法*/ Son.prototype.sonFun = function () { console.log(this.sonPrimitive, this.sonReference); }; /* 使用测试*/ var instance1 = new Father('父para'); var instance2 = new Son('子para1', '子para2'); console.log(instance1, instance2);
“子类继承父类方法”可以改为不使用
Object.create
的方式:(function (subType, superType) { function _object(o) { function F() {} F.prototype = o; return new F(); } var prototype = _object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }(Son, Father));
-
原型式继承
Object.create
(Polyfill)问题:包含引用类似值的属性,始终会共享给原型与所有实例(浅复制)。
-
-
JS自动完成内存分配、回收。
-
变量在内存中的存储
-
栈内存(stack):
先进后出,寄存速度快,栈数据可共享,系统自动分配,数据固定不灵活,空间大小有限制,超出则栈溢出。
存储内容:变量标识(指向基本数据类型、引用数据类型的地址)、基本数据类型、引用数据类型的地址。
-
堆内存(heap):
顺序随意,寄存速度慢,由程序申请,操作简单,存储空间较大(取决于系统有效虚拟内存)。
存储内容:引用数据类型。
-
-
变量的值传递
变量(包括函数的参数)都是值传递(变量指向的值复制给另一个变量去指向)。
-
引用数据类型的浅复制
- 拷贝对象A时,对象B将拷贝A的所有字段。若字段是引用数据类型(内存地址),B将拷贝地址;若字段是基本数据类型,B将复制其值。
- 缺点:如果改变了对象B(或A)所指向的内存地址所存储的值,同时也改变了对象A(或B)指向这个地址所存储的值。
-
引用数据类型的深复制
- 新开辟一个内存空间,完全拷贝所有数据至新的空间,新对象指向这个新空间的地址(原对象不变化)。
- 优点:B与A不会相互依赖(A,B完全脱离关联);缺点:拷贝的速度更慢,代价更大。
-
-
存储、值传递步骤举例
// e.g. var a = 'test1'; //i var b = {'key': 'test1'}; //ii var c = a; //iii c = 'test2'; //iv var d = b; //v d['key'] = 'test2'; //vi
-
var a = 'test1';
步骤:- 声明a:查找栈内存中是否存在a,不存在则创建,不管是已存在还是新创建的a,都指向undefined。
- 为a赋值:在栈内存中查找'test1',无则栈内存中创建'test1',然后让a指向'test1'。
-
var b = {'key': 'test1'};
步骤:- 声明b(同声明a)。
- 为b赋值:堆内存中创建{'key': 'test1'},栈内存中创建其堆地址url_b,让b指向url_b。
-
var c = a;
步骤:- 声明c(同声明a)。
- 为c赋值:在栈内存中查找a,找不到则抛出错误,找到a则让c指向a所指向的'test1'。
-
c = 'test2';
步骤:- 在栈内存中查找c,未找到则声明一个全局变量c。
- 为c赋新值:在栈内存中查找'test2',无则栈内存中创建'test2',然后让c指向'test2'。
-
var d = b;
步骤:- 声明d(同声明a)。
- 为d赋值:在栈内存中查找b,找不到则抛出错误,找到b则让d指向b所指向的url_b。
-
d['key'] = 'test2'
步骤:- 在栈内存中查找d,找不到则抛出错误,找到d则通过其所指向url_b找到堆内存中的{'key': 'test1'}。
- 修改堆内存中的{'key': 'test1'}为{'key': 'test2'}。
-
-
递归赋值(最全面方式)
-
针对仅能够被json直接表示的数据结构(对象、数组、数值、字符串、布尔值、null):
JSON.parse(JSON.stringify(obj));
-
使用原型继承(单向不改变值):
Object.create(obj)
深复制要处理的坑:循环引用、各种引用数据类型。
内存泄露:计算机内存逐渐丢失。当某个程序总是无法释放内存时,出现内存泄露。
-
全局变量不会被垃圾回收。
-
被闭包引用的变量不会被垃圾回收。
-
DOM清空或删除时,事件绑定未清除导致内存泄漏(删除DOM前,先移除事件绑定)。
-
被遗忘的计数器或回调函数:不使用时及时清除。
-
后代元素存在引用引起的内存泄漏:
- 黄色是指直接被JS变量所引用,在内存里。
- 红色是指间接被JS变量所引用,refB被refA间接引用,导致即使refB变量被清空,也是不会被回收的。
- 子元素refB由于parentNode的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除。
-
强制转换
-
Number(参数)
-
基本数据类型:
e.g.
Number('123'); //123 Number('123a'); //NaN Number(''); //0 Number(true); //1 Number(false); //0 Number(); //0 Number(undefined); //NaN Number(null); //0 Number(Symbol()); //抛出TypeError错误
parseInt
与Number
均忽略字符串前后的不可见字符。parseInt
从前向后逐个解析字符,只要开头有数字则返回数值;Number
判断只要有一个字符无法转成数值,则返回NaN
。 -
引用数据类型:
若参数是单个数值的数组、空数组、单个数值为undefined的数组,则返回
数值
、0
、0
;否则返回NaN
。e.g.
Number([5]); //5 Number([]); //0 Number([undefined]); //0 Number([1, 2, 3]); //NaN Number({}); //NaN
-
Number(对象)
具体规则:- 调用原对象自身的
valueOf
,若返回值是基本数据类型,则再使用Number
,不再进行后续步骤; - 调用原对象自身的
toString
,若返回值是基本数据类型,则再使用Number
,不再进行后续步骤; - 若以上返回都还是对象,报错。
- 调用原对象自身的
-
-
-
String(参数)
-
基本数据类型:
- 数值:转为相应的字符串。
- 字符串:原来的值。
- 布尔值:
true
转为'true'
,false
转为'false'
。 undefined
:转为'undefined'
。null
:转为'null'
。
-
引用数据类型:
若是数组,则返回该数组的字符串形式;否则返回一个类型字符串。
// e.g. String([1, 2, 3]); //'1,2,3' String({a: 1}); //'[object Object]''
-
String(对象)
具体规则:- 调用原对象自身的
toString
,若返回值是基本数据类型,则再使用String
,不再进行后续步骤; - 调用原对象自身的
valueOf
,若返回值是基本数据类型,则再使用String
,不再进行后续步骤; - 若以上返回都还是对象,报错。
- 调用原对象自身的
-
-
-
Boolean(参数)
数据类型 转换为 true
的值转换为 false
的值Boolean true
false
String 任何非空字符串 ''
Number 任何非零数值(包括无穷大) 0
、-0
、+0
、NaN
Symbol 任何Symbol类型值 无 Object 任何对象 null
undefined undefined
-
-
自动转换
-
触发情况:
-
运算数的数据类型与运算符的预期不符。
-
不同类型的数据互相运算。
-
对非布尔值类型的数据求布尔值。
-
对非数值类型的数据使用一元运算符(
+
、-
)// e.g. 'a' + + 'a' // 'a' + (+ 'a') -> 'a' + NaN -> 'aNaN' + '123'; // 123 - [123]; // -123 1 + undefined // NaN '1' + undefined // '1undefined'
-
-
行为:
预期什么类型的值,就调用该类型的转换函数。
-
自动转换为
Boolean
:if
、while
、for
等条件语句。条件运算 ? 表达式1 : 表达式2
。!!条件运算
。
-
自动转换为
String
:主要发生在加法运算时:当一个值为字符串,另一个值为非字符串,则后者转为字符串。
-
自动转换为
Number
:除了
加法运算符有可能把运算数转为字符串,其他运算符都会把运算数自动转成数值(包括一元运算符)。
-
+
加法运算:- 若运算数有对象,先
ToPrimitive(对象)
转换为的基本数据类型,再按照下面方式继续自动转换或运算出结果; - 若运算数有字符串,则都转换为字符串(
String(基本数据类型)
)进行; - 若运算数是数值与布尔值,则布尔值转换为数值(
true
->1、false
->0)进行。
- 若运算数有对象,先
-
-
-
==
与!=
进行的强制类型转换步骤-
运算数若存在
NaN
,则返回false
;除了
!=
和!==
,只要对比(==
、===
、>
、>=
、<
、<=
)的操作数有一个是NaN
,则返回false
。 -
运算数若存在
布尔值
,将布尔转换为数字(true
->1、false
->0); -
运算数若存在
字符串
, 有三种情况:- 对方是
对象
,则进行ToPrimitive(对象, 'string')
后进行对比; - 对方是
数字
,字符串转数字; - 对方是
字符串
,直接比较; - 其他返回
false
。
- 对方是
-
若运算数是
数字
与对象
,则对象进行ToPrimitive(对象, 'number')
,再比较; -
若运算数都是
对象
,则比较它们的引用值
(如果两个运算数指向同一对象,那么返回true
,否则false
); -
null
与undefined
不会进行类型转换, 但相等。
-
ToPrimitive
:依次调用对象的valueOf
、toString
,将对象转化了基本数据类型。{ toString: () => {console.log('toString');return {}}, valueOf: () => {console.log('valueOf');return {}} }
对象来判断先调用哪个属性。
-
expr1 || expr2
:- 赋值操作:如果expr1能转换成true(
Boolean(expr1)
)则返回expr1,否则返回expr2。 - 在Boolean环境(如if的条件判断)中使用:两个操作结果中只要有一个为true,返回true;二者操作结果都为false时返回false。
- 赋值操作:如果expr1能转换成true(
-
expr1 && expr2
:- 赋值操作:如果expr1能转换成false(
Boolean(expr1)
)则返回expr1,否则返回expr2。 - 在Boolean环境(如if的条件判断)中使用:两个操作结果都为true时返回true,否则返回false。
- 赋值操作:如果expr1能转换成false(
参考阮一峰:再谈Event Loop、Help, I’m stuck in an event-loop.、Tasks, microtasks, queues and schedules。
不是在ECMAScript没有定义,而是在HTML Standard中定义。
-
JS的主线程是单线程
-
原因:
- JS设计初衷是为了与用户互动、操作DOM等简单操作。
- 单线程提高效率,多线程会带来复杂的同步问题。
-
结果:
- 程序在执行时必须排队,且一个程序不能中断另一个程序的执行。
- 没有任何代码是立即执行的,但一旦进程空闲则尽快执行。
-
弥补单线程计算量太大、事件耗时太久影响浏览器体验:
-
新增Web Worker标准,但不能
操作DOM,完全受主线程控制。 -
多个异步线程分别处理:网络请求、定时器、读写文件、I/O设备事件、页面渲染等。
DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。
-
-
-
任务类型
-
同步任务(synchronous):
在主线程上排队执行的任务。
-
异步任务(asynchronous):
-
先挂起到(对应种类的)工作线程等待结果,主线程不会因此阻塞;有结果后,发起通知进入(对应种类的)“任务队列”(task queue或“消息队列”);“执行栈”(execution context stack)为空后会读取并执行“任务队列”的通知对应的回调函数。
相同类型的异步工作线程是串行工作;不同类型的异步工作线程互不影响执行。
-
异步任务分为两种类型:macrotask(task)、microtask(job)
-
macrotask一般包括:
-
I/O
监听的事件。
-
requestAnimationFrame
-
新的<script>
-
setTimeout
、setInterval
、setImmediate
在当前“任务队列”的尾部,添加事件。
-
AJAX
-
-
microtask一般包括:
-
process.nextTick
在当前“执行栈”的尾部——读取"任务队列"之前,添加事件。
-
async-await
(只有await
才是异步) -
Promise
(Promise.then/catch/all/race
)new Promise
和Prmise.resolve/reject
都是直接执行。 -
MutationObserver
-
-
macrotask和microtast选择
如果想让一个任务立即执行,就把它设置为microtask,除此之外都用macrotask。因为虽然JS是异步非阻塞,但在一个事件循环中,microtask的执行方式基本上是用同步的。
-
macrotask、microtask的事件循环运行机制:
-
检查macrotask队列
-
选择最早加入的任务X,设置为“目前运行的任务”并进入“执行栈”;如果macrotask队列为空,跳到第4步;
最初时刻:“执行栈”为空,
<script>
作为第一个macrotask被运行。 -
“执行栈”运行任务X(运行对应的回调函数);
-
设置“目前运行的任务”为
null
,从macrotask队列中移除任务X; -
跳出macrotask队列、进行浏览器渲染。
-
-
浏览器渲染;
-
检查microtask队列:
- 选择最早加入的任务a,设置为“目前运行的任务”并进入“执行栈”;如果microtask队列为空,跳到第5步;
- “执行栈”运行任务a(运行对应的回调函数);
- 设置“目前运行的任务”为
null
,从microtask队列中移除任务a; - 跳到第1步(检查下一个最早加入的microtask任务);
- 跳出microtask队列、进行检查macrotask队列。
-
-
-
-
-
JS的事件循环运行机制:
-
“执行栈”进行:
- 所有同步任务都在主线程上执行,形成一个“执行栈”,串行执行直到“执行栈”为空(只有前一个任务执行完毕,才能执行后一个任务)。
- 主线程之外,还存在“任务队列”。“执行栈”遇到异步任务则把其挂起到异步线程,只要异步线程有了运行结果,就在“任务队列”之中放置通知。
-
一旦“执行栈”中的所有同步任务执行完毕,系统就会(按照macrotask、microtask的事件循环运行机制)读取“任务队列”,把一个通知对应的回调函数加入执行栈。跳回步骤1(“执行栈”又有内容可以执行)。
-
-
由于异步函数是立刻返回,异步事务中发生的错误是无法通过
try-catch
来捕捉。
-
定时器(
setInterval
、setTimeout
)不适合制作动画。
定时器触发后,会把定时器处理程序(回调函数)插入至等待执行的任务队列最后面。
setInterval
和setTimeout
的内部运行机制完全一致。-
setInterval
、clearInterval
:- 同一个被setInterval执行的函数只能插入一个定时器处理程序到任务队列。
- setInterval是间隔时间去尝试执行函数,不关注上一次是何时执行。
- 若setInterval触发时已有它的定时器处理程序在任务队列中,则忽略此次触发。直到没有它的定时器处理程序在任务队列后才可以再次插入(正在执行的不算在任务队列中)。
- 相邻的2次定时器处理程序可能小于或大于(或等于)设定的间隔时间。无法确定定时器处理程序何时执行。
-
setTimeout
、clearTimeout
:用setTimeout模拟setInterval,可以提高性能(上一次执行完毕间隔时间后再执行下一次,而不是固定间隔时间都尝试执行),并且可以确保每次定时器处理程序执行间隔一定大于(或等于)设置的间隔时间。
-
setImmediate
、clearImmediate
:仅ie10支持。
等价于
setTimeout(func, 0)
。
-
-
重绘函数(
requestAnimationFrame
)-
浏览器重绘之前(大部分浏览器是1秒钟60帧,也就是16.67ms进行一帧重绘)调用一次。
-
替代执行时机无法保证的
setTimeout
、setInterval
进行动画操作,提升渲染性能:- 把每一帧中的所有DOM操作集中起来,在一次重绘或重排中完成动画,且时间间隔紧随浏览器的刷新频率。
- 仅仅绘制用户可见的动画。这意味着没把CPU或电池寿命浪费在绘制处于背景标签,最小化窗口,或页面隐藏区域的动画上。
- 当浏览器准备好绘制时(空闲时),才绘制一帧,此时没有等待中的帧。意味着其绘制动画不可能出现多个排队的回调函数,或阻塞浏览器。因此动画更平滑,CPU和电池使用被进一步优化。
-
-
空闲函数(
requestIdleCallback
、cancelIdleCallback
)在浏览器空闲时期依次调用函数。
- 只有当前帧的运行时间小于16.66ms时,回调函数才会执行。否则,就推迟到下一帧,如果下一帧也没有空闲时间,就推迟到下下一帧,以此类推。
- 第二个参数表示指定的毫秒数。如果在指定的这段时间之内,每一帧都没有空闲时间,那么回调函数将会强制执行。
-
数组的空位:数组的某一个位置没有任何值
- 空位是可以读取的,返回
undefined
。 - 空位不是
undefined,空位没有任何值。一个位置的值等于undefined
,依然有值。 - 使用
delete
删除一个数组成员,会形成空位,并且不会影响length
属性。 - 给一个数组的
length
属性赋予大于其长度的值,新创建的项都是空位。
// e.g. [, , ,][0]; //undefined 0 in [undefined, undefined, undefined]; //true 0 in Array.apply(null, new Array(3)); //true(密集数组:没有空位的数组) 0 in new Array(3); //false(稀疏数组:有空位的数组) 0 in [, , ,]; //false
new Array(数量)
(或Array(数量)
)返回的是有空位的稀疏数组。Array.apply(null, new Array(数量));
返回的是没有空位的密集数组。- 若数组最后一个元素后面有逗号,并不会产生空位,而是忽略这个逗号:
[1, ].length === 1
。
- 空位是可以读取的,返回
-
ES5大多数情况下会忽略空位:
forEach
、filter
、every
、some
、find
、findIndex
、reduce
、reduceRight
等遍历方法的回调函数会跳过空位;map
的回调函数会跳过空位,但返回值保留空位。join
、toString
将空位
、undefined
、null
处理成空字符串''
。
-
ES6明确将空位转为
undefined
。