javascript 易错点、难点笔记
本文主要记录在学习过程中遇到的JavaScript难点或者容易疏忽的细节,也方便自己日后翻阅学习。
1、arr.length === + arr.length
arr.length === + arr.length
是一种鸭式辨型的判断方法。这句话包含两层意思:
-
arr有length这个属性
- arr.length是一个Number(可以试一下x === +x)
鸭式辨型的经典假设:只要是会游泳的鸟类,不管它是什么,我都把它当成鸭子。只要arr有length这个属性,且它是个Number,我都把arr当成是数组。
这种鸭式辨型的判定方法,要看你的具体需求!!比如你并不在乎其他事,只关心要使用的这个变量必须满足上面的两个条件,就可以这么判断。
var arr = "123"; var arr2 = [1,2,3]; arr.length === + arr.length; //true arr2.length === + arr2.length; //true
因为字符串有length这个属性,且它的length是个Number。所以如果你用了arr.length === + arr.length;来判断数组,字符串也满足这样的条件。如果在你的开发场景中,只关心这两个条件,当然就没问题。如果你要求必须是个数组,就不能用这种鸭式辨型的方法来判断了。
据说jQuery中要判断一个变量是否是一个数组的确切方法是:
Object.prototype.toString.call(arr)==='[object Array]'
关于鸭式辨型更多的了解,可以参考《javascript权威指南》中的描述
2、Array(5)和 new Array(5)
var s=Array(5); for(var i=0;i<s.length;i++) console.log(s.length);//5 5 5 5 5 var arr= new Array(5); for(var i=0;i<arr.length;i++) console.log(arr.length);//5 5 5 5 5
两者都是在创建数组。
3、void 0 与 undefined
undefined在JavaScript中并不属于保留字/关键字,因此在IE5.5~8中我们可以将其当作变量那样对其赋值(IE9+及其他现代浏览器中赋值给undefined将无效)
var undefinedBackup = undefined; undefined = 1; // 显示"undefined" console.log(typeof undefinedBackup); // 在IE5.5~8中显示"number",其他浏览器中则显示"undefined" console.log(typeof undefined);
void 运算符能对给定的表达式进行求值,然后返回 undefined。也就是说,void 后面你随便跟上一个表达式,返回的都是 undefined,都能完美代替 undefined!于是采用void方式获取undefined则成了通用准则。
其他能够得到undefined:
1. 未赋值的变量
var myUndefined; console.log(typeof myUndefined); // 显示"undefined"
2. 未赋值的实参(和未赋值的变量同理)
var getUndefined = function(undefined){ return undefined; }; var myUndefined = getUndefined(); // 或通过arguments获取 var getUndefined = function(){ return arguments[arguments.length]; };
3. 无返回值函数
var getUndefined = function(){}; var myUndefined = getUndefined();
4. 未定义的属性
var myUndefined1 = {}['']; var myUndefined2 = [][0];
4、ownerDocument
ownerDocument可以理解为指向document的指针
<!DOCTYPE html> <html> </body> <div id="id"></div> <script> var div=document.createElement("div"); console.log(div.ownerDocument===document) console.log(div.ownerDocument.getElementById("id")) </script> </html>
中间的等式已经说明了。
5、严格检查传入函数的所有参数
有时候,我们的函数需要严格检查传入的参数,否则会引起比较严重的错误,这时候,我们可以用下面这种方法。
// Strictly check a list of variable types against a list of arguments function strict( types, args ) { // Make sure that the number of types and args matches if ( types.length != args.length ) { // If they do not, throw a useful exception throw "Invalid number of arguments. Expected " + types.length + ", received " + args.length + " instead."; } // Go through each of the arguments and check their types for ( var i = 0; i < args.length; i++ ) { // if ( args[i].constructor != types[i] ) { throw "Invalid argument type. Expected " + types[i].name + ", received " + args[i].constructor.name + " instead."; } } } // A simple function for printing out a list of users function userList( prefix, num, users ) { // Make sure that the prefix is a string, num is a number, // and users is an array strict( [ String, Number, Array ], arguments ); // Iterate up to 'num' users for ( var i = 0; i < num; i++ ) { // Displaying a message about each user alert( prefix + ": " + users[i] ); } }
6、延时的写法
很多时候,我们都会这么写:setTimeout(“otherFunction()", 1000);
但是,为了更好的操作,其实我们可以这样:
// A generic function for displaying a delayed alert message function delayedAlert( msg, time ) { // Initialize an enclosed callback setTimeout(function(){ // Which utilizes the msg passed in from the enclosing function alert( msg ); }, time ); }
7、上下文
在上下文对象内使用函数,并将其上下对象切换成另一个变量
var obj = { yes: function(){ // this == obj this.val = true; }, no: function(){ this.val = false; } }; // We see that there is no val property in the 'obj' object console.log( obj.val == null );//true // We run the yes function and it changes the val property // associated with the 'obj' object obj.yes();//函数执行后,才会添加新的属性 console.log( obj.val == true );//true // However, we now point window.no to the obj.no method and run it window.no = obj.no; window.no(); // This results in the obj object staying the same (as the context was // switched to the window object) console.log( obj.val == true );//true // and window val property getting updated. console.log( window.val == false );//true
之所以会这样是因为我们将obj.no的方法赋给了window.no,调用的时候this指向的是window而不是obj.
8、将类数组对象转化为数组
类 数组对象的概念其实很简单,首先是可以像数组一样的获取某一个值,比如:类数组对象likeArray,取其第一个属性值的时候是 likeArray[0],第二个就是likeArray[1]。另外还有一个要求就是还有一个length的属性,length来表示类数组对象包含多 少个属性。
var data={0:"1",length:1}; var arr2=Array.prototype.slice.call(data,0); console.log(arr2); //[1] var data={0:"1"}; var arr2=Array.prototype.slice.call(data,0); console.log(arr2); //[] var data={0:"1",r:1,length:2}; var arr2=Array.prototype.slice.call(data,0); console.log(arr2.length); //2 console.log(arr2[1]); //undefined console.log(arr2[0]); //1 var data={t:"1",r:1,length:"2"}; var arr2=Array.prototype.slice.call(data,0); console.log(arr2.length); //2 console.log(arr2[0]); //undefined
可以看到,只有有length属性就会返回数组,前提是length的属性值可以转化为数字,否则返回一个空数组。
9、getYear()和getFullYear()
返回的是从 1900 年开始算起的,如果想要返回完整年份,应该用 new Date().getFullYear()。
new Date().getYear(); //116
new Date().getFullYear(); //2016
10、new Date(2015,12,26)
不加引号时,第二个参数是指第几个月,比如你这里的 12 就是第12个月,这明显已到了下一年的第一个月,因为月份是从0开始的。
加引号时,就相当于格式化时间格式。因此,new Date(2015,12,26)会多出一个月的天数,改为new Date(“2015,12,26”)却不会
11、字符串翻转
其他语言的翻转可能有些麻烦,但是对于js却可以很轻易实现。
var str = "abcdefg";
str.split("").reverse().join("")
先用split,将其变成数组,然后翻转,再用join()实现无缝连接,即实现了字符串的翻转
12、!+"\v1" 判断浏览器类型
其实就是利用各浏览器对转义字符"\v"的理解。在ie浏览器中,"\v"没有转义,得到的结果为"v"。而在其他浏览器中"\v"表示一个垂直制表符(一定程度上相当于空格),所以ie解析的"\v1" 为 "v1"
而其他浏览器解析到 "\v1" 为 "1",在前面加上一个"+"是为了把后面的字符串转变成数字。由于ie认为"\v1"为"v1",所以前面的加上加号无法转变成数字,为NaN,其他浏览器均能变成 1。再因为js与c语言类似,进行逻辑判断时可使用数字,并且 0 为 false,其他数字则为true,所以 !1 = false ,于是其他浏览器均返回false。js在遇到如下几个值会返回false:undefined、null、NaN,所以ie中 !NaN = true。
13、js正则表达式中/=\s*\".*?\"/g
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。\s*匹配0-n个空白,\"匹配双引号,.*?匹配任意个字符,这个正则就是匹配带双引号的内容,比如:“a”、"sdafsas"这些
14、百度前端技术学院 task21 的点击删除
我竟然又忘了伪元素的作用,企图通过js来实现hover的时候出现点击删除,离开的时候消失,我真是太天真了,而这其实可以用css就可以实现,另外删除的时候,直接用removeChild(target);
#tagContainer div:hover {
background-color: red;
cursor: pointer;
}
#tagContainer div:hover:before {
content: "点击删除";
}
另一种方法:直接在输出的时候就添加删除,但是给他添加一个类名,并设置Css样式。
.tag-list li:hover {background-color: #fe0200;}
.tag-list li .delect {display: none;margin-right: 5px;}
.tag-list li:hover .delect {display: inline-block;}
//js
tagLiNode.innerHTML = "<span class = 'delect'>删除</span><span class = 'ivalue'>"+inputValue+"</span>";
第三种方法:采用js实现,貌似只能在keyup的时候效果比较好,而且是有值输入的情况下。
EventUtil.on(inputTag,'keyup',addtagNumkeyHandle);
function addtagNumkeyHandle(event){
for (var i = 0; i < tagNum.childNodes.length; i++){
tagNum.childNodes[i] = i;
fn(i);
}
}
function fn(i){
var old = tagNum.childNodes[i].innerHTML;
tagNum.childNodes[i].onmouseover = function (){
this.innerHTML = '点击删除' + old;
this.style.background = '#f00';
};
tagNum.childNodes[i].onmouseout = function (){
this.innerHTML = old;
this.style.background = '#8DC9FB';
};
tagNum.childNodes[i].onclick = function (){
this.parentNode.removeChild(this);
for (var j = 0; j < arr.length; j++){
console.log(old, arr[j]);
arrDelJ(old, j);
}
console.log(old, arr,json);
}
}
15、keypress,keyup,textInput事件分析
可以参看百度前端技术学院task21代码
//textInput事件处理程序,传入event,可以获取event.data,即刚刚按下的字符是什么。
function addtextHandle(event) {
event=EventUtil.getEvent(event);
if (pattern.test(event.data)){
inputTagValue=inputTag.value.split(pattern1);
renderdata(tagNum,inputTagValue);
inputTag.value='';
event.data='';
}
}
//刚开始绑定的是keypress,一直不对,后面采用keyup就好多了。原因在于keypress只是键盘按下去这个事件响应了,
//但是keyup的话此时输入的值已经显示在input的当中了,这时我们就可以做清理工作了。
function addtagNumkeyHandle(event){
event=EventUtil.getEvent(event);
if (event.keyCode==13){//如果按下的是回车键,就将上述结果输入。
inputTagValue=inputTag.value.split(pattern);
renderdata(tagNum,inputTagValue);
inputTag.value='';
}
inputTagValue=inputTag.value.split(pattern);
if(inputTagValue[1]=="" ){
inputTagValue=inputTag.value.split(pattern);
renderdata(tagNum,inputTagValue);
inputTag.value='';
}
}
16、JSON.parse()和JSON.stringify()
JSON.stringify() 方法可以将任意的 JavaScript 值序列化成 JSON 字符串。若转换的函数被指定,则被序列化的值的每个属性都会经过该函数的转换和处理;若转换的数组被指定,只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中。
重点在于变成字符串!!!
关于序列化,有下面五点注意事项:
- 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
- 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
- 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
- 不可枚举的属性会被忽略
JSON.stringify({}); // '{}' JSON.stringify(true); // 'true' JSON.stringify("foo"); // '"foo"' JSON.stringify([1, "false", false]); // '[1,"false",false]' JSON.stringify({ x: 5 }); // '{"x":5}' JSON.stringify({x: 5, y: 6}); // '{"x":5,"y":6}' 或者 '{"y":6,"x":5}' 都可能 JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // '[1,"false",false]' JSON.stringify({x: undefined, y: Object, z: Symbol("")}); // '{}' JSON.stringify([undefined, Object, Symbol("")]); // '[null,null,null]' JSON.stringify({[Symbol("foo")]: "foo"}); // '{}' JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]); // '{}' JSON.stringify({[Symbol.for("foo")]: "foo"}, function (k, v) { if (typeof k === "symbol"){ return "a symbol"; } }); // '{}' // 不可枚举的属性默认会被忽略: JSON.stringify( Object.create(null, { x: { value: 'x', enumerable: false }, y: { value: 'y', enumerable: true } }) ); // '{"y":"y"}'
JSON.parse() 方法将一个 字符串解析成一个 JSON 对象。在解析过程中,还可以选择性的修改某些属性的原始解析值。
重点在于解析字符串,传入的要是字符串!!!
JSON.parse('{}'); // {} JSON.parse('true'); // true JSON.parse('"foo"'); // "foo" JSON.parse('[1, 5, "false"]'); // [1, 5, "false"] JSON.parse('null'); // null JSON.parse('{"name": "asad"}') //{name: "asad"}
17、关于数组空数组[]
var arr=[],s; console.log(arr==0); //true console.log(arr==null); //false console.log(arr==undefined);//false console.log(arr==""); //true console.log(0==null); //false console.log(0==undefined); //false console.log(null==undefined);//true console.log(""==undefined); //false console.log(s==undefined) //true console.log(s==0) //false
18、判断是node环境还是浏览器环境
if(typeof(global) !== 'undefined' && typeof(window) === 'undefined') { juicer.set('cache', false); }
19、字符串转义方法
// 字符串转义 function stringify (code) { return "'" + code // 单引号与反斜杠转义 .replace(/('|\\)/g, '\\$1') // 换行符转义(windows + linux) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') + "'"; }
20、点和中括号的区别
中括号运算符总是能代替点运算符。但点运算符却不一定能全部代替中括号运算符。
中括号运算符可以用字符串变量的内容作为属性名。点运算符不能。
中括号运算符可以用纯数字为属性名。点运算符不能。
中括号运算符可以用js的关键字和保留字作为属性名。点运算符不能。
21、ASCII码
大写字母比小写字母小32;例如大写A=65;a=97;
// char-->ascii
var a = 122;
console.log("a".charCodeAt(0));
// ascii-->char String.fromCharCode(a);
22、字面量正则和new的方式的区别
var reg = new RegExp("^\\d+$"); var reg1 = new RegExp("^\d+$"); var d= /^\d+$/ console.log(reg) // /^\d+$/ console.log(reg1) // /^d+$/ console.log(d) // /^\d+$/ console.log(reg.test(12)) // true console.log(reg1.test(12)) // flase console.log(d.test(12)) // true
注意在使用 new 的时候,要在 “\” 多加一个 “\”。
23、闭包中的this
!function(ob){ ob.ob=function(){ this.a=3; console.log(this); //window } }(window) ob(); console.log(a);
24、setTimeout 会导致与 this 绑定到全局对象运行
注意使用bind
function Bomb() { this.message = 'Boom!'; } Bomb.prototype.explode = function() { console.log(this.message); }; var bomb = new Bomb(); var message = 'window'; setTimeout(bomb.explode.bind(bomb), 1000); //Boom! setTimeout(bomb.explode, 1000); //window
25、 i++和++i的区别
(++i) means to add 1 to i itself and return the new value
(id++) means to add 1 to id itself and return the old value
var i=0; console.log(i++,++i,++i,i++,i); //0 2 3 3 4