37个 JavaScript 基本面试问题和解答

2018/05/12 · JavaScript
· 面试

原文出处:

DOM 操作成本到底高在哪儿?

2018/04/09 · 基础技术 ·
DOM

原文出处:
palmerye   

从我接触前端到现在,一直听到的一句话:操作DOM的成本很高,不要轻易去操作DOM。尤其是React、vue等MV*框架的出现,数据驱动视图的模式越发深入人心,jQuery时代提供的强大便利地操作DOM的API在前端工程里用的越来越少。刨根问底,这里说的成本,到底高在哪儿呢?

 

也许你不知道,JS animation比CSS更快!

2017/12/07 · JavaScript
· Animation

原文出处: Julian
Shapiro   译文出处:众成翻译
/凯小凯   

CSS vs. JS Animation: 哪个更快?

基于JavaScript的动画竟然已经默默地比CSS的transition动画快了?而且,Adobe和
Google竟然一直在发布可以媲美原生应用的富媒体移动站点?

这篇文章将会逐点讲解基于JavaScript的DOM动画库,比如Velocity.js和GSAP,是如何比jQuery和基于CSS的动画库高效的。

Toptal   译文出处:[众成翻译

xiaosheng222]()   

什么是DOM

Document Object Model 文档对象模型

什么是DOM?可能很多人第一反应就是div、p、span等html标签(至少我是),但要知道,DOM是Model,是Object
Model,对象模型,是为HTML(and XML)提供的API。HTML(Hyper Text Markup
Language)是一种标记语言,HTML在DOM的模型标准中被视为对象,DOM只提供编程接口,却无法实际操作HTML里面的内容。但在浏览器端,前端们可以用脚本语言(JavaScript)通过DOM去操作HTML内容。

那么问题来了,只有JavaScript才能调用DOM这个API吗?

答案是NO

Python也可以访问DOM。所以DOM不是提供给Javascript的API,也不是Javascript里的API。

PS:
实质上还存在CSSOM:CSS
Object
Model,浏览器将CSS代码解析成树形的数据结构,与DOM是两个独立的数据结构

jQuery

让我们先从这个事实开始:JavaScript和jQuery被错误的混淆了。JavaScript的动画是快的,但是jQuery的动画慢。为什么?因为虽然jQuery很强大,但是它的目标从来不是为了成为一个高效的动画引擎。

  • jQuery不能避免布局震荡因为它的代码除了动画还提供了很多功能。
  • jQuery的内存消耗经常触发垃圾回收,导致动画卡住
  • jQuery使用setInterval而不是requestAnimationFrame
    (RAF)为了避免一些bug

注意,布局震荡引起了动画开始处的卡顿,垃圾回收导致了动画进行中的卡顿,RAF的缺席导致了帧率低。

1、使用typeof bar ===“object”来确定bar是否是一个对象时有什么潜在的缺陷?这个陷阱如何避免?

尽管typeof bar
===“object”是检查bar是否是对象的可靠方法,但JavaScript中令人惊讶的问题是null也被认为是一个对象!

因此,对于大多数开发人员来说,下面的代码会将真实(而不是错误)记录到控制台:

var bar = null; console.log(typeof bar === “object”); // logs true!

1
2
var bar = null;
console.log(typeof bar === "object");  // logs true!

只要知道这一点,就可以通过检查bar是否为空来轻松避免该问题:

console.log((bar !== null) && (typeof bar === “object”)); // logs false

1
console.log((bar !== null) && (typeof bar === "object"));  // logs false

为了在我们的答案更加的完整,还有两件事值得注意:

首先,如果bar是一个函数,上面的解决方案将返回false。在大多数情况下,这是所期望的行为,但是在您希望函数返回true的情况下,您可以将上述解决方案修改为:

console.log((bar !== null) && ((typeof bar === “object”) || (typeof bar
=== “function”)));

1
console.log((bar !== null) && ((typeof bar === "object") || (typeof bar === "function")));

其次,如果bar是数组,则上述解决方案将返回true(例如,如果var bar =
[];)。在大多数情况下,这是所希望的行为,因为数组确实是对象,但是在您想要对数组也是false的情况下,可以将上述解决方案修改为:

console.log((bar !== null) && (typeof bar === “object”) &&
(toString.call(bar) !== “[object Array]”));

1
console.log((bar !== null) && (typeof bar === "object") && (toString.call(bar) !== "[object Array]"));

