JavaScript面试大全
JavaScript面试大全
1.求y和z的值是多少?
var x = 1;
var y = 0;
var z = 0;
function add(n){n=n+1;}
y = add(x);
function add(n){n=n+3;}
z = add(x);
都为undefined,因为没有返回值。
2.JavaScript是面向对象的语言,怎么体现javascript的继承关系?
使用prototype来实现。
3.javascript怎样选中一个checkbox,怎样设置它无效?
document.all.cb1[0].disabled = true;
4.form中的input可以设置为readonly和disable,请问2者有什么区别?
readonly不可编辑,但可以选择和复制;值可以传递到后台 ;disabled不能编辑,不能复制,不能选择;值不可以传递到后台
5.js中的3种弹出式消息提醒(警告窗口,确认窗口,信息输入窗口)的命令式什么?
alert、confirm、 prompt
6.form中input是类型有哪些?
text:文本框
password:密码框
radio:单选按钮
checkbox:复选框
file:文件选择域
hidden:隐藏域
button:按钮
reset:重置按钮
submit:表单提交按钮
image:图片按钮,类似submit可以为按钮添加图片...
7.JS的2种变量范围有什么不同?
全局变量:当前页面内有效
局部变量:方法内有效
8.列举JS的3种主要数据类型,2种复合数据类型和2种特殊数据类型。
主要数据类型:string, boolean, number
复合数据类型:function, object
特殊数据类型:undefined,null
9.程序中捕获异常的方法?
window.error
try{}catch(){}finally{}
10.写出函数DateDemo的返回结果,系统时间假定为今天
function DateDemo(){
var d, s="今天日期是:";
d = new Date();
s += d.getMonth() + "/";
s += d.getDate() + "/";
s += d.getYear();
return s;
}
结果:今天日期是:7/17/2010
11.写出程序运行的结果?
for(i=0, j=0; i<10, j<6; i++, j++){
k = i + j;
}
结果:10(小心陷阱),j=5时i=5;此时k=10;再循环时j=6,i=6此时已经不满足j<6的条件了,所以循环跳出,因而k=10.
12.运行的结果?
function hi(){
var a;
alert(a);
}
结果:undefined//a声明了但未赋值,所以未定义
13.运行的结果?
function hi(){
var a = null;
alert(a);
}
结果:null
14.浏览器的对象模型?
window
顶级对象
window.alert(msg)
window.prompt()
window.confirm()
if(window.confirm()){
...
}
window.open()
window.close()
document
document.write()
history
当用户浏览网页时,浏览器保存了一个最近所访问网页的url列表。这个列表就是用history对象表示。
history.back():后退,
history.forward():前进
history.go(n):正数表示向前,负数表示向后
location
表示当前打开的窗口或框架的URL信息。
location.href:重定向
等价于location.assign(url)
location.host:类似www.qq.com:8080
navigator
表示浏览器的信息及js运行的环境
navigator.cookieEnabled:该属性表示是否启用cookie
screen
用于显示网页的显示器的大小和颜色
screen.width/screen.height:表示显示器的分辨率(总的宽度,高度)
---以上答案可www.w3schol.com.cn学习。
15.XMLHTTPRequest对象是什么?
Ajax原理~
XMLHttpRequest 对象用于在后台与服务器交换数据
16.超链接的属性target的可选值:blank, parent, self, top和框架名称有什么区别?
blank重新打开新的窗口。parent则是覆盖上层窗口,self是本窗口内,top是最顶层的窗口。
17.JS的常用对象有哪些?
String, Math, Date和Array对象、正则等内置对象。
18.innerHTML,innerText,outerHTML,outerText的区别?
innerHTML 设置或获取位于对象起始和结束标签内的 HTML
outerHTML 设置或获取对象及其内容的 HTML 形式
innerText 设置或获取位于对象起始和结束标签内的文本
outerText 设置(包括标签)或获取(不包括标签)对象的文本
19.Ajax的优缺点都有什么?
Ajax的优点:
1)页面无刷新,用户体验非常好。
2)使用异步方式与服务器通信,具有更加迅速的响应能力。
3)可以把一些服务器负担的工作转到客户端,利用客户端闲置的能力来处理,减轻服务器负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax的原则是“按需取数据”,可以最大程度的减少冗余请求和响应对服务器造成的负担。
4)基于标准化并被广泛支持的技术,不需要下载插件或者小程序。
Ajax的缺点:
1)Ajax不支持浏览器back按钮。
2)安全问题, Ajax暴露了与服务器交互的细节。
3)对搜索引擎的支持比较弱。
4)破坏了程序的异常机制。
5)不容易调试。
20.简述一下Ajax的工作原理
Ajax的核心是JavaScript对象XmlHttpRequest。该对象在Internet Explorer5中首次引入,它是一种支持异步请求的技术。简而言之,XmlHttpRequest使您可以使用JavaScript向服务器提出请求并处理响应,而不阻塞用户。
21.在JavaScript中,表达式2+5+”8″得到的结果是什么?并说明原因。
因为2和5是数字类型,“8”是字符串类型,数字类型和字符串类型相加,数字类型会被强制转化成字符串类型再相加,所以得到的结果是78。
22.JavaScript原型,原型链 ? 有什么特点?
JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型。虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。实现传统的类继承模型是很简单,但是实现 JavaScript 中的原型继承则要困难的多。
由于 JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链。
原型
10年前,我刚学习JavaScript的时候,一般都是用如下方式来写代码:
var decimalDigits = 2,
tax = 5;
function add(x, y) {
return x + y;
}
function subtract(x, y) {
return x - y;
}
//alert(add(1, 3));
通过执行各个function来得到结果,学习了原型之后,我们可以使用如下方式来美化一下代码。
原型使用方式1
在使用原型之前,我们需要先将代码做一下小修改:
var Calculator = function (decimalDigits, tax) {
this.decimalDigits = decimalDigits;
this.tax = tax;
};
然后,通过给Calculator对象的prototype属性赋值对象字面量来设定Calculator对象的原型。
Calculator.prototype = {
add: function (x, y) {
return x + y;
},
subtract: function (x, y) {
return x - y;
}
};
//alert((new Calculator()).add(1, 3));
这样,我们就可以new Calculator对象以后,就可以调用add方法来计算结果了。
原型使用方式2
第二种方式是,在赋值原型prototype的时候使用function立即执行的表达式来赋值,即如下格式:
Calculator.prototype = function () { } ();
它的好处在前面的帖子里已经知道了,就是可以封装私有的function,通过return的形式暴露出简单的使用名称,以达到public/private的效果,修改后的代码如下:
Calculator.prototype = function () {
add = function (x, y) {
return x + y;
},
subtract = function (x, y) {
return x - y;
}
return {
add: add,
subtract: subtract
}
} ();
//alert((new Calculator()).add(11, 3));
同样的方式,我们可以new Calculator对象以后调用add方法来计算结果了。
分步声明
上述使用原型的时候,有一个限制就是一次性设置了原型对象,我们再来说一下如何分来设置原型的每个属性吧。
var BaseCalculator = function () {
//为每个实例都声明一个小数位数
this.decimalDigits = 2;
};
//使用原型给BaseCalculator扩展2个对象方法
BaseCalculator.prototype.add = function (x, y) {
return x + y;
};
BaseCalculator.prototype.subtract = function (x, y) {
return x - y;
};
首先,声明了一个BaseCalculator对象,构造函数里会初始化一个小数位数的属性decimalDigits,然后通过原型属性设置2个function,分别是add(x,y)和subtract(x,y),当然你也可以使用前面提到的2种方式的任何一种,我们的主要目的是看如何将BaseCalculator对象设置到真正的Calculator的原型上。
var BaseCalculator = function() {
this.decimalDigits = 2;
};
BaseCalculator.prototype = {
add: function(x, y) {
return x + y;
},
subtract: function(x, y) {
return x - y;
}
};
创建完上述代码以后,我们来开始:
var Calculator = function () {
//为每个实例都声明一个税收数字
this.tax = 5;
};
Calculator.prototype = new BaseCalculator();
我们可以看到Calculator的原型是指向到BaseCalculator的一个实例上,目的是让Calculator集成它的add(x,y)和subtract(x,y)这2个function,还有一点要说的是,由于它的原型是BaseCalculator的一个实例,所以不管你创建多少个Calculator对象实例,他们的原型指向的都是同一个实例。
var calc = new Calculator();
alert(calc.add(1, 1));
//BaseCalculator 里声明的decimalDigits属性,在 Calculator里是可以访问到的
alert(calc.decimalDigits);
上面的代码,运行以后,我们可以看到因为Calculator的原型是指向BaseCalculator的实例上的,所以可以访问他的decimalDigits属性值,那如果我不想让Calculator访问BaseCalculator的构造函数里声明的属性值,那怎么办呢?这么办:
var Calculator = function () {
this.tax= 5;
};
Calculator.prototype = BaseCalculator.prototype;
通过将BaseCalculator的原型赋给Calculator的原型,这样你在Calculator的实例上就访问不到那个decimalDigits值了,如果你访问如下代码,那将会提升出错。
var calc = new Calculator();
alert(calc.add(1, 1));
alert(calc.decimalDigits);
重写原型
在使用第三方JS类库的时候,往往有时候他们定义的原型方法是不能满足我们的需要,但是又离不开这个类库,所以这时候我们就需要重写他们的原型中的一个或者多个属性或function,我们可以通过继续声明的同样的add代码的形式来达到覆盖重写前面的add功能,代码如下:
//覆盖前面Calculator的add() function
Calculator.prototype.add = function (x, y) {
return x + y + this.tax;
};
var calc = new Calculator();
alert(calc.add(1, 1));
这样,我们计算得出的结果就比原来多出了一个tax的值,但是有一点需要注意:那就是重写的代码需要放在最后,这样才能覆盖前面的代码。
原型链
在将原型链之前,我们先上一段代码:
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// 设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// 修正Bar.prototype.constructor为Bar本身
Bar.prototype.constructor = Bar;
var test = new Bar() // 创建Bar的一个新实例
// 原型链
test [Bar的实例]
Bar.prototype [Foo的实例]
{ foo: 'Hello World' }
Foo.prototype
{method: ...};
Object.prototype
{toString: ... /* etc. */};
上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此,它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。
属性查找
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部 - 也就是 Object.prototype - 但是仍然没有找到指定的属性,就会返回 undefined,我们来看一个例子:
function foo() {
this.add = function (x, y) {
return x + y;
}
}
foo.prototype.add = function (x, y) {
return x + y + 10;
}
Object.prototype.subtract = function (x, y) {
return x - y;
}
var f = new foo();
alert(f.add(1, 2)); //结果是3,而不是13
alert(f.subtract(1, 2)); //结果是-1
通过代码运行,我们发现subtract是安装我们所说的向上查找来得到结果的,但是add方式有点小不同,这也是我想强调的,就是属性在查找的时候是先查找自身的属性,如果没有再查找原型,再没有,再往上走,一直插到Object的原型上,所以在某种层面上说,用 for in语句遍历属性的时候,效率也是个问题。
还有一点我们需要注意的是,我们可以赋值任何类型的对象到原型上,但是不能赋值原子类型的值,比如如下代码是无效的:
function Foo() {}
Foo.prototype = 1; // 无效
hasOwnProperty函数
hasOwnProperty是Object.prototype的一个方法,它可是个好东西,他能判断一个对象是否包含自定义属性而不是原型链上的属性,因为hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。
// 修改Object.prototype
Object.prototype.bar = 1;
var foo = {goo: undefined};
foo.bar; // 1
'bar' in foo; // true
foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true
只有 hasOwnProperty 可以给出正确和期望的结果,这在遍历对象的属性时会很有用。 没有其它方法可以用来排除原型链上的属性,而不是定义在对象自身上的属性。
但有个恶心的地方是:JavaScript 不会保护 hasOwnProperty 被非法占用,因此如果一个对象碰巧存在这个属性,就需要使用外部的 hasOwnProperty 函数来获取正确的结果。
var foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Here be dragons'
};
foo.hasOwnProperty('bar'); // 总是返回 false
// 使用{}对象的 hasOwnProperty,并将其上下为设置为foo
{}.hasOwnProperty.call(foo, 'bar'); // true
当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。同时在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法,这将会避免原型对象扩展带来的干扰,我们来看一下例子:
// 修改 Object.prototype
Object.prototype.bar = 1;
var foo = {moo: 2};
for(var i in foo) {
console.log(i); // 输出两个属性:bar 和 moo
}
我们没办法改变for in语句的行为,所以想过滤结果就只能使用hasOwnProperty 方法,代码如下:
// foo 变量是上例中的
for(var i in foo) {
if (foo.hasOwnProperty(i)) {
console.log(i);
}
}
这个版本的代码是唯一正确的写法。由于我们使用了 hasOwnProperty,所以这次只输出 moo。如果不使用 hasOwnProperty,则这段代码在原生对象原型(比如 Object.prototype)被扩展时可能会出错。
总结:推荐使用 hasOwnProperty,不要对代码运行的环境做任何假设,不要假设原生对象是否已经被扩展了。
- eval()是做什么的?
eval()是“魔鬼” - null,undefined 的区别?
大多数计算机语言,有且仅有一个表示"无"的值,比如,C语言的NULL,Java语言的null,Python语言的None,Ruby语言的nil。
有点奇怪的是,JavaScript语言居然有两个表示"无"的值:undefined和null。这是为什么?
undefined vs. null
一、相似性
在JavaScript中,将一个变量赋值为undefined或null,老实说,几乎没区别。
var a = undefined;
var a = null;
上面代码中,a变量分别被赋值为undefined和null,这两种写法几乎等价。
undefined和null在if语句中,都会被自动转为false,相等运算符甚至直接报告两者相等。
if (!undefined)
console.log('undefined is false');
// undefined is false
if (!null)
console.log('null is false');
// null is false
undefined == null
// true
上面代码说明,两者的行为是何等相似!
既然undefined和null的含义与用法都差不多,为什么要同时设置两个这样的值,这不是无端增加JavaScript的复杂度,令初学者困扰吗?Google公司开发的JavaScript语言的替代品Dart语言,就明确规定只有null,没有undefined!
二、历史原因
最近,我在读新书《Speaking JavaScript》时,意外发现了这个问题的答案!
原来,这与JavaScript的历史有关。1995年JavaScript诞生时,最初像Java一样,只设置了null作为表示"无"的值。
根据C语言的传统,null被设计成可以自动转为0。
Number(null)
// 0
5 + null
// 5
但是,JavaScript的设计者Brendan Eich,觉得这样做还不够,有两个原因。
首先,null像在Java里一样,被当成一个对象。但是,JavaScript的数据类型分成原始类型(primitive)和合成类型(complex)两大类,Brendan Eich觉得表示"无"的值最好不是对象。
其次,JavaScript的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。Brendan Eich觉得,如果null自动转为0,很不容易发现错误。
因此,Brendan Eich又设计了一个undefined。
三、最初设计
JavaScript的最初版本是这样区分的:null是一个表示"无"的对象,转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。
Number(undefined)
// NaN
5 + undefined
// NaN
四、目前的用法
但是,上面这样的区分,在实践中很快就被证明不可行。目前,null和undefined基本是同义的,只有一些细微的差别。
null表示"没有对象",即该处不应该有值。典型用法是:
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
Object.getPrototypeOf(Object.prototype)
// null
undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
var i;
i // undefined
function f(x){console.log(x)}
f() // undefined
var o = new Object();
o.p // undefined
var x = f();
x // undefined
(完)
来看看大神阮一峰的解释吧
- 写一个通用的事件侦听器函数。
js事件监听是学习js过程中必然要学习和掌握的。下面是js事件监听的代码
function addEventHandler(target, type, func) {
if (target.addEventListener)
target.addEventListener(type, func, false);
else if (target.attachEvent)
target.attachEvent("on" + type, func);
else target["on" + type] = func;
}
用法1:
function test(){alert("3");}
addEventHandler(Button1, "click", test );
用法2:
addEventHandler(Button1,"click", function() { alert("3"); } );
有人问我,为什么我要用事件监听呢?因为我在target后面加一个.onclick或者.onmouseover 等等的事
件,各个浏览器都是完全兼容的啊。下面几点就说明我们为什么要使用事件监听器。
第一:当同一个对象使用.onclick的写法触发多个方法的时候,后一个方法会把前一个方法覆盖掉,也就
是说,在对象的onclick事件发生时,只会执行最后绑定的方法。而是用事件监听则不会有覆盖的现象,
每个绑定的事件都会被执行。但是IE家族先执行后绑定的方法,也就是逆绑定顺序执行方法,其他浏览器
按绑定次数顺序执行方法。
第二:也是最重要的一点,采用事件监听给对象绑定方法后,可以解除相应的绑定,写法如下
function removeEventHandler(target, type, func) {
if (target.removeEventListener)
target.removeEventListener(type, func, false);
else if (target.detachEvent)
target.detachEvent("on" + type, func);
else delete target["on" + type];
}
用法:
function test(){alert("3");}
removeEventHandler(Button1,"click",test);
说明:如果你使用的是方法2绑定,不管你是匿名还是有名的,在无法移除,因为添加和移除里是定义了两个对象了(不懂的可以参考我的JS的对象操作),下面有相关代码;
第三:解除绑定事件的时候一定要用函数的句柄,把整个函数写上是无法解除绑定的。看实例:
错误的写法:
addEventHandler(Button1, "click", function() { alert("3"); } );
removeEventHandler(Button1, "click", function() { alert("3"); });
正确的写法:
function test(){alert("3");}
addEventHandler(Button1, "click", test );
removeEventHandler(Button1, "click", test);
下面为整个HTML代码示例:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1.0-Transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Javascript事件监听</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<button id="Button1">测试</button>
<script type="text/javascript">
function addEventHandler(target, type, func) {
if (target.addEventListener)
target.addEventListener(type, func, false);
else if (target.attachEvent)
target.attachEvent("on" + type, func);
else target["on" + type] = func;
}
function removeEventHandler(target, type, func) {
if (target.removeEventListener)
target.removeEventListener(type, func, false);
else if (target.detachEvent)
target.detachEvent("on" + type, func);
else delete target["on" + type];
}
var Button1 = document.getElementById("Button1");
var test1 = function() { alert(1); };
function test2() {alert("2")}
addEventHandler(Button1,"click",test1);
addEventHandler(Button1,"click",test2 );
addEventHandler(Button1,"click", function() { alert("3"); } );
addEventHandler(Button1,"click", function() { alert("4"); } );
removeEventHandler(Button1,"click",test1);
removeEventHandler(Button1,"click",test2);
removeEventHandler(Button1,"click",function() { alert("3"); });
</script>
</body>
</html>
JS事件监听器
- Node.js的适用场景?
一. NodeJS的特点
我们先来看看NodeJS官网上的介绍:
Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.
其特点为:
1. 它是一个Javascript运行环境
2. 依赖于Chrome V8引擎进行代码解释
3. 事件驱动
4. 非阻塞I/O
5. 轻量、可伸缩,适于实时数据交互应用
6. 单进程,单线程
二. NodeJS带来的对系统瓶颈的解决方案
它的出现确实能为我们解决现实当中系统瓶颈提供了新的思路和方案,下面我们看看它能解决什么问题。
1. 并发连接
举个例子,想象一个场景,我们在银行排队办理业务,我们看看下面两个模型。
(1)系统线程模型:
系统线程模型
这种模型的问题显而易见,服务端只有一个线程,并发请求(用户)到达只能处理一个,其余的要先等待,这就是阻塞,正在享受服务的请求阻塞后面的请求了。
(2)多线程、线程池模型:
多线程、线程池模型
这个模型已经比上一个有所进步,它调节服务端线程的数量来提高对并发请求的接收和响应,但并发量高的时候,请求仍然需要等待,它有个更严重的问题。到代码层面上来讲,我们看看客户端请求与服务端通讯的过程:
客户端请求与服务端通讯的过程
服务端与客户端每建立一个连接,都要为这个连接分配一套配套的资源,主要体现为系统内存资源,以PHP为例,维护一个连接可能需要20M的内存。这就是为什么一般并发量一大,就需要多开服务器。
那么NodeJS是怎么解决这个问题的呢?我们来看另外一个模型,想象一下我们在快餐店点餐吃饭的场景。
(3)异步、事件驱动模型
异步、事件驱动模型
我们同样是要发起请求,等待服务器端响应;但是与银行例子不同的是,这次我们点完餐后拿到了一个号码,拿到号码,我们往往会在位置上等待,而在我们后面的请求会继续得到处理,同样是拿了一个号码然后到一旁等待,接待员能一直进行处理。
等到饭菜做号了,会喊号码,我们拿到了自己的饭菜,进行后续的处理(吃饭)。这个喊号码的动作在NodeJS中叫做回调(Callback),能在事件(烧菜,I/O)处理完成后继续执行后面的逻辑(吃饭),这体现了NodeJS的显著特点,异步机制、事件驱动整个过程没有阻塞新用户的连接(点餐),也不需要维护已经点餐的用户与厨师的连接。
基于这样的机制,理论上陆续有用户请求连接,NodeJS都可以进行响应,因此NodeJS能支持比Java、PHP程序更高的并发量虽然维护事件队列也需要成本,再由于NodeJS是单线程,事件队列越长,得到响应的时间就越长,并发量上去还是会力不从心。
总结一下NodeJS是怎么解决并发连接这个问题的:更改连接到服务器的方式,每个连接发射(emit)一个在NodeJS引擎进程中运行的事件(Event),放进事件队列当中,而不是为每个连接生成一个新的OS线程(并为其分配一些配套内存)。
2. I/O阻塞
NodeJS解决的另外一个问题是I/O阻塞,看看这样的业务场景:需要从多个数据源拉取数据,然后进行处理。
(1)串行获取数据,这是我们一般的解决方案,以PHP为例I/O阻塞-PHP为例
假如获取profile和timeline操作各需要1S,那么串行获取就需要2S。
(2)NodeJS非阻塞I/O,发射/监听事件来控制执行过程
非I/O阻塞-PHP为例
NodeJS遇到I/O事件会创建一个线程去执行,然后主线程会继续往下执行的,因此,拿profile的动作触发一个I/O事件,马上就会执行拿timeline的动作,两个动作并行执行,假如各需要1S,那么总的时间也就是1S。它们的I/O操作执行完成后,发射一个事件,profile和timeline,事件代理接收后继续往下执行后面的逻辑,这就是NodeJS非阻塞I/O的特点。
总结一下:Java、PHP也有办法实现并行请求(子线程),但NodeJS通过回调函数(Callback)和异步机制会做得很自然。
三. NodeJS的优缺点
优点:1. 高并发(最重要的优点)
2. 适合I/O密集型应用
缺点:1. 不适合CPU密集型应用;CPU密集型应用给Node带来的挑战主要是:由于JavaScript单线程的原因,如果有长时间运行的计算(比如大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起;
解决方案:分解大型运算任务为多个小任务,使得运算能够适时释放,不阻塞I/O调用的发起;
2. 只支持单核CPU,不能充分利用CPU
3. 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃
原因:单进程,单线程
解决方案:(1)Nnigx反向代理,负载均衡,开多个进程,绑定多个端口;
(2)开多个进程监听同一个端口,使用cluster模块;
4. 开源组件库质量参差不齐,更新快,向下不兼容
5. Debug不方便,错误没有stack trace
四. 适合NodeJS的场景
1. RESTful API
这是NodeJS最理想的应用场景,可以处理数万条连接,本身没有太多的逻辑,只需要请求API,组织数据进行返回即可。它本质上只是从某个数据库中查找一些值并将它们组成一个响应。由于响应是少量文本,入站请求也是少量的文本,因此流量不高,一台机器甚至也可以处理最繁忙的公司的API需求。
2. 统一Web应用的UI层
目前MVC的架构,在某种意义上来说,Web开发有两个UI层,一个是在浏览器里面我们最终看到的,另一个在server端,负责生成和拼接页面。
不讨论这种架构是好是坏,但是有另外一种实践,面向服务的架构,更好的做前后端的依赖分离。如果所有的关键业务逻辑都封装成REST调用,就意味着在上层只需要考虑如何用这些REST接口构建具体的应用。那些后端程序员们根本不操心具体数据是如何从一个页面传递到另一个页面的,他们也不用管用户数据更新是通过Ajax异步获取的还是通过刷新页面。
3. 大量Ajax请求的应用
例如个性化应用,每个用户看到的页面都不一样,缓存失效,需要在页面加载的时候发起Ajax请求,NodeJS能响应大量的并发请求。 总而言之,NodeJS适合运用在高并发、I/O密集、少量业务逻辑的场景。
五. 结尾
其实NodeJS能实现几乎一切的应用,我们考虑的点只是适不适合用它来做。
NodeJS优缺点及适用场景讨论
- 介绍js的基本数据类型。
js中有5种数据类型:Undefined、Null、Boolean、Number和String。
还有一种复杂的数据类型Object,Object本质是一组无序的名值对组成的。
Undefined类型只有一个值,即undefined,使用var声明变量,但是未对初始化的,这个变量就是Undefined类型的,例子:
var
i;
alert(i == undefined);//true
var i;与var i =
undefined;这两句是等价的。
包含Undefined值的变量和未定义的变量是不一样的。
Null类型也只有一个值:null.null表示一个空对象的指针。
Boolean类型:只有两个字面量true和false。但是js中多有的变量都可以使用Boolean()函数转换成一个Boolean类型的值。
Number类型:整数和浮点数。NaN:Not
a Number。这个数值用于本来要返回一个数值,但是却未能放回一个数值的情况,以防止报错。例如:1/0
返回的就是NaN。NaN的特点:1、任何涉及NaN的操作都会返回NaN。2、NaN对任何值都不相等,包括自己NaN本身。
针对NaN特性,JS内置了isNaN()函数,来确定数值是不是NaN类型。
String类型:略
typeof操作符:对一个变量进行推断变量的类型,可能返回以下字符串:
"undefined"
如果这个值,未定义或者为初始化
"boolean" 布尔值
"string" 字符串
"number" 数值
"object"
对象
"function" 函数
用法:typeof 95; 或者 typeof(95); 会返回"number".
- Javascript如何实现继承?
js继承有5种实现方式:
1、继承第一种方式:对象冒充
function Parent(username){
this.username = username;
this.hello = function(){
alert(this.username);
}
}
function Child(username,password){
//通过以下3行实现将Parent的属性和方法追加到Child中,从而实现继承
//第一步:this.method是作为一个临时的属性,并且指向Parent所指向的对象,
//第二步:执行this.method方法,即执行Parent所指向的对象函数
//第三步:销毁this.method属性,即此时Child就已经拥有了Parent的所有属性和方法
this.method = Parent;
this.method(username);//最关键的一行
delete this.method;
this.password = password;
this.world = function(){
alert(this.password);
}
}
var parent = new Parent("zhangsan");
var child = new Child("lisi","123456");
parent.hello();
child.hello();
child.world();
2、继承第二种方式:call()方法方式
call方法是Function类中的方法
call方法的第一个参数的值赋值给类(即方法)中出现的this
call方法的第二个参数开始依次赋值给类(即方法)所接受的参数
function test(str){
alert(this.name + " " + str);
}
var object = new Object();
object.name = "zhangsan";
test.call(object,"langsin");//此时,第一个参数值object传递给了test类(即方法)中出现的this,而第二个参数"langsin"则赋值给了test类(即方法)的str
function Parent(username){
this.username = username;
this.hello = function(){
alert(this.username);
}
}
function Child(username,password){
Parent.call(this,username);
this.password = password;
this.world = function(){
alert(this.password);
}
}
var parent = new Parent("zhangsan");
var child = new Child("lisi","123456");
parent.hello();
child.hello();
child.world();
3、继承的第三种方式:apply()方法方式
apply方法接受2个参数,
A、第一个参数与call方法的第一个参数一样,即赋值给类(即方法)中出现的this
B、第二个参数为数组类型,这个数组中的每个元素依次赋值给类(即方法)所接受的参数
function Parent(username){
this.username = username;
this.hello = function(){
alert(this.username);
}
}
function Child(username,password){
Parent.apply(this,new Array(username));
this.password = password;
this.world = function(){
alert(this.password);
}
}
var parent = new Parent("zhangsan");
var child = new Child("lisi","123456");
parent.hello();
child.hello();
child.world();
4、继承的第四种方式:原型链方式,即子类通过prototype将所有在父类中通过prototype追加的属性和方法都追加到Child,从而实现了继承
function Person(){
}
Person.prototype.hello = "hello";
Person.prototype.sayHello = function(){
alert(this.hello);
}
function Child(){
}
Child.prototype = new Person();//这行的作用是:将Parent中将所有通过prototype追加的属性和方法都追加到Child,从而实现了继承
Child.prototype.world = "world";
Child.prototype.sayWorld = function(){
alert(this.world);
}
var c = new Child();
c.sayHello();
c.sayWorld();
5、继承的第五种方式:混合方式
混合了call方式、原型链方式
function Parent(hello){
this.hello = hello;
}
Parent.prototype.sayHello = function(){
alert(this.hello);
}
function Child(hello,world){
Parent.call(this,hello);//将父类的属性继承过来
this.world = world;//新增一些属性
}
Child.prototype = new Parent();//将父类的方法继承过来
Child.prototype.sayWorld = function(){//新增一些方法
alert(this.world);
}
var c = new Child("zhangsan","lisi");
c.sayHello();
c.sayWorld();
JS的五种继承实现方式
- [”1”, “2”, “3”].map(parseInt) 答案是多少?
http://www.html-js.com/article/1809
- 如何创建一个对象? (画出此对象的内存图)
- 谈谈This对象的理解。
深入理解JavaScript中的this
- 事件、IE与火狐的事件机制有什么区别? 如何阻止冒泡?
① 我们在网页中的某个操作(有的操作对应多个事件)。例如:当我们点击一个按钮就会产生一个事件。是可以被 JavaScript 侦测到的行为。
② 事件处理机制:IE是事件冒泡、火狐是 事件捕获;
③ ev.stopPropagation();
- 什么是闭包(closure),为什么要用它?
闭包;闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
- “use strict”;是什么意思 ? 使用它的好处和坏处分别是什么?
JavaScript严谨模式(Strict Mode)提升开发效率和质量
- 如何判断Javascript对象是否存在?
链接
- new操作符具体干了什么呢?
理解js中的new
- Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?
hasOwnProperty
- JSON 的了解?
JSON(JavaScript Object Notation)
是一种轻量级的数据交换格式。它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小{'age':'12',
'name':'back'}
- js延迟加载的方式有哪些?
defer和async、动态创建DOM方式(用得最多)、按需异步载入js
- ajax 是什么?
- 同步和异步的区别?
- 如何解决跨域问题?
js跨域及解决方案
- 模块化怎么做?
- AMD(Modules/Asynchronous-Definition)、CMD(Common Module
Definition)规范区别?
看下大牛玉伯对AMD和CMD的理解
- 异步加载的方式有哪些?
异步加载JS的五种方式
- .call() 和 .apply() 的区别?
区别
- jQuery与jQuery UI 有啥区别?
jQuery是JS的一个库,jQueyr UI 可以看做是jQuery的插件
- JQuery的源码看过吗?能不能简单说一下它的实现原理?
- jquery 中如何将数组转化为json字符串,然后再转化回来?
- 针对 jQuery 的优化方法?
- JavaScript中的作用域与变量声明提升?
如何编写高性能的Javascript?
很多JavaScript引擎,如Google的V8引擎(被Chrome和Node所用),是专门为需要快速执行的大型JavaScript应用所设计的。如果你是一个开发者,并且关心内存使用情况与页面性能,你应该了解用户浏览器中的JavaScript引擎是如何运作的。无论是V8,SpiderMonkey的(Firefox)的Carakan(Opera),Chakra(IE)或其他引擎,这样做可以帮助你更好地优化你的应用程序。这并不是说应该专门为某一浏览器或引擎做优化,千万别这么做。
但是,你应该问自己几个问题:
•在我的代码里,是否可以使代码更高效一些
•主流的JavaScript引擎都做了哪些优化
•什么是引擎无法优化的,垃圾回收器(GC)是否能回收我所期望的东西
加载快速的网站就像是一辆快速的跑车,需要用到特别定制的零件
编写高性能代码时有一些常见的陷阱,在这篇文章中,我们将展示一些经过验证的、更好的编写代码方式。
那么,JavaScript在V8里是如何工作的?
如果你对JS引擎没有较深的了解,开发一个大型Web应用也没啥问题,就好比会开车的人也只是看过引擎盖而没有看过车盖内的引擎一样。鉴于Chrome是我的浏览器首选,所以谈一下它的JavaScript引擎。V8是由以下几个核心部分组成:
•一个基本的编译器,它会在代码执行前解析JavaScript代码并生成本地机器码,而不是执行字节码或简单地解释它。这些代码最开始并不是高度优化的。
•V8将对象构建为对象模型。在JavaScript中对象表现为关联数组,但是在V8中对象被看作是隐藏的类,一个为了优化查询的内部类型系统。
•运行时分析器监视正在运行的系统,并标识了“hot”的函数(例如花费很长时间运行的代码)。
•优化编译器重新编译和优化那些被运行时分析器标识为“hot”的代码,并进行“内联”等优化(例如用被调用者的主体替换函数调用的位置)。
•V8支持去优化,这意味着优化编译器如果发现对于代码优化的假设过于乐观,它会舍弃优化过的代码。
•V8有个垃圾收集器,了解它是如何工作的和优化JavaScript一样重要。
垃圾回收
垃圾回收是内存管理的一种形式,其实就是一个收集器的概念,尝试回收不再被使用的对象所占用的内存。在JavaScript这种垃圾回收语言中,应用程序中仍在被引用的对象不会被清除。
手动消除对象引用在大多数情况下是没有必要的。通过简单地把变量放在需要它们的地方(理想情况下,尽可能是局部作用域,即它们被使用的函数里而不是函数外层),一切将运作地很好。
垃圾回收器尝试回收内存
在JavaScript中,是不可能强制进行垃圾回收的。你不应该这么做,因为垃圾收集过程是由运行时控制的,它知道什么是最好的清理时机。
“消除引用”的误解
网上有许多关于JavaScript内存回收的讨论都谈到delete这个关键字,虽然它可以被用来删除对象(map)中的属性(key),但有部分开发者认为它可以用来强制“消除引用”。建议尽可能避免使用delete,在下面的例子中delete o.x 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。
var o = { x:1 };
delete o.x; // true
o.x; // undefined
你会很容易地在流行的JS库中找到引用删除——这是具有语言目的性的。这里需要注意的是避免在运行时修改”hot”对象的结构。JavaScript引擎可以检测出这种“hot”的对象,并尝试对其进行优化。如果对象在生命周期中其结构没有较大的改变,引擎将会更容易优化对象,而delete操作实际上会触发这种较大的结构改变,因此不利于引擎的优化。
对于null是如何工作也是有误解的。将一个对象引用设置为null,并没有使对象变“空”,只是将它的引用设置为空而已。使用o.x= null比使用delete会更好些,但可能也不是很必要。
var o = { x:1 };
o = null;
o; // null
o.x // TypeError
如果此引用是当前对象的最后引用,那么该对象将被作为垃圾回收。如果此引用不是当前对象的最后引用,则该对象是可访问的且不会被垃圾回收。
另外需要注意的是,全局变量在页面的生命周期里是不被垃圾回收器清理的。无论页面打开多久,JavaScript运行时全局对象作用域中的变量会一直存在。
var myGlobalNamespace = {};
全局对象只会在刷新页面、导航到其他页面、关闭标签页或退出浏览器时才会被清理。函数作用域的变量将在超出作用域时被清理,即退出函数时,已经没有任何引用,这样的变量就被清理了。
经验法则
为了使垃圾回收器尽早收集尽可能多的对象,不要hold着不再使用的对象。这里有几件事需要记住:
•正如前面提到的,在合适的范围内使用变量是手动消除引用的更好选择。即一个变量只在一个函数作用域中使用,就不要在全局作用域声明它。这意味着更干净省心的代码。
•确保解绑那些不再需要的事件监听器,尤其是那些即将被销毁的DOM对象所绑定的事件监听器。
•如果使用的数据缓存在本地,确保清理一下缓存或使用老化机制,以避免大量不被重用的数据被存储。
函数
接下来,我们谈谈函数。正如我们已经说过,垃圾收集的工作原理,是通过回收不再是访问的内存块(对象)。为了更好地说明这一点,这里有一些例子。
function foo(){
var bar = new LargeObject();
bar.someCall();
}
当foo返回时,bar指向的对象将会被垃圾收集器自动回收,因为它已没有任何存在的引用了。
对比一下:
function foo(){
var bar = new LargeObject();
bar.someCall();
return bar;
}
// somewhere else
var b = foo();
现在我们有一个引用指向bar对象,这样bar对象的生存周期就从foo的调用一直持续到调用者指定别的变量b(或b超出范围)。
闭包(CLOSURES)
当你看到一个函数,返回一个内部函数,该内部函数将获得范围外的访问权,即使在外部函数执行之后。这是一个基本的闭包 —— 可以在特定的上下文中设置的变量的表达式。例如:
function sum (x){
function sumIt(y){
return x + y;
};
return sumIt;
}
// Usage
var sumA = sum(4);
var sumB = sumA(3);
console.log(sumB); // Returns 7
在sum调用上下文中生成的函数对象(sumIt)是无法被回收的,它被全局变量(sumA)所引用,并且可以通过sumA(n)调用。
让我们来看看另外一个例子,这里我们可以访问变量largeStr吗?
var a = function (){
var largeStr = new Array(1000000).join(‘x’);
return function (){
return largeStr;
};
}();
是的,我们可以通过a()访问largeStr,所以它没有被回收。下面这个呢?
var a = function (){
var smallStr = ‘x’;
var largeStr = new Array(1000000).join(‘x’);
return function (n){
return smallStr;
};
}();
我们不能再访问largeStr了,它已经是垃圾回收候选人了。【译者注:因为largeStr已不存在外部引用了】
定时器
最糟的内存泄漏地方之一是在循环中,或者在setTimeout()/ setInterval()中,但这是相当常见的。思考下面的例子:
var myObj = {
callMeMaybe:function (){
var myRef = this;
var val = setTimeout(function (){
console.log(‘Time is running out!’);
myRef.callMeMaybe();
},1000);
}
};
如果我们运行myObj.callMeMaybe();来启动定时器,可以看到控制台每秒打印出“Time is running out!”。如果接着运行myObj = null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。
同样值得牢记的是,setTimeout/setInterval调用(如函数)中的引用,将需要执行和完成,才可以被垃圾收集。
当心性能陷阱
永远不要优化代码,直到你真正需要。现在经常可以看到一些基准测试,显示N比M在V8中更为优化,但是在模块代码或应用中测试一下会发现,这些优化真正的效果比你期望的要小的多。
做的过多还不如什么都不做.
比如我们想要创建这样一个模块:
•需要一个本地的数据源包含数字ID
•绘制包含这些数据的表格
•添加事件处理程序,当用户点击的任何单元格时切换单元格的css class
这个问题有几个不同的因素,虽然也很容易解决。我们如何存储数据,如何高效地绘制表格并且append到DOM中,如何更优地处理表格事件?
面对这些问题最开始(天真)的做法是使用对象存储数据并放入数组中,使用jQuery遍历数据绘制表格并append到DOM中,最后使用事件绑定我们期望地点击行为。
注意:这不是你应该做的
var moduleA = function (){
return {
data:dataArrayObject,
init:function (){
this.addTable();
this.addEvents();
},
addTable:function (){
for (var i = 0; i <rows; i++){
$tr = $(‘<tr></tr>’);
for (var j = 0; j <this.data.length; j++){
$tr.append(‘<td>’ + this.data[j][‘id’]+ ‘</td>’);
}
$tr.appendTo($tbody);
}
},
addEvents:function (){
$(‘table td’).on(‘click’,function (){
$(this).toggleClass(‘active’);
});
}
};
}();
这段代码简单有效地完成了任务。
但在这种情况下,我们遍历的数据只是本应该简单地存放在数组中的数字型属性ID。有趣的是,直接使用DocumentFragment和本地DOM方法比使用jQuery(以这种方式)来生成表格是更优的选择,当然,事件代理比单独绑定每个td具有更高的性能。
要注意虽然jQuery在内部使用DocumentFragment,但是在我们的例子中,代码在循环内调用append并且这些调用涉及到一些其他的小知识,因此在这里起到的优化作用不大。希望这不会是一个痛点,但请务必进行基准测试,以确保自己代码ok。
对于我们的例子,上述的做法带来了(期望的)性能提升。事件代理对简单的绑定是一种改进,可选的DocumentFragment也起到了助推作用。
var moduleD = function (){
return {
data:dataArray,
init:function (){
this.addTable();
this.addEvents();
},
addTable:function (){
var td,tr;
var frag = document.createDocumentFragment();
var frag2 = document.createDocumentFragment();
for (var i = 0; i <rows; i++){
tr = document.createElement(‘tr’);
for (var j = 0; j <this.data.length; j++){
td = document.createElement(‘td’);
td.appendChild(document.createTextNode(this.data[j]));
frag2.appendChild(td);
}
tr.appendChild(frag2);
frag.appendChild(tr);
}
tbody.appendChild(frag);
},
addEvents:function (){
$(‘table’).on(‘click’,‘td’,function (){
$(this).toggleClass(‘active’);
});
}
};
}();
接下来看看其他提升性能的方式。你也许曾经在哪读到过使用原型模式比模块模式更优,或听说过使用JS模版框架性能更好。有时的确如此,不过使用它们其实是为了代码更具可读性。对了,还有预编译!让我们看看在实践中表现的如何?
moduleG = function (){};
moduleG.prototype.data = dataArray;
moduleG.prototype.init = function (){
this.addTable();
this.addEvents();
};
moduleG.prototype.addTable = function (){
var template = _.template($(‘#template’).text());
var html = template({‘data’ :this.data});
$tbody.append(html);
};
moduleG.prototype.addEvents = function (){
$(‘table’).on(‘click’,‘td’,function (){
$(this).toggleClass(‘active’);
});
};
var modG = new moduleG();
事实证明,在这种情况下的带来的性能提升可以忽略不计。模板和原型的选择并没有真正提供更多的东西。也就是说,性能并不是开发者使用它们的原因,给代码带来的可读性、继承模型和可维护性才是真正的原因。
更复杂的问题包括高效地在canvas上绘制图片和操作带或不带类型数组的像素数据。
在将一些方法用在你自己的应用之前,一定要多了解这些方案的基准测试。也许有人还记得JS模版的shoot-off和随后的扩展版。你要搞清楚基准测试不是存在于你看不到的那些虚拟应用,而是应该在你的实际代码中去测试带来的优化。
V8优化技巧
详细介绍了每个V8引擎的优化点在本文讨论范围之外,当然这里也有许多值得一提的技巧。记住这些技巧你就能减少那些性能低下的代码了。
•特定模式可以使V8摆脱优化的困境,比如说try-catch。欲了解更多有关哪些函数能或不能进行优化,你可以在V8的脚本工具d8中使用–trace-opt file.js命令。
•如果你关心速度,尽量使你的函数职责单一,即确保变量(包括属性,数组,函数参数)只使用相同隐藏类包含的对象。举个例子,别这么干:
function add(x,y){
return x+y;
}
add(1,2);
add(‘a’,‘b’);
add(my_custom_object,undefined);
•不要加载未初始化或已删除的元素。如果这么做也不会出现什么错误,但是这样会使速度变慢。
•不要使函数体过大,这样会使得优化更加困难。
更多内容可以去看Daniel Clifford在Google I/O的分享Breaking the JavaScript Speed Limit with V8。Optimizing For V8 — A Series也非常值得一读。
对象VS数组:我应该用哪个?
•如果你想存储一串数字,或者一些相同类型的对象,使用一个数组。
•如果你语义上需要的是一堆的对象的属性(不同类型的),使用一个对象和属性。这在内存方面非常高效,速度也相当快。
•整数索引的元素,无论存储在一个数组或对象中,都要比遍历对象的属性快得多。
•对象的属性比较复杂:它们可以被setter们创建,具有不同的枚举性和可写性。数组中则不具有如此的定制性,而只存在有和无这两种状态。在引擎层面,这允许更多存储结构方面的优化。特别是当数组中存在数字时,例如当你需要容器时,不用定义具有x,y,z属性的类,而只用数组就可以了。
JavaScript中对象和数组之间只有一个的主要区别,那就是数组神奇的length属性。如果你自己来维护这个属性,那么V8中对象和数组的速度是一样快的。
使用对象时的技巧
•使用一个构造函数来创建对象。这将确保它创建的所有对象具有相同的隐藏类,并有助于避免更改这些类。作为一个额外的好处,它也略快于Object.create()
•你的应用中,对于使用不同类型的对象和其复杂度(在合理的范围内:长原型链往往是有害的,呈现只有一个极少数属性的对象比大对象会快一点)是有没限制的。对于“hot”对象,尽量保持短原型链,并且少属性。
对象克隆
对于应用程序开发人员,对象克隆是一个常见的问题。虽然各种基准测试可以证明V8对这个问题处理得很好,但仍要小心。复制大的东西通常是较慢的——不要这么做。JS中的for..in循环尤其糟糕,因为它有着恶魔般的规范,并且无论是在哪个引擎中,都可能永远不会比任何对象快。
当你一定要在关键性能代码路径上复制对象时,使用数组或一个自定义的“拷贝构造函数”功能明确地复制每个属性。这可能是最快的方式:
function clone(original){
this.foo = original.foo;
this.bar = original.bar;
}
var copy = new clone(original);
模块模式中缓存函数
使用模块模式时缓存函数,可能会导致性能方面的提升。参阅下面的例子,因为它总是创建成员函数的新副本,你看到的变化可能会比较慢。
另外请注意,使用这种方法明显更优,不仅仅是依靠原型模式(经过jsPerf测试确认)。
使用模块模式或原型模式时的性能提升
这是一个原型模式与模块模式的性能对比测试:
// Prototypal pattern
Klass1 = function (){}
Klass1.prototype.foo = function (){
log(‘foo’);
}
Klass1.prototype.bar = function (){
log(‘bar’);
}
// Module pattern
Klass2 = function (){
var foo = function (){
log(‘foo’);
},
bar = function (){
log(‘bar’);
};
return {
foo:foo,
bar:bar
}
}
// Module pattern with cached functions
var FooFunction = function (){
log(‘foo’);
};
var BarFunction = function (){
log(‘bar’);
};
Klass3 = function (){
return {
foo:FooFunction,
bar:BarFunction
}
}
// Iteration tests
// Prototypal
var i = 1000,
objs = [];
while (i--){
var o = new Klass1()
objs.push(new Klass1());
o.bar;
o.foo;
}
// Module pattern
var i = 1000,
objs = [];
while (i--){
var o = Klass2()
objs.push(Klass2());
o.bar;
o.foo;
}
// Module pattern with cached functions
var i = 1000,
objs = [];
while (i--){
var o = Klass3()
objs.push(Klass3());
o.bar;
o.foo;
}
// See the test for full details
使用数组时的技巧
接下来说说数组相关的技巧。在一般情况下,不要删除数组元素,这样将使数组过渡到较慢的内部表示。当索引变得稀疏,V8将会使元素转为更慢的字典模式。
数组字面量
数组字面量非常有用,它可以暗示VM数组的大小和类型。它通常用在体积不大的数组中。
// Here V8 can see that you want a 4-element array containing numbers:
var a = [1,2,3,4];
// Don‘t do this:
a = []; // Here V8 knows nothing about the array
for(var i = 1; i <= 4; i++){
a.push(i);
}
存储单一类型VS多类型
将混合类型(比如数字、字符串、undefined、true/false)的数据存在数组中绝不是一个好想法。例如var arr = [1, “1”, undefined, true, “true”]
类型推断的性能测试
正如我们所看到的结果,整数的数组是最快的。
稀疏数组与满数组
当你使用稀疏数组时,要注意访问元素将远远慢于满数组。因为V8不会分配一整块空间给只用到部分空间的数组。取而代之的是,它被管理在字典中,既节约了空间,但花费访问的时间。
稀疏数组与满数组的测试
预分配空间VS动态分配
不要预分配大数组(如大于64K的元素),其最大的大小,而应该动态分配。在我们这篇文章的性能测试之前,请记住这只适用部分JavaScript引擎。
空字面量与预分配数组在不同的浏览器进行测试
Nitro (Safari)对预分配的数组更有利。而在其他引擎(V8,SpiderMonkey)中,预先分配并不是高效的。
预分配数组测试
// Empty array
var arr = [];
for (var i = 0; i <1000000; i++){
arr[i]= i;
}
// Pre-allocated array
var arr = new Array(1000000);
for (var i = 0; i <1000000; i++){
arr[i]= i;
}
优化你的应用
在Web应用的世界中,速度就是一切。没有用户希望用一个要花几秒钟计算某列总数或花几分钟汇总信息的表格应用。这是为什么你要在代码中压榨每一点性能的重要原因。
理解和提高应用程序的性能是非常有用的同时,它也是困难的。我们推荐以下的步骤来解决性能的痛点:
•测量:在您的应用程序中找到慢的地方(约45%)
•理解:找出实际的问题是什么(约45%)
•修复它! (约10%)
下面推荐的一些工具和技术可以协助你。
基准化(BENCHMARKING)
有很多方式来运行JavaScript代码片段的基准测试其性能——一般的假设是,基准简单地比较两个时间戳。这中模式被jsPerf团队指出,并在SunSpider和Kraken的基准套件中使用:
var totalTime,
start = new Date,
iterations = 1000;
while (iterations--){
// Code snippet goes here
}
// totalTime →the number of milliseconds taken
// to execute the code snippet 1000 times
totalTime = new Date - start;
在这里,要测试的代码被放置在一个循环中,并运行一个设定的次数(例如6次)。在此之后,开始日期减去结束日期,就得出在循环中执行操作所花费的时间。
然而,这种基准测试做的事情过于简单了,特别是如果你想运行在多个浏览器和环境的基准。垃圾收集器本身对结果是有一定影响的。即使你使用window.performance这样的解决方案,也必须考虑到这些缺陷。
不管你是否只运行基准部分的代码,编写一个测试套件或编码基准库,JavaScript基准其实比你想象的更多。如需更详细的指南基准,我强烈建议你阅读由Mathias Bynens和John-David Dalton提供的Javascript基准测试。
分析(PROFILING)
Chrome开发者工具为JavaScript分析有很好的支持。可以使用此功能检测哪些函数占用了大部分时间,这样你就可以去优化它们。这很重要,即使是代码很小的改变会对整体表现产生重要的影响。
Chrome开发者工具的分析面板
分析过程开始获取代码性能基线,然后以时间线的形式体现。这将告诉我们代码需要多长时间运行。“Profiles”选项卡给了我们一个更好的视角来了解应用程序中发生了什么。JavaScript CPU分析文件展示了多少CPU时间被用于我们的代码,CSS选择器分析文件展示了多少时间花费在处理选择器上,堆快照显示多少内存正被用于我们的对象。
利用这些工具,我们可以分离、调整和重新分析来衡量我们的功能或操作性能优化是否真的起到了效果。
“Profile”选项卡展示了代码性能信息。
一个很好的分析介绍,阅读Zack Grossbart的JavaScript Profiling With The Chrome Developer Tools。
提示:在理想情况下,若想确保你的分析并未受到已安装的应用程序或扩展的任何影响,可以使用--user-data-dir <empty_directory>标志来启动Chrome。在大多数情况下,这种方法优化测试应该是足够的,但也需要你更多的时间。这是V8标志能有所帮助的。
避免内存泄漏——3快照技术
在谷歌内部,Chrome开发者工具被Gmail等团队大量使用,用来帮助发现和排除内存泄漏。
Chrome开发者工具中的内存统计
内存统计出我们团队所关心的私有内存使用、JavaScript堆的大小、DOM节点数量、存储清理、事件监听计数器和垃圾收集器正要回收的东西。推荐阅读Loreena Lee的“3快照”技术。该技术的要点是,在你的应用程序中记录一些行为,强制垃圾回收,检查DOM节点的数量有没有恢复到预期的基线,然后分析三个堆的快照来确定是否有内存泄漏。
单页面应用的内存管理
单页面应用程序(例如AngularJS,Backbone,Ember)的内存管理是非常重要的,它们几乎永远不会刷新页面。这意味着内存泄漏可能相当明显。移动终端上的单页面应用充满了陷阱,因为设备的内存有限,并在长期运行Email客户端或社交网络等应用程序。能力愈大责任愈重。
有很多办法解决这个问题。在Backbone中,确保使用dispose()来处理旧视图和引用(目前在Backbone(Edge)中可用)。这个函数是最近加上的,移除添加到视图“event”对象中的处理函数,以及通过传给view的第三个参数(回调上下文)的model或collection的事件监听器。dispose()也会被视图的remove()调用,处理当元素被移除时的主要清理工作。Ember 等其他的库当检测到元素被移除时,会清理监听器以避免内存泄漏。
Derick Bailey的一些明智的建议:
与其了解事件与引用是如何工作的,不如遵循的标准规则来管理JavaScript中的内存。如果你想加载数据到的一个存满用户对象的Backbone集合中,你要清空这个集合使它不再占用内存,那必须这个集合的所有引用以及集合内对象的引用。一旦清楚了所用的引用,资源就会被回收。这就是标准的JavaScript垃圾回收规则。
在文章中,Derick涵盖了许多使用Backbone.js时的常见内存缺陷,以及如何解决这些问题。
Felix Geisendörfer的在Node中调试内存泄漏的教程也值得一读,尤其是当它形成了更广泛SPA堆栈的一部分。
减少回流(REFLOWS)
当浏览器重新渲染文档中的元素时需要重新计算它们的位置和几何形状,我们称之为回流。回流会阻塞用户在浏览器中的操作,因此理解提升回流时间是非常有帮助的。
回流时间图表
你应该批量地触发回流或重绘,但是要节制地使用这些方法。尽量不处理DOM也很重要。可以使用DocumentFragment,一个轻量级的文档对象。你可以把它作为一种方法来提取文档树的一部分,或创建一个新的文档“片段”。与其不断地添加DOM节点,不如使用文档片段后只执行一次DOM插入操作,以避免过多的回流。
例如,我们写一个函数给一个元素添加20个div。如果只是简单地每次append一个div到元素中,这会触发20次回流。
function addDivs(element){
var div;
for (var i = 0; i <20; i ++){
div = document.createElement(‘div’);
div.innerHTML = ‘Heya!’;
element.appendChild(div);
}
}
要解决这个问题,可以使用DocumentFragment来代替,我们可以每次添加一个新的div到里面。完成后将DocumentFragment添加到DOM中只会触发一次回流。
function addDivs(element){
var div;
// Creates a new empty DocumentFragment.
var fragment = document.createDocumentFragment();
for (var i = 0; i <20; i ++){
div = document.createElement(‘a’);
div.innerHTML = ‘Heya!’;
fragment.appendChild(div);
}
element.appendChild(fragment);
}
可以参阅Make the Web Faster,JavaScript Memory Optimization 和Finding Memory Leaks。
JS内存泄漏探测器
为了帮助发现JavaScript内存泄漏,谷歌的开发人员((Marja Hölttä和Jochen Eisinger)开发了一种工具,它与Chrome开发人员工具结合使用,检索堆的快照并检测出是什么对象导致了内存泄漏。
一个JavaScript内存泄漏检测工具
有完整的文章介绍了如何使用这个工具,建议你自己到内存泄漏探测器项目页面看看。
如果你想知道为什么这样的工具还没集成到我们的开发工具,其原因有二。它最初是在Closure库中帮助我们捕捉一些特定的内存场景,它更适合作为一个外部工具。
V8优化调试和垃圾回收的标志位
Chrome支持直接通过传递一些标志给V8,以获得更详细的引擎优化输出结果。例如,这样可以追踪V8的优化:
“/Applications/Google Chrome/Google Chrome” --js-flags=“--trace-opt --trace-deopt”
Windows用户可以这样运行chrome.exe –js-flags=”–trace-opt –trace-deopt”
在开发应用程序时,下面的V8标志都可以使用。
•trace-opt —— 记录优化函数的名称,并显示跳过的代码,因为优化器不知道如何优化。
•trace-deopt —— 记录运行时将要“去优化”的代码。
•trace-gc —— 记录每次的垃圾回收。
V8的处理脚本用*(星号)标识优化过的函数,用~(波浪号)表示未优化的函数。
如果你有兴趣了解更多关于V8的标志和V8的内部是如何工作的,强烈建议阅读Vyacheslav Egorov的excellent post on V8 internals。
HIGH-RESOLUTION TIME 和NAVIGATION TIMING API
高精度时间(HRT)是一个提供不受系统时间和用户调整影响的亚毫秒级高精度时间接口,可以把它当做是比new Date 和Date.now()更精准的度量方法。这对我们编写基准测试帮助很大。
高精度时间(HRT)提供了当前亚毫秒级的时间精度
目前HRT在Chrome(稳定版)中是以window.performance.webkitNow()方式使用,但在Chrome Canary中前缀被丢弃了,这使得它可以通过window.performance.now()方式调用。Paul Irish在HTML5Rocks上了关于HRT更多内容的文章。
现在我们知道当前的精准时间,那有可以准确测量页面性能的API吗?好吧,现在有个Navigation Timing API可以使用,这个API提供了一种简单的方式,来获取网页在加载呈现给用户时,精确和详细的时间测量记录。可以在console中使用window.performance.timing来获取时间信息:
显示在控制台中的时间信息
我们可以从上面的数据获取很多有用的信息,例如网络延时为responseEnd – fetchStart,页面加载时间为loadEventEnd – responseEnd,处理导航和页面加载的时间为loadEventEnd – navigationStart。
正如你所看到的,perfomance.memory的属性也能显示JavaScript的内存数据使用情况,如总的堆大小。
更多Navigation Timing API的细节,阅读Sam Dutton的Measuring Page Load Speed With Navigation Timing。
ABOUT:MEMORY 和ABOUT:TRACING
Chrome中的about:tracing提供了浏览器的性能视图,记录了Chrome的所有线程、tab页和进程。
About:Tracing提供了浏览器的性能视图
这个工具的真正用处是允许你捕获Chrome的运行数据,这样你就可以适当地调整JavaScript执行,或优化资源加载。
Lilli Thompson有一篇写给游戏开发者的使用about:tracing分析WebGL游戏的文章,同时也适合JavaScript的开发者。
在Chrome的导航栏里可以输入about:memory,同样十分实用,可以获得每个tab页的内存使用情况,对定位内存泄漏很有帮助。
总结
我们看到,JavaScript的世界中有很多隐藏的陷阱,且并没有提升性能的银弹。只有把一些优化方案综合使用到(现实世界)测试环境,才能获得最大的性能收益。即便如此,了解引擎是如何解释和优化代码,可以帮助你调整应用程序。
测量,理解,修复。不断重复这个过程。
谨记关注优化,但为了便利可以舍弃一些很小的优化。例如,有些开发者选择.forEach和Object.keys代替for和for..in循环,尽管这会更慢但使用更方便。要保证清醒的头脑,知道什么优化是需要的,什么优化是不需要的。
同时注意,虽然JavaScript引擎越来越快,但下一个真正的瓶颈是DOM。回流和重绘的减少也是重要的,所以必要时再去动DOM。还有就是要关注网络,HTTP请求是珍贵的,特别是移动终端上,因此要使用HTTP的缓存去减少资源的加载。
记住这几点可以保证你获取了本文的大部分信息,希望对你有所帮助!
- 那些操作会造成内存泄漏?
网址 - JQuery一个对象可以同时绑定多个事件,这是如何实现的?
$("#div1", "#divN").click(function() { });
转载于:https://www.cnblogs.com/shimily/articles/5539592.html
JavaScript面试大全相关推荐
- JavaScript面试大全(二)
为什么80%的码农都做不了架构师?>>> eval()是做什么的? eval()是"魔鬼" null,undefined 的区别? 来看看大神阮一峰的解释吧 ...
- 前端面试大全(JavaScript篇——面试利器值得拥有!)
目录 面试系列 内容介绍 1.介绍js的基本数据类型. 2.介绍js有哪些内置对象? 3.说几条写JavaScript的基本规范?, 4.JavaScript原型,原型链? 有什么特点? 一.关系 二 ...
- 前端面试大全(Ajax篇——面试官这样问,你答得上来吗?)
目录 面试系列 内容介绍 1.Ajax 是什么? 如何创建一个Ajax? ①.Ajax ②.异步 ③.步骤 2.同步和异步的区别? ①.同步 ②.异步 3.如何解决跨域问题? 4.http状态码有那些 ...
- 前端面试大全(序篇)
面试系列 后续更新分类将同步更新系列目录: 前端面试大全(序篇):https://blog.csdn.net/cainiaoyihao_/article/details/116518423 前端面试大 ...
- PHP面试大全 基础篇100道问题
2017年3月7日14:23:21 其实计划很久但是移植没时间去收集和处理弄成一个完整的文件 SVN地址: https://git.oschina.net/zxadmin/live_z 目前基础部分更 ...
- 数据结构面试大全(二) - [算法]
版权声明 :转载时请以超链接形式标明文章原始出处和作者信息及本声明 http://lzhshen.blogbus.com/logs/20356236.html 10, strstr() 的简单实现 s ...
- java后端面试大全,java后端面试宝典
文章目录 -2 flink -1 linux of view linux查看占用cup最高的10个进程的命令: 〇.分布式锁 & 分布式事务 0-1分布式锁--包含CAP理论模型 概述 分布式 ...
- 好程序员Java教程分享JavaScript面试问题及答案(一)
好程序员Java教程分享JavaScript面试问题及答案(一) 1.使用 typeof bar === "object" 来确定 bar 是否是对象的潜在陷阱是什么?如何避免这个 ...
- JavaScript 面试中常见算法问题详解
JavaScript 面试中常见算法问题详解,翻译自 https://github.com/kennymkchan/interview-questions-in-javascript.下文提到的很多问 ...
最新文章
- python打包成exe可执行文件指定进程名字
- 解决sharepoint 2010浏览器在线浏览Word出错
- android xml中设置水平虚线及竖直虚线
- 利用Azure backup备份和恢复Azure虚拟机(1)
- 获取手机本地的图片或者照相机照像的图片 为头像
- Sprin boot 加载位置顺序
- 网络克隆自动修改计算机名ip,GHOST网克专用IP及计算机名自动修改器
- [ASP.NET 设计模式] 用Visual Studio2010搭建一个简单的分层结构示例Step by Step —— 06 用户界面层...
- 【HDU 5869】Different GCD Subarray Query【区间不同 gcd 个数】
- 构筑城市生命线:应急管理需要新思路,全域能力成关键
- javascript网站期末作品~html电影资讯博客网站模板(HTML+CSS+JavaScript)在线电影网页设计...
- 99乘法口诀表用for循环
- Android Framework 框架系列之PowerManager
- 广西交通职业技术学院计算机专业在哪个校区,广西交通职业技术学院宿舍怎么样...
- Error LNK2005:_main already defined in test.obj
- BFS算法之地铁路线问题
- 数据中台方向创业者上海小胖的采访记
- Struts框架漏洞
- Servlet相关简介及使用教程
- 微软Windows Azure云应用开发实践
热门文章
- JS知识资源一站式集锦
- Self -Attention、Multi-Head Attention、Cross-Attention
- 有计算机绘制函数结题报告,信息技术应用用计算机绘制函数图象
- JS滚轮事件(mousewheel/DOMMouseScroll)了解 -2014
- DaVinci Resolve Studio 17 Mac(达芬奇调色软件)
- MySQL基于时间点恢复和快照恢复实例
- 计算机专业的求职信英语,计算机专业英语求职信范文
- 地磁温度误差补偿算法
- c++ vector 一部分_丝杠支座组件C-BUN6价格
- [mysql]-日期-时间-riqi-shijian