2019独角兽企业重金招聘Python工程师标准>>>
自测链接>> 在js25题、js21题或者js85题测验你的知识掌握。
js25题笔记
1. 使用typeof bar === "object"
可能遇到的陷阱和解决方法
在 Javascript 里使用 typeof 来判断数据类型,只能区分基本类型,即 “number”,”string”,”undefined”,”boolean”,”object” 五种。
陷阱:如果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()来判断:
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:
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);
示例:
6.use strict 使用严格模式有什么好处
7.return的陷阱
当return单独处于一行时,Javascript会自动补全为return;
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 &#61; 0; i <5; i&#43;&#43;) {var btn &#61; document.createElement(&#39;button&#39;);btn.appendChild(document.createTextNode(&#39;Button &#39; &#43; i));btn.addEventListener(&#39;click&#39;, (function(i) {return function() { console.log(i); };})(i));document.body.appendChild(btn);
}
学ECMAScript 6的时候遇过这个问题&#xff0c;点击任意按钮都是输出5&#xff0c;因为在onclick触发前&#xff0c;for循环已经结束了&#xff0c;点击按钮时输出的i是for循环结束后的i&#xff0c;所以都是5.为了输出0&#xff0c;1&#xff0c;2&#xff0c;3这样的值&#xff0c;我们可以从i的作作用域和onclick的执行时机下手。以下有三个方法&#xff0c;其中前两个方法是基于i的作用域处理&#xff0c;后一个则是限制onclick的执行时机&#xff08;用了立即调用方式&#xff09;
for (let i &#61; 0; i <5; i&#43;&#43;) { //let让i的作用域在每次执行循环时都独立出来 var btn &#61; document.createElement(&#39;button&#39;);btn.appendChild(document.createTextNode(&#39;Button &#39; &#43; i));btn.addEventListener(&#39;click&#39;, (function(i) {return function() { console.log(i); };})(i));document.body.appendChild(btn);
}
[&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;].forEach(function (value, i) { //[&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;]同样是将每次循环的i分隔开来var btn &#61; document.createElement(&#39;button&#39;);btn.appendChild(document.createTextNode(&#39;Button &#39; &#43; i));btn.addEventListener(&#39;click&#39;, function() { console.log(i); });document.body.appendChild(btn);
});
for (var i &#61; 0; i <5; i&#43;&#43;) {var btn &#61; document.createElement(&#39;button&#39;);btn.appendChild(document.createTextNode(&#39;Button &#39; &#43; i));(function (i) {btn.addEventListener(&#39;click&#39;, function() { console.log(i); });})(i);//立即调用表达式使btn.addEventListener在循环中完成初始化并加载document.body.appendChild(btn);
}
10.reverse,push,concat的使用
var arr1 &#61; "john".split(&#39;&#39;);
var arr2 &#61; arr1.reverse();
var arr3 &#61; "jones".split(&#39;&#39;);
arr2.push(arr3);
console.log("array 1: length&#61;" &#43; arr1.length &#43; " last&#61;" &#43; arr1.slice(-1));
console.log("array 2: length&#61;" &#43; arr2.length &#43; " last&#61;" &#43; arr2.slice(-1));
//输出&#xff1a;
//"array 1: length&#61;5 last&#61;j,o,n,e,s"
//"array 2: length&#61;5 last&#61;j,o,n,e,s"
通过上面这段代码来巩固reverse,push和concat的使用。
首先&#xff0c;reverse会改变数组本身&#xff08;arr1&#xff09;并且返回一个数组引用&#xff08;不是返回数组arr1&#xff0c;是返回arr1的引用&#xff09;&#xff0c;因此arr2 &#61; arr1.reverse() 使得arr2指向arr1&#xff0c;从而对arr2的操作也会映射到arr1上。
此时arr1与arr2指向同一个对象&#xff0c;值为[ &#39;n&#39; , &#39;h&#39; , &#39;o&#39; , &#39;j&#39; ]。
其次&#xff0c;push在插入一个数组时&#xff0c;会把数组当成一个元素插进去&#xff0c;而且直接改变数组。因此arr2.push(arr3)的结果是[ &#39;n&#39; , &#39;h&#39; , &#39;o&#39; , &#39;j&#39; ,[&#39;j&#39; ,&#39;o&#39;, &#39;n&#39; ,&#39;e&#39; ,&#39;s&#39;]]。从中我们知道push和concat的区别就是concat是将数组中的元素一个一个拼接到前一个数组中&#xff0c;而且不直接改变对象&#xff0c;要将拼接后的对象重新返回给原对象&#xff08;使用时 arr &#61; arr.concat(xxx); <&#61;&#61; > arr.push(xxx);&#xff09;。而push是把元素整个放入对象结尾&#xff0c;所以遇到数组时push是往数组中放入array对象&#xff0c;concat是拼接数组。
因此在上面这段代码中&#xff0c;输出arr1最后一个元素时便输出了arr3这个数组&#xff08;push时arr3被当作最后一个元素加入arr1指向的数组对象&#xff09;&#xff0c;而arr1和arr2指向的对象相同&#xff0c;所以输出两者内容都是一样的。
11.利用事件队列解决堆栈溢出
下面这份代码在list特别大的时候会导致堆栈溢出
var list &#61; readHugeList();var nextListItem &#61; function() {var item &#61; list.pop();if (item) {// process the list item...nextListItem();}
};
可以巧妙地用事件队列解决这个问题
var list &#61; readHugeList();var nextListItem &#61; function() {var item &#61; list.pop();if (item) {// process the list item...setTimeout( nextListItem, 0);}
};
通过上面的方法&#xff0c;用事件队列处理递归&#xff0c;而不是调用堆栈。当运行nextListItem时&#xff0c;如果item不为空&#xff08;未到列表最后一个&#xff09;&#xff0c;这个计时方法&#xff08;nextListItem&#xff09;会被加入事件队列&#xff0c;同时该方法也就退出了调用堆栈&#xff0c;这样nextListItem方法会被一直加入到事件队列&#xff0c;知道item为空时开始逐个执行事件队列里的方法。与此同时&#xff0c;调用堆栈一直为空。
12. || 与 &&
0 || 1 &#61; 1 //0为false&#xff0c;继续计算下一个操作数1&#xff0c;最终得到1
1 || 2 &#61; 1 //1为true&#xff0c;忽略下一个操作数2&#xff0c;最终得到1
0 && 1 &#61; 0 //0为false&#xff0c;忽略下一个操作数1&#xff0c;最终得到0
1 && 2 &#61; 2 //1为true&#xff0c;继续计算下一个操作数1&#xff0c;最终得到2
13. &#61;&#61; 与 &#61;&#61;&#61;
&#61;&#61;判断值相等&#xff0c;&#61;&#61;&#61;判断值与类型均相同。
14.js在设置对象属性时&#xff0c;会自动吧参数字符串化。
var a&#61;{},b&#61;{key:&#39;b&#39;},c&#61;{key:&#39;c&#39;};a[b]&#61;123;
a[c]&#61;456;console.log(a[b]); //456
原因&#xff1a;b和c都是object&#xff0c;放入a时字符串化得到[object object] 。因此a[b],a[c]其实都是a["[object object]"]&#xff0c;输出的值自然就是最后赋值的那一个了。
15.bind的用法&#xff08;IE678不支持&#xff09;
var hero &#61; {_name: &#39;John Doe&#39;,getSecretIdentity: function (){return this._name;}
};var stoleSecretIdentity &#61; hero.getSecretIdentity;//undefinedconsole.log(stoleSecretIdentity());//John Doevar stoleSecretIdentity2 &#61; hero.getSecretIdentity.bind(hero);//John Doe