但是,还有一个替代方法对空值,数组和函数返回false,但对于对象则为true:

console.log((bar !== null) && (bar.constructor === Object));

1
console.log((bar !== null) && (bar.constructor === Object));

或者,如果您使用jQuery:

console.log((bar !== null) && (typeof bar === “object”) && (!
$.isArray(bar)));

1
console.log((bar !== null) && (typeof bar === "object") && (! $.isArray(bar)));

ES5使得数组的情况非常简单,包括它自己的空检查:

console.log(Array.isArray(bar));

1
console.log(Array.isArray(bar));

浏览器渲染过程

讨论DOM操作成本,肯定要先了解该成本的来源,那么就离不开浏览器渲染。

这里暂只讨论浏览器拿到HTML之后开始解析、渲染。(怎么拿到HTML资源的可能后续另开篇总结吧,什么握握握手啊挥挥挥挥手啊,万恶的flag…)

  1. 解析HTML,构建DOM树(这里遇到外链,此时会发起请求)
  2. 解析CSS,生成CSS规则树
  3. 合并DOM树和CSS规则,生成render树
  4. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
  5. 绘制render树(paint),绘制页面像素信息
  6. 浏览器会将各层的信息发送给GPU,GPU将各层合成(composite),显示在屏幕上

实现的例子

避免布局震荡,包括简单地合并DOM查询和DOM更新:

var currentTop, currentLeft; /* 有布局震荡 */ currentTop =
element.style.top; /* QUERY */ element.style.top = currentTop + 1; /*
UPDATE */ currentLeft = element.style.left; /* QUERY */
element.style.left = currentLeft + 1; /* UPDATE */ /* 没有布局震荡
*/ currentTop = element.style.top; /* QUERY */ currentLeft =
element.style.left; /* QUERY */ element.style.top = currentTop + 1;
/* UPDATE */ element.style.left = currentLeft + 1; /* UPDATE */

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var currentTop,
  currentLeft;
 
/* 有布局震荡 */
currentTop = element.style.top; /* QUERY */
element.style.top = currentTop + 1; /* UPDATE */
 
currentLeft = element.style.left; /* QUERY */
element.style.left = currentLeft + 1; /* UPDATE */
 
/* 没有布局震荡 */
currentTop = element.style.top; /* QUERY */
currentLeft = element.style.left; /* QUERY */
 
element.style.top = currentTop + 1; /* UPDATE */
element.style.left = currentLeft + 1; /* UPDATE */

发生在更新之后的查询会强制浏览器立马重新布局,并计算给出页面样式的计算值(把更新的影响考虑在内)。这对于运行于16ms间隔的动画来讲,会产生巨大的开销。

同样,实现RAF并不需要对既有代码改动很大。让我们来对比一下RAF的实现和setInterval的实现:

var startingTop = 0; /* setInterval: 每16ms运行一次来达到60fps
(1000ms/60 ~= 16ms). */ setInterval(function() { /*
由于这里的代码会在1s内执行60次,所以我们把top属性每秒1单位的增长分成60份
*/ element.style.top = (startingTop += 1/60); }, 16); /*
requestAnimationFrame: 不管浏览器是否处于最优状态,都试图运行在60fps */
function tick () { element.style.top = (startingTop += 1/60); }
window.requestAnimationFrame(tick);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var startingTop = 0;
 
/* setInterval: 每16ms运行一次来达到60fps (1000ms/60 ~= 16ms). */
setInterval(function() {
  /* 由于这里的代码会在1s内执行60次,所以我们把top属性每秒1单位的增长分成60份 */
    element.style.top = (startingTop += 1/60);
}, 16);
 
/* requestAnimationFrame: 不管浏览器是否处于最优状态,都试图运行在60fps */
function tick () {
    element.style.top = (startingTop += 1/60);
}
 
window.requestAnimationFrame(tick);

RAF极大限度地提高了动画的性能。而您只需要修改为数不多的代码。

2、下面的代码将输出到控制台的是什么,为什么?

(function(){ var a = b = 3; })(); console.log(“a defined? ” + (typeof a
!== ‘undefined’)); console.log(“b defined? ” + (typeof b !==
‘undefined’));

1
2
3
4
5
6
(function(){
  var a = b = 3;
})();
 
console.log("a defined? " + (typeof a !== ‘undefined’));
console.log("b defined? " + (typeof b !== ‘undefined’));

