自测连接>> 在js25题、js21题或者js85题测验你的知识掌握。javascript
js25题笔记html
1. 使用typeof bar === "object"
可能遇到的陷阱和解决方法java
在 JavaScript 里使用 typeof 来判断数据类型,只能区分基本类型,即 “number”,”string”,”undefined”,”boolean”,”object” 五种。jquery
陷阱:若是bar是null,js会视之为object类型。数组
解决:判断bar是object但非null: 安全
console.log((bar !== null) && (typeof bar === "object"));
扩展:判断bar是数组: typeof只能区分基本类型,它将function,array,object都视为object类型,因此要区分这三类对象就不能用typeof方法了,能够用toString.call()的返回值来判断:闭包
console.log((bar !== null) && (typeof bar === "object") && (! $.isArray(bar)));
或者用jq的isArray() isFunction()来判断:app
onsole.log((bar !== null) && (typeof bar === "object") && (toString.call(bar) !== "[object Array]"));
ECMA 5.1 中关于Object.prototype.toString.call() 的描述:函数
When the toString method is called, the following steps are taken:this
If the this value is undefined, return “[object Undefined]“.
If the this value is null, return “[object Null]“.
Let O be the result of calling ToObject passing the this value as the argument.
Let class be the value of the [[Class]] internal property of O.
Return the String value that is the result of concatenating the three Strings “[object ", class, and "]“.
对全部值类型应用 Object.prototype.toString.call() 方法结果以下:
console.log(Object.prototype.toString.call(123)) //[object Number] console.log(Object.prototype.toString.call('123')) //[object String] console.log(Object.prototype.toString.call(undefined)) //[object Undefined] console.log(Object.prototype.toString.call(true)) //[object Boolean] console.log(Object.prototype.toString.call({})) //[object Object] console.log(Object.prototype.toString.call([])) //[object Array] console.log(Object.prototype.toString.call(function(){})) //[object Function]
2.不要写容易引发误会的代码
(function(){ var a = b = 3; })(); console.log("a defined? " + (typeof a !== 'undefined')); console.log("b defined? " + (typeof b !== 'undefined'));
陷阱: 误觉得函数内至关于
var b = 3; var a = b;
事实上是
b = 3; var a = b;
所以若是不是在strict mode下,b被当成全局变量。因此应该是输出b有定义,a无定义。
3.在内联函数中,ECMA5以前this指向window,而在ECMA5 内联函数的this变成undefined。
4.闭包的应用
闭包的例子:
function f1(){ var privateA = 1; function f2(){//至关于getter方法 return privateA; }; f3 = function(n){//至关于setter方法,注意,这里的f3是一个全局变量 privateA = n; }; return f2; } var f4 = f1(); console.log(f4());//1 f3(2); console.log(f4());//2
闭包的特色和做用:
1.保护函数内的变量安全,限定访问接口,实现JS私有属性和私有方法。f1的变量只有f2能访问,f2至关于f1私有变量的getter方法。
2.在内存中维持一个变量。f4持有f2,f2引用着f1,所以f4存在的时候,f2和f1也会驻留在内存中(GC不会回收,所以使用闭包时也要留意内存泄露的问题)
5.jQ为了处理多库共存,使用noConflict()来转移$使用权,而当即调用表达式能够在加载时就初始化,造成一个单例模式的效果,故而使用当即调用表达式能够解决js变量污染问题。若是jq的$使用权转移以后还想用jq,结合当即调用表达式的做用,下面这个方法使咱们仍然能使用jq:
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);
示例:
<!DOCTYPE html> <html> <head> <title></title> </head> <body> <div id="foo"> </div> </body> <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript"> $.noConflict(); ;(function ($) { $("#foo").html("我仍是能使用jq!") })(jQuery); $("#foo").html("这里就用不了jq了!")//报错,$找不到 </script> </html>
6.use strict 使用严格模式有什么好处
7.return的陷阱
当return单独处于一行时,javascript会自动补全为return;
<!DOCTYPE html> <html> <head> <title></title> </head> <body> <script type="text/javascript"> function foo1() { return { bar: "hello" }; } function foo2() { return { bar: "hello" }; } console.log("foo1 returns:"); console.log(foo1());//Object {bar: "hello"} console.log("foo2 returns:"); console.log(foo2()) //!!foo2的返回值为 undefined </script> </body> </html>
8.NaN的陷阱
NaN表示一个非数字类型的值。与任何值比较都返回false,包括它本身。用数学函数操做都会返回NaN。
var a = NaN; console.log(a);//NaN console.log(a===a);//false console.log(a>0);//false console.log(Math.max(a));//NaN
可是使用typeof比较时却会判断它为数值类型。
console.log(typeof NaN === "number"); // "true"
判断NaN的方法能够用isNaN(),虽然这个方法存在不足, 有一个更好的方法是使用Number.isNaN()
Number.isNaN(NaN); // true Number.isNaN(Number.NaN); // true Number.isNaN(0 / 0) // true // 使用全局的isNaN(),下面会返回true Number.isNaN("NaN"); // false Number.isNaN(undefined); // false Number.isNaN({}); // false Number.isNaN("blabla"); // false isNaN("NaN"); // true isNaN(undefined); // true isNaN({}); // true isNaN("blabla"); // true // 使用Number.isNaN()和全局isNaN()都返回false Number.isNaN(true); Number.isNaN(null); Number.isNaN(37); Number.isNaN("37"); Number.isNaN("37.37"); Number.isNaN(""); Number.isNaN(" ");
9.for循环中变量做用域的问题
for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', (function(i) { return function() { console.log(i); }; })(i)); document.body.appendChild(btn); }
学ECMAScript 6的时候遇过这个问题,点击任意按钮都是输出5,由于在onclick触发前,for循环已经结束了,点击按钮时输出的i是for循环结束后的i,因此都是5.为了输出0,1,2,3这样的值,咱们能够从i的做做用域和onclick的执行时机下手。如下有三个方法,其中前两个方法是基于i的做用域处理,后一个则是限制onclick的执行时机(用了当即调用方式)
for (let i = 0; i < 5; i++) { //let让i的做用域在每次执行循环时都独立出来 var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', (function(i) { return function() { console.log(i); }; })(i)); document.body.appendChild(btn); }
['a', 'b', 'c', 'd', 'e'].forEach(function (value, i) { //['a', 'b', 'c', 'd', 'e']一样是将每次循环的i分隔开来 var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', function() { console.log(i); }); document.body.appendChild(btn); });
for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); (function (i) { btn.addEventListener('click', function() { console.log(i); }); })(i);//当即调用表达式使btn.addEventListener在循环中完成初始化并加载 document.body.appendChild(btn); }
10.reverse,push,concat的使用
var arr1 = "john".split(''); var arr2 = arr1.reverse(); var arr3 = "jones".split(''); arr2.push(arr3); console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1)); console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1)); //输出: //"array 1: length=5 last=j,o,n,e,s" //"array 2: length=5 last=j,o,n,e,s"
经过上面这段代码来巩固reverse,push和concat的使用。
首先,reverse会改变数组自己(arr1)而且返回一个数组引用(不是返回数组arr1,是返回arr1的引用),所以arr2 = arr1.reverse() 使得arr2指向arr1,从而对arr2的操做也会映射到arr1上。
此时arr1与arr2指向同一个对象,值为[ 'n' , 'h' , 'o' , 'j' ]。
其次,push在插入一个数组时,会把数组当成一个元素插进去,并且直接改变数组。所以arr2.push(arr3)的结果是[ 'n' , 'h' , 'o' , 'j' ,['j' ,'o', 'n' ,'e' ,'s']]。从中咱们知道push和concat的区别就是concat是将数组中的元素一个一个拼接到前一个数组中,并且不直接改变对象,要将拼接后的对象从新返回给原对象(使用时 arr = arr.concat(xxx); <== > arr.push(xxx);)。而push是把元素整个放入对象结尾,因此遇到数组时push是往数组中放入array对象,concat是拼接数组。
所以在上面这段代码中,输出arr1最后一个元素时便输出了arr3这个数组(push时arr3被看成最后一个元素加入arr1指向的数组对象),而arr1和arr2指向的对象相同,因此输出二者内容都是同样的。
11.利用事件队列解决堆栈溢出
下面这份代码在list特别大的时候会致使堆栈溢出
var list = readHugeList(); var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... nextListItem(); } };
能够巧妙地用事件队列解决这个问题
var list = readHugeList(); var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... setTimeout( nextListItem, 0); } };
经过上面的方法,用事件队列处理递归,而不是调用堆栈。当运行nextListItem时,若是item不为空(未到列表最后一个),这个计时方法(nextListItem)会被加入事件队列,同时该方法也就退出了调用堆栈,这样nextListItem方法会被一直加入到事件队列,知道item为空时开始逐个执行事件队列里的方法。与此同时,调用堆栈一直为空。
12. || 与 &&
0 || 1 = 1 //0为false,继续计算下一个操做数1,最终获得1
1 || 2 = 1 //1为true,忽略下一个操做数2,最终获得1
0 && 1 = 0 //0为false,忽略下一个操做数1,最终获得0
1 && 2 = 2 //1为true,继续计算下一个操做数1,最终获得2
13. == 与 ===
==判断值相等,===判断值与类型均相同。
14.js在设置对象属性时,会自动吧参数字符串化。
var a={}, b={key:'b'}, c={key:'c'}; a[b]=123; a[c]=456; console.log(a[b]); //456
缘由:b和c都是object,放入a时字符串化获得[object object] 。所以a[b],a[c]其实都是a["[object object]"],输出的值天然就是最后赋值的那一个了。
15.bind的用法(IE678不支持)
var hero = { _name: 'John Doe', getSecretIdentity: function (){ return this._name; } }; var stoleSecretIdentity = hero.getSecretIdentity;//undefined console.log(stoleSecretIdentity());//John Doe var stoleSecretIdentity2 = hero.getSecretIdentity.bind(hero);//John Doe