javascript作为一门脚本语言,因为缺少约束,以及各类自动容错机制和隐式转换,产生了不少容易误解和容易引起问题的地方, 《javascript语言精髓》一书中,有很大一部分篇幅介绍了javascript语言的糟粕和毒瘤部分,相信大部分问题有些人遇到过,有些人则经过学习知晓其原理而完美的躲过,随着ES规范的不断完善与发展,其中的一些问题获得了解决或完善, 本文总结了常见的坑点,前车可鉴,后车之覆,但愿你们读了以后能少踩坑,少翻车,也能了解相关问题的根源所在。javascript
这个是新人最常遇到的问题了,好比下面的例子,我想把空格所有替换掉java
"are you kidding me?".replace(" ","-")
复制代码
"are you kidding me?".replace(/\s/g,"-")
复制代码
其实,数组是用对象模拟出来的,你能够用数组的任意下标赋值,甚至用字符串作下标也不要紧,length返回的是值最大的那个索引,原来数组一直在欺骗咱们git
arr=[];
arr.a="hello";
console.log(arr.length)
arr["100"]="world";
console.log(arr.length)
复制代码
谁能告诉我,为啥月份是从0开始的,日期倒是从1开始的。由于不统一,不符合直觉,一不当心就踩坑。github
new Date("2019-01-01") 在chrome,firefox 下运行是没有问题的,在safari和IE下都没法返回正常的结果,在ie下将日期格式改写为2019-1-1 可以获得正确的结果。大多数人只是凭直觉这么写了,而在chrome下恰好能返回正确的结果,谁知道在别的浏览器下就可能会翻车。web
根据mdn文档, new Date建立日期对象 基于 Unix Time Stamp,即自1970年1月1日(UTC)起通过的毫秒数。面试
最好是老老实实的这样写:chrome
new Date(2019,0,1)
复制代码
通过实际测试 new Date("2019/01/01") 这样的日期格式也能够正确解析gulp
全局变量的泄漏真的不是由于你没用var声明,而是全部具备id的元素都会自动变成一个全局变量。这可能会无心中形成变量冲突。数组
var class ={}; // 非法
obj={"class":".on"} ; // 非法
object = {case: value}; // 非法
object.case = value; // 非法
复制代码
ES规范规定不能用保留字作变量名,使用点号引用属性名时也会出现问题。ES5规范修正了保留 字作属性名问题,因此如今点号引用属性名在chrome中能正常执行了,在IE9如下会产生报错。浏览器
js变量不声明就能够赋值使用,使咱们常常忽略了用var或let定位变量名,一方面会形成泄露出来不少全局变量,另外一方面,有些是代码是if条件赋值,若是由于逻辑问题绕过了赋值的语句,就会产生异常了,例如以下代码,当条件赋值不成立时就会产生异常了
if(false){
UserInfo={userName:"Hello"}
}
if(UserInfo){
console.log("do something")
}
复制代码
若是没给UserInfo赋值的语句没执行,则会报错 Uncaught ReferenceError: UserInfo is not defined 判断一个变量是否存在正确的姿式是用typeof 判断是不是undefined,若是是全局变量可用加window.变量名
还有一种常见的变量检测场景,如if(result.userInfo.userName) 这样写法,是很不安全的,一但result没有返回,或返回了null值,就会报错了,这绝对是js异常排行榜中稳居前几位的错了,安全的写法 if(result && result.userInfo && result.userInfo.userName)
给数组或对象原型添加了一些方法以后,会被for in 遍历出来。我曾遇到不当心使用for in遍历数组,功能实现上也没有什么问题,某天以后,使用Array.prototype给数组添加了一 polyfill方法以后,功能出现问题
var obj ={"a":1,"b":2,"c":3};
Object.prototype.method1= function (){} ;
for(var key in obj){
console.log(obj[key]);
}
复制代码
第一次踩这个坑的时候真是排查了好久,属于万万没想到的问题, 一直认为instanceof 是很靠谱的,没想到它在跨iframe 使用时彻底失效了。举例来讲你在A页面定义了一个公共函数addConfig,若是传入的是对象就添加到配置列表中,若是传入是数组,就把多个配置都添加到配置列表中,很常见的功能场景
var configList=[];
function addConfig(config){
if(config instanceof Array){
configList=configList.concat(config)
}else{
configList.push(config)
}
}
复制代码
在单个页面中使用是没问题,一旦在A页面中嵌入一个iframe子页面B,B页面使用parent.addConfig调用,就会出现问题,由于不一样窗口(window)中的Array构造器函数是不相等的,因此instanceof 老是会返回false。
因此最安全的判断数组方法是用Array.isArray方法,若是是ES3兼容就用Array.prototype.toString判断。
其实上面那个例子有更简便的写法,用ES6延展操做符,连类型判断都省了,这姿式真帅。
function addConfig(config){
configList.push(...config)
}
复制代码
arguments只是和数组长的有点像,你想直接调用数组的那些方法是不行的,要先转化一下成为数组 [].slice.call(arguments);
遇到这种状况,乖乖的写普通函数吧,什么?你既想享受 this的便利,又想用arguments,箭头函数:臣妾作不到啊。
若是你的数组很大,想找到符合的项就退出循环,报歉,仍是作不到,本身选择的forEach ,含着泪也要执行完。
解决方法
result=[1,2,3,4,5].some(function(item){
console.log(item)
if(item==3){
return true
}
})
复制代码
forEach 回调函数是独立的上下文,没法用async/await来实现阻塞外层函数执行的效果,示例代码
function getResponse(url,time){
return new Promise(function (resolve){
setTimeout(function (){
resolve(url);
},time)
});
}
var urlList=["/url1","url2"]
urlList.forEach(async (url)=>{
let res=await getResponse(url,2000);
console.log(res);
});
复制代码
以上代码,若是用普通的for 循环是串行的,应该是2秒后输出url1 ,4秒后输出url2,但用forEach倒是并行的,url1和url2同时输出了
以下代码
function test(){
return
{
a: "hello"
}
}
console.log(test()) //输出undefined
复制代码
return 语句后面自动插分号了,至关于return ; 返回了undefined,这不是我想要的结果
//文件1:
(function test1(){
console.log("test1");
})()
//文件2:
(function test2(){
console.log("test2");
})()
复制代码
两个文件单独执行都没问题,可是用gulp打包合并在一块儿应会报错了,天啊,谁能想到呢,
Uncaught TypeError: (intermediate value)(...) is not a function
复制代码
我在第一次遇到的时候真的是很抓狂,查了一成天,明明代码都没有错啊。尤为是打包还会压缩代码,根本看不出是哪里的问题,一开始都觉得是编译器压缩代码出问题了,方向都搞错了,没想到浓眉大眼的家伙也会叛变革命了。
缘由是js自动插分号,因此记得写分号是个好习惯。有些同窗喜欢在文件头加个分号,不要以为奇葩,就是为了不这个问题。
obj.getName() 和 fun= obj.getName; fun(); 效果是不同的, 老生常谈的问题了,当你不了解this的原理时,常常会犯以下的错误
var MyObject = function () {
var self = this;
this.alertMessage = "Javascript rules";
this.OnClick = function() {
alert(self.value);
}
}();
document.getElementById("theText").onclick = MyObject.OnClick
复制代码
复习一下,this 与函数自己以及上下文都没有关系,只取决于函数是如何被调用的。记住这句话永远不会被this搞晕。
this只有4种情景:
a [ "b" ]()
这种形式调用,this指向对象变量不须要var声明就能够赋值(在严格模式下已经禁止) 在控制台输入,name="hello"
刷新页面再执行console.log(name)
值在页面刷新后竟然还在,是否是很震惊。缘由是直接给name赋值至关于给全局变量window.name赋值,window.name是进程级的,不会随窗口刷新消失。这个坑形成的最大问题就是泄露出来不少全局变量。
面试题常常会出这些,就是由于很常见,一不当心就会踩坑,因此记一下经常使用的是颇有必要的,加号操做符的两个操做数若是都是值类型,第一优先级是转换为string(若是其中有一个string),第二优先级是转换为number (若是其中有一个number)
// 1
console.log( 1 + 2 ); // 3
console.log( 1 + 2 +"3" ); //33
console.log( "3" + "4" ); // "34"
// 2
console.log( 1 + "3" ); // "13"
console.log( "3" + 1 ); // "31"
// 3
console.log( 1 + null ); //1
console.log( 1 + undefined ); //NaN
console.log( 1 + NaN );//NaN
// 4
console.log( "3" + null ); //3null
console.log( "3" + undefined ); //3undefined
console.log( "3" + NaN );//3NaN
// 5
console.log( 1 + {} ); //1[object Object]
console.log( 1 + [] ); //1
复制代码
if 检测条件会被隐式转换为boolean类型,以下
if(something){
console.log(something)
}
复制代码
真的要去背一下真值表吗,否则真的很难保证不踩到雷, 总结一下来讲,当值为false,null,undefined,"",0,NaN时if条件不成立,其它条件都成立
如下是常见类型的测试:
if(false){
alert('false'); // false
}
if(undefined){
alert('false'); // false
}
if(null){
alert('null'); // false
}
if(0){
alert('0'); // false
}
if(''){
alert(''); // false
}
if(NaN){
alert('NaN'); // false
}
if(' '){
alert(' '); // true
}
if([]){
alert('[]'); // true
}
if({}){
alert('[]'); // true
}
if(new Object()){
alert('object'); // true
}
复制代码
switch case 若是不写break,会一直往下一个case执行,虽然会带来一些便利,可是会形成逻辑混乱难以读懂,其危害就像臭名昭著的goto语句同样,以下代码:
var type="a";
switch(type){
case "a":
console.log("aaa")
case "b":
console.log("bbb");
case "c":
console.log("cccc")
}
复制代码
代码的执行结果是aaa,bbb,cccc都会输出, 所以,每写一个case都要先写上一个break,避免忘记。若是真的是有几个case须要执行相同的逻辑,那就封装一个闭包函数来调用。
若是你还要兼容IE,这个问题绝对是挥之不去的梦魇
var obj={
a:1,
b:2,
}
复制代码
屡次由于这个问题出现上线后IE9如下浏览器整个挂掉。好在ES5标准已经要求兼容了,IE9以上不会再出现。
本文总结了javascript常见的21个坑,他们有些是语言设计的缺陷,有些是特性原本如此,只是违反咱们的直觉,人们老是在踩坑,错误,失败中不断吸收经验教训不断成长,聪明的人会从别人的问题中吸收经验教训,有了前车可鉴,咱们能够少走不少弯路,加速成长。
最后,推荐一下个人github开源项目:github.com/windyfancy/…
但愿你们多多支持。
参考资料:<javascript语言精髓> Nine Javascript Gotchas