由于a和b都在函数的封闭范围内定义,并且由于它们所在的行以var关键字开头,因此大多数JavaScript开发人员会希望typeof
a和typeof b在上面的示例中都未定义。

但是,情况并非如此。这里的问题是大多数开发人员错误地理解语句var a = b =
3;以下简写为:

var b = 3; var a = b;

1
2
var b = 3;
var a = b;

但实际上,var a = b = 3;其实是速记:

b = 3; var a = b;

1
2
b = 3;
var a = b;

因此(如果您不使用严格模式),代码片段的输出将为:

a defined? false b defined? true

1
2
a defined? false
b defined? true

但是如何在封闭函数的范围之外定义b?那么,因为声明var a = b = 3;是语句b =
3的简写;并且var a = b;
b最终成为一个全局变量(因为它不在var关键字后面),因此它仍然在作用域内,即使在封闭函数之外。

注意,在严格模式下(即,使用strict),语句var
a = b =
3;会产生一个ReferenceError的运行时错误:b没有定义,从而避免了可能导致的任何头headfakes/bugs。
(这就是为什么你应该在你的代码中使用strict,一个重要的例子!)

1.构建DOM树

<html> <head> <meta name=”viewport”
content=”width=device-width,initial-scale=1″> <link
href=”style.css” rel=”stylesheet”> <title>Critical
Path</title> </head> <body> <p>Hello
<span>web performance</span> students!</p>
<div><img src=”awesome-photo.jpg”></div> </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

无论是DOM还是CSSOM,都是要经过Bytes → characters → tokens → nodes → object model这个过程。

图片 1

DOM树构建过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。

CSS Transitions

CSS
transitions的动画性能优于jQuery,它把动画的逻辑交给了浏览器本身。这会有助于:1)优化DOM交互和内存消耗以避免卡顿,2)在底层借助RAF的特性,3)强制硬件加速(借助GPU的能力来提高动画性能)。

然而,实际情况是,这些优化可以直接通过JavaScript来实现,GSAP已经致力于此多年。Velocity.js,一个新的动画引擎,不止借助于上述技术,还应用了其他方法–我们将很快探讨。

明白JavaScript动画可以媲美CSS动画库这一事实,只是我们计划的第一步。第二步是我们要明白JavaScript动画可以比CSS动画还快。

让我们从检查CSS动画库的缺陷开始:

  • Transitions的强制硬件加速是使GPU加速,然而这反而会导致GPU强压状况下动画的卡顿。这些影响在移动设备上更为严重。(特别地,这个卡顿是由于数据在浏览器的主线程和排序线程间传递的开销导致的。一些CSS属性,比如transforms和opacity,是不受这个开销影响的。)Adobe在这里阐述了这个问题。
  • Transitions在IE10以下有兼容问题,
    这在PC端站点会很容易导致问题发生,因为IE8和IE9依然很流行。
  • 因为transitions并不是被JavaScript控制(它们只是被JavaScript触发),浏览器并不知道如何同步地使用JavaScript代码来操控优化transitions。

相反地:基于JavaScript的动画库,可以自己决定什么时候使用硬件加速,可以兼容所有版本的IE,并且它们非常适合批量动画优化。

我的建议是,当您只是开发移动站点,并且您的动画只包含简单的状态变化时,可以使用原生CSS
transitions。在这种情况下,transitions算是一种高效并且原生的解决方案,并且可以把所有的动画逻辑只放在css中,避免了因为引入JavaScript库而导致页面臃肿。但是,如果您正在设计复杂的UI,或者正在开发具有状态UI的应用程序,请使用JavaScript动画库,它可以使您的动画保持高性能,使您的工作流程保持可控。特别是在管理CSStransitions方面做得很棒的一个库是

Transit

3、下面的代码将输出到控制台的是什么?,为什么?

var myObject = { foo: “bar”, func: function() { var self = this;
console.log(“outer func: this.foo = ” + this.foo); console.log(“outer
func: self.foo = ” + self.foo); (function() { console.log(“inner func:
this.foo = ” + this.foo); console.log(“inner func: self.foo = ” +
self.foo); }()); } }; myObject.func();

1
2
3
4
5
6
7
8
9
10
11
12
13
var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();

以上代码将输出到控制台:

outer func: this.foo = bar outer func: self.foo = bar inner func:
this.foo = undefined inner func: self.foo = bar

1
2
3
4
outer func:  this.foo = bar
outer func:  self.foo = bar
inner func:  this.foo = undefined
inner func:  self.foo = bar

在外部函数中,this和self都引用myObject,因此都可以正确地引用和访问foo。

但在内部函数中,这不再指向myObject。因此,this.foo在内部函数中是未定义的,而对局部变量self的引用仍然在范围内并且可以在那里访问。

2.构建CSSOM树

上述也提到了CSSOM的构建过程,也是树的结构,在最终计算各个节点的样式时,浏览器都会先从该节点的普遍属性(比如body里设置的全局样式)开始,再去应用该节点的具体属性。还有要注意的是,每个浏览器都有自己默认的样式表,因此很多时候这棵CSSOM树只是对这张默认样式表的部分替换。

JavaScript Animation

Okay,所以JavaScript在性能上可以占上风。但是JavaScript究竟可以快多少呢?其实,它已经快到可以创建复杂的,通常只能用WebGL构建的3D
animation
demo。已经快到可以创建通常只能用Flash或者影效处理做到的multimedia
teaser。已经快到可以创建通常只能用canvas构建的virtual
world。

为了直观比较动画库的领先性能,包括Transit(内部使用CSS
transitions),请查阅Velocity的文档,在VelocityJS.org。

依然存在问题:JavaScript究竟如何达到高性能?下面是基于JavaScript的动画库能实现的优化列表:

  • 为了减小布局震荡,将整个动画中涉及到DOM同步化到堆栈中。
  • 缓存链式调用中的属性值,以尽量减少DOM查询(它是影响DOM动画性能的致命弱点)的发生。
  • 在同一个跨同级元素调用中缓存单位转换比率(例如PX到%、em等)。
  • 当样式更新在视觉上不明显时,跳过更新。

回顾之前讲的布局震荡,Velocity.js利用这些最佳实践来缓存动画的结束值,这些值会被重用为之后动画的开始值,从而避免再次查询DOM元素的初始值:

$element /* 将元素向下滑动到视图中。 */ .velocity({ opacity: 1, top:
“50%” }) /* 延迟1000ms,元素滑动出视图 */ .velocity({ opacity: 0, top:
“-50%” }, { delay: 1000 });

1
2
3
4
5
$element
  /* 将元素向下滑动到视图中。 */
  .velocity({ opacity: 1, top: "50%" })
  /* 延迟1000ms,元素滑动出视图 */
  .velocity({ opacity: 0, top: "-50%" }, { delay: 1000 });

在上面的例子中,第二个Velocity自动知道它应该从opacity为1,top为50%开始。

浏览器最终可以自己执行很多相同的优化,但这样做将需要极大地限制开发人员编写动画代码的方式。因此,同样的原因,jQuery不使用RAF(见上文),浏览器也永远不会强加优化,即使这些优化只有非常小的可能会打破规范或偏离预期的行为。

最后,让我们来比较一下这两个JavaScript动画库(Velocity.js和GSAP)。

  • GSAP是一种快速、功能丰富的动画平台。Velocit是一个轻量级工具,可以极大地提高UI动画性能和工作流程。
  • GSAP需要许可费。Velocity是通过许MIT开源的。
  • 性能都很优异,GSAP和Velocity在真实项目中没有区别。

我的建议是:当您需要精确的控制(例如重映,暂停/恢复/搜索)、运动(例如Bezier曲线路径),或复杂的分组/排序时,使用GSAP。这些特性对于游戏开发和某些niche应用非常重要,但在Web应用程序的UI中并不常见。

4、在功能块中封装JavaScript源文件的全部内容的重要性和原因是什么?

这是一种日益普遍的做法,被许多流行的JavaScript库(jQuery,Node.js等)所采用。这种技术在文件的全部内容周围创建一个闭包,这可能最重要的是创建一个私有名称空间,从而有助于避免不同JavaScript模块和库之间的潜在名称冲突。

这种技术的另一个特点是为全局变量提供一个容易引用(可能更短)的别名。例如,这通常用于jQuery插件。
jQuery允许您使用jQuery.noConflict()来禁用对jQuery名称空间的$引用。如果这样做了,你的代码仍然可以使用$使用闭包技术,如下所示:

(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

1
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

3.生成render树

DOM树和CSSOM树合并生成render树

图片 2

简单描述这个过程:

DOM树从根节点开始遍历可见节点,这里之所以强调了“可见”,是因为如果遇到设置了类似display: none;的不可见节点,在render过程中是会被跳过的(但visibility: hidden; opacity: 0这种仍旧占据空间的节点不会被跳过render),保存各个节点的样式信息及其余节点的从属关系。

Velocity.js

定位GSAP功能丰富,并不意味着Velocity功能单一。相反地,在压缩后只有7Kb的文件中,Velocity不仅提供了jQuery$.animate()的所有功能,而且提供了color
animation,transforms,loops,easings,class animation和scrolling。

简而言之,Velocity是jQuery、jQuery UI和CSStransitions的最佳组合。

进一步,从方便的角度,Velocity在底层使用jQuery的$.queue()方法,因此可以无缝地与jQuery的$.animate(),
$.fade()$.delay()函数交互。并且,由于Velocity的语法和$.animate()一致,您页面的代码不需要修改

让我们快速看一下Velocity.js。在基础动画上,Velocity和$.animate()一样:

$element .delay(1000) /* 使用Velocity的2000ms内改变元素top属性的动画*/
.velocity({ top: “50%” }, 2000) /*
当上面Velocity动画执行完时,使用标准的jQuery方法来使元素淡出*/
.fadeOut(1000);

1
2
3
4
5
6
$element
  .delay(1000)
  /* 使用Velocity的2000ms内改变元素top属性的动画*/
  .velocity({ top: "50%" }, 2000)
  /* 当上面Velocity动画执行完时,使用标准的jQuery方法来使元素淡出*/
  .fadeOut(1000);

在高级动画上,复杂的滚动场景和三维动画都可以创建——只需要两行简单的代码:

$element /* 在1000ms内,浏览器滚动到这个元素的顶部 */
.velocity(“scroll”, 1000) /* 之后使元素绕着它的Y轴旋转360度。 */
.velocity({ rotateY: “360deg” }, 1000);

1
2
3
4
5
$element
  /* 在1000ms内,浏览器滚动到这个元素的顶部 */
  .velocity("scroll", 1000)
  /* 之后使元素绕着它的Y轴旋转360度。 */
  .velocity({ rotateY: "360deg" }, 1000);

5、在JavaScript源文件的开头包含’use strict’的意义和有什么好处?

这里最简单也是最重要的答案是use
strict是一种在运行时自动执行更严格的JavaScript代码解析和错误处理的方法。如果代码错误被忽略或失败,将会产生错误或抛出异常。总的来说,这是一个很好的做法。

严格模式的一些主要优点包括:

  • 使调试更容易。 如果代码错误本来会被忽略或失败,那么现在将会产生错误或抛出异常,从而更快地发现代码中的问题,并更快地指引它们的源代码。
  • 防止意外全局。 如果没有严格模式,将值赋给未声明的变量会自动创建一个具有该名称的全局变量。这是JavaScript中最常见的错误之一。在严格模式下,尝试这样做会引发错误。
  • 消除隐藏威胁。在没有严格模式的情况下,对null或undefined的这个值的引用会自动强制到全局。这可能会导致许多headfakespull-out-your-hair类型的错误。在严格模式下,引用null或undefined的这个值会引发错误。
  • 不允许重复的参数值。 严格模式在检测到函数的重复命名参数(例如,函数foo(val1,val2,val1){})时会引发错误,从而捕获代码中几乎可以肯定存在的错误,否则您可能会浪费大量的时间追踪。
    • 注意:它曾经是(在ECMAScript
      5中)strict模式将禁止重复的属性名称(例如var object =
      {foo:“bar”,foo:“baz”};)但是从ECMAScript
      2015 开始,就不再有这种情况了。
  • 使eval()更安全。 eval()在严格模式和非严格模式下的行为方式有些不同。最重要的是,在严格模式下,在eval()语句内部声明的变量和函数不会在包含范围中创建(它们是以非严格模式在包含范围中创建的,这也可能是问题的常见来源)。
  • 抛出无效的使用错误的删除符。 删除操作符(用于从对象中删除属性)不能用于对象的不可配置属性。当试图删除一个不可配置的属性时,非严格代码将自动失败,而在这种情况下,严格模式会引发错误。

4.Layout 布局

有了各个节点的样式信息和属性,但不知道各个节点的确切位置和大小,所以要通过布局将样式信息和属性转换为实际可视窗口的相对大小和位置。

结束语

Velocity的目标是保持领先的DOM动画性能和便捷。本文的重点是前者。请去VelocityJS.org学习更多关于后者的知识。

在我们结束之前,记得\一个高性能的UI不仅仅是选择合适的动画库*。页面的其余部分也应该优化。从下面这些奇妙的Google话题中学习更多:

  • Jank Free
  • Rendering Without
    Lumps
  • Faster
    Websites

    1 赞 3 收藏
    评论

图片 3

6、考虑下面的两个函数。他们都会返回同样的值吗?为什么或者为什么不?

function foo1() { return { bar: “hello” }; } function foo2() { return {
bar: “hello” }; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo1()
{
  return {
      bar: "hello"
  };
}
 
function foo2()
{
  return
  {
      bar: "hello"
  };
}

令人惊讶的是,这两个函数不会返回相同的结果。而是:

console.log(“foo1 returns:”); console.log(foo1()); console.log(“foo2
returns:”); console.log(foo2());

1
2
3
4
console.log("foo1 returns:");
console.log(foo1());
console.log("foo2 returns:");
console.log(foo2());

会产生:

foo1 returns: Object {bar: “hello”} foo2 returns: undefined

1
2
3
4
foo1 returns:
Object {bar: "hello"}
foo2 returns:
undefined

这不仅令人惊讶,而且特别令人烦恼的是,foo2()返回未定义而没有引发任何错误。

原因与JavaScript中分号在技术上是可选的事实有关(尽管忽略它们通常是非常糟糕的形式)。因此,在foo2()中遇到包含return语句的行(没有其他内容)时,会在return语句之后立即自动插入分号。

由于代码的其余部分是完全有效的,即使它没有被调用或做任何事情(它只是一个未使用的代码块,它定义了一个属性栏,它等于字符串“hello”),所以不会抛出任何错误。

这种行为也被认为是遵循了在JavaScript中将一行开头大括号放在行尾的约定,而不是在新行的开头。如此处所示,这不仅仅是JavaScript中的一种风格偏好。

5.Paint 绘制

万事俱备,最后只要将确定好位置大小的各节点,通过GPU渲染到屏幕的实际像素。

7、什么是NaN?它的类型是什么?如何可靠地测试一个值是否等于NaN?

NaN属性表示“不是数字”的值。这个特殊值是由于一个操作数是非数字的(例如“abc”/
4)或者因为操作的结果是非数字而无法执行的。

虽然这看起来很简单,但NaN有一些令人惊讶的特征,如果人们没有意识到这些特征,就会导致bug。

一方面,虽然NaN的意思是“不是数字”,但它的类型是,数字:

console.log(typeof NaN === “number”); // logs “true”

1
console.log(typeof NaN === "number");  // logs "true"

此外,NaN相比任何事情 – 甚至本身! – 是false:

console.log(NaN === NaN); // logs “false”

1
console.log(NaN === NaN);  // logs "false"

测试数字是否等于NaN的半可靠方法是使用内置函数isNaN(),但即使使用isNaN()也不是一个好的解决方案。.

一个更好的解决方案要么是使用value!==值,如果该值等于NaN,那么只会生成true。另外,ES6提供了一个新的Number.isNaN()函数 ,它与旧的全局isNaN()函数不同,也更加可靠。

Tips

  • 在上述渲染过程中,前3点可能要多次执行,比如js脚本去操作dom、更改css样式时,浏览器又要重新构建DOM、CSSOM树,重新render,重新layout、paint;
  • Layout在Paint之前,因此每次Layout重新布局(reflow
    回流)后都要重新出发Paint渲染,这时又要去消耗GPU;
  • Paint不一定会触发Layout,比如改个颜色改个背景;(repaint 重绘)
  • 图片下载完也会重新出发Layout和Paint;

图片 4

8、下面的代码输出什么?解释你的答案。

console.log(0.1 + 0.2); console.log(0.1 + 0.2 == 0.3);

1
2
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 == 0.3);

对这个问题的一个有教养的回答是:“你不能确定。它可能打印出0.3和true,或者可能不打印。
JavaScript中的数字全部用浮点精度处理,因此可能不会总是产生预期的结果。“

上面提供的示例是演示此问题的经典案例。令人惊讶的是,它会打印出来:

0.30000000000000004 false

1
2
0.30000000000000004
false

一个典型的解决方案是比较两个数字与特殊常数Number.EPSILON之间的绝对差值:

function areTheNumbersAlmostEqual(num1, num2) { return Math.abs( num1 –
num2 ) < Number.EPSILON; } console.log(areTheNumbersAlmostEqual(0.1 +
0.2, 0.3));

1
2
3
4
function areTheNumbersAlmostEqual(num1, num2) {
    return Math.abs( num1 – num2 ) < Number.EPSILON;
}
console.log(areTheNumbersAlmostEqual(0.1 + 0.2, 0.3));

讨论写函数的可能方法isInteger(x),它确定x是否是一个整数。

这听起来很平凡,事实上,ECMAscript
6为此正好引入了一个新的Number.isInteger()函数,这是微不足道的。但是,在ECMAScript
6之前,这有点复杂,因为没有提供与Number.isInteger()方法等价的方法。

问题在于,在ECMAScript规范中,整数只在概念上存在;即数值始终作为浮点值存储。

考虑到这一点,最简单,最清洁的ECMAScript-6之前的解决方案(即使将非数字值(例如字符串或空值)传递给该函数,该解决方案也具有足够的可靠性以返回false)将成为以下用法按位异或运算符:

function isInteger(x) { return (x ^ 0) === x; }

1
function isInteger(x) { return (x ^ 0) === x; }

下面的解决方案也可以工作,尽管不如上面那样高雅

function isInteger(x) { return Math.round(x) === x; }

1
function isInteger(x) { return Math.round(x) === x; }

请注意,在上面的实现中Math.ceil()或Math.floor()可以同样使用(而不是Math.round())。

或者:

function isInteger(x) { return (typeof x === ‘number’) && (x % 1 === 0);
}

1
function isInteger(x) { return (typeof x === ‘number’) && (x % 1 === 0); }

一个相当常见的不正确的解决方案如下:

function isInteger(x) { return parseInt(x, 10) === x; }

1
function isInteger(x) { return parseInt(x, 10) === x; }

虽然这个基于parseInt的方法对许多x值很有效,但一旦x变得相当大,它将无法正常工作。问题是parseInt()在解析数字之前将其第一个参数强制转换为字符串。因此,一旦数字变得足够大,其字符串表示将以指数形式呈现(例如1e

  • 21)。因此,parseInt()将尝试解析1e +
    21,但是当它到达e字符时将停止解析,因此将返回值1.观察:

> String(1000000000000000000000) ‘1e+21’

1
2
> String(1000000000000000000000)
‘1e+21’

> parseInt(1000000000000000000000, 10) 1

1
2
> parseInt(1000000000000000000000, 10)
1

> parseInt(1000000000000000000000, 10) === 1000000000000000000000
false

1
2
> parseInt(1000000000000000000000, 10) === 1000000000000000000000
false

何时触发reflow和repaint

reflow(回流): 根据Render
Tree布局(几何属性),意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树;
repaint(重绘):
意味着元素发生的改变只影响了节点的一些样式(背景色,边框颜色,文字颜色等),只需要应用新样式绘制这个元素就可以了;
reflow回流的成本开销要高于repaint重绘,一个节点的回流往往回导致子节点以及同级节点的回流;

GoogleChromeLabs
里面有一个csstriggers,列出了各个CSS属性对浏览器执行Layout、Paint、Composite的影响。

9、执行下面的代码时,按什么顺序将数字1-4记录到控制台?为什么?

(function() { console.log(1); setTimeout(function(){console.log(2)},
1000); setTimeout(function(){console.log(3)}, 0); console.log(4); })();

1
2
3
4
5
6
(function() {
    console.log(1);
    setTimeout(function(){console.log(2)}, 1000);
    setTimeout(function(){console.log(3)}, 0);
    console.log(4);
})();

这些值将按以下顺序记录:

1 4 3 2

1
2
3
4
1
4
3
2

我们先来解释一下这些可能更为明显的部分:

  • 首先显示1和4,因为它们是通过简单调用console.log()而没有任何延迟记录的
  • 在3之后显示,因为在延迟1000毫秒(即1秒)之后记录2,而在0毫秒的延迟之后记录3。

好的。但是,如果在延迟0毫秒后记录3,这是否意味着它正在被立即记录?而且,如果是这样,不应该在4之前记录它,因为4是由后面的代码行记录的吗?

答案与正确理解JavaScript事件和时间有关。 .

浏览器有一个事件循环,它检查事件队列并处理未决事件。例如,如果在浏览器繁忙时(例如,处理onclick)在后台发生事件(例如脚本onload事件),则该事件被附加到队列中。当onclick处理程序完成时,将检查队列并处理该事件(例如,执行onload脚本)。

同样,如果浏览器繁忙,setTimeout()也会将其引用函数的执行放入事件队列中。

当值为零作为setTimeout()的第二个参数传递时,它将尝试“尽快”执行指定的函数。具体来说,函数的执行放置在事件队列中,以在下一个计时器滴答时发生。但请注意,这不是直接的;该功能不会执行,直到下一个滴答声。这就是为什么在上面的例子中,调用console.log(4)发生在调用console.log(3)之前(因为调用console.log(3)是通过setTimeout调用的,所以稍微延迟了一点)。

引起reflow回流

现代浏览器会对回流做优化,它会等到足够数量的变化发生,再做一次批处理回流。

  1. 页面第一次渲染(初始化)
  2. DOM树变化(如:增删节点)
  3. Render树变化(如:padding改变)
  4. 浏览器窗口resize
  5. 获取元素的某些属性:
    浏览器为了获得正确的值也会提前触发回流,这样就使得浏览器的优化失效了,这些属性包括offsetLeft、offsetTop、offsetWidth、offsetHeight、
    scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()或者IE的currentStyle

10、编写一个简单的函数(少于160个字符),返回一个布尔值,指示字符串是否是palindrome。

如果str是回文,以下一行函数将返回true;否则,它返回false。

function isPalindrome(str) { str = str.replace(/\W/g,
”).toLowerCase(); return (str == str.split(”).reverse().join(”)); }

1
2
3
4
function isPalindrome(str) {
  str = str.replace(/\W/g, ”).toLowerCase();
  return (str == str.split(”).reverse().join(”));
}

例如:

console.log(isPalindrome(“level”)); // logs ‘true’
console.log(isPalindrome(“levels”)); // logs ‘false’
console.log(isPalindrome(“A car, a man, a maraca”)); // logs ‘true’

1
2
3
console.log(isPalindrome("level"));                   // logs ‘true’
console.log(isPalindrome("levels"));                  // logs ‘false’
console.log(isPalindrome("A car, a man, a maraca"));  // logs ‘true’

引起repaint重绘

  1. reflow回流必定引起repaint重绘,重绘可以单独触发
  2. 背景色、颜色、字体改变(注意:字体大小发生变化时,会触发回流)

11、写一个sum方法,当使用下面的语法调用时它将正常工作。

console.log(sum(2,3)); // Outputs 5 console.log(sum(2)(3)); // Outputs 5

1
2
console.log(sum(2,3));   // Outputs 5
console.log(sum(2)(3));  // Outputs 5

有(至少)两种方法可以做到这一点:

METHOD 1

function sum(x) { if (arguments.length == 2) { return arguments[0] +
arguments[1]; } else { return function(y) { return x + y; }; } }

1
2
3
4
5
6
7
function sum(x) {
  if (arguments.length == 2) {
    return arguments[0] + arguments[1];
  } else {
    return function(y) { return x + y; };
  }
}

在JavaScript中,函数提供对参数对象的访问,该对象提供对传递给函数的实际参数的访问。这使我们能够使用length属性在运行时确定传递给函数的参数的数量

如果传递两个参数,我们只需将它们相加并返回。

否则,我们假设它是以sum(2)(3)的形式被调用的,所以我们返回一个匿名函数,它将传递给sum()(在本例中为2)的参数和传递给匿名函数的参数这种情况3)。

METHOD 2

function sum(x, y) { if (y !== undefined) { return x + y; } else {
return function(y) { return x + y; }; } }

1
2
3
4
5
6
7
function sum(x, y) {
  if (y !== undefined) {
    return x + y;
  } else {
    return function(y) { return x + y; };
  }
}

当函数被调用时,JavaScript不需要参数的数量来匹配函数定义中参数的数量。如果传递的参数数量超过了函数定义中参数的数量,则超出的参数将被忽略。另一方面,如果传递的参数数量少于函数定义中的参数数量,则在函数内引用时,缺少的参数将具有未定义的值。因此,在上面的例子中,通过简单地检查第二个参数是否未定义,我们可以确定函数被调用的方式并相应地继续。

优化reflow、repaint触发次数

  • 避免逐个修改节点样式,尽量一次性修改
  • 使用DocumentFragment将需要多次修改的DOM元素缓存,最后一次性append到真实DOM中渲染
  • 可以将需要多次修改的DOM元素设置display: none,操作完再显示。(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘)
  • 避免多次读取某些属性(见上)
  • 将复杂的节点元素脱离文档流,降低回流成本

发表评论

电子邮件地址不会被公开。 必填项已用*标注