于程序员来说,this这个关键字应该是再熟悉不过了,在C++、C#以及Java中,它的身影随处可见,this也可以说是编程语言JavaScript的精髓,但是一定也有不少JavaScript开发者都遇到过this的陷阱。
在C++、C#、Java中,this拥有十分明确的含义,就是单纯指向当前的对象实例,但是,在动态编程语言JavaScript中,相同的this写法,却有不同的含义。
JavaScript中的this代表函数运行时自动生成的一个内部对象,只能在函数内部使用。并且,你不能在创建统计this的时候就确定它,而应该在函数执行时进行确定,所以,一不留神,你就会掉进意想不到的坑!
0、谁调用了就谁来负责
我们先来看看代码:
var name='a';
var obj={
name: 'b',
getName: function() {
console.log(this.name);
}
};
obj.getName(); // b
var getName=obj.getName;
getName(); // a
var obj2=(function() {
return function() {
console.log(this.name);
}
})();
obj2(); // a
首先,obj.getName()打印出来b,因为,obj调用了getName(),所以其this指向obj,this.name就是b。
其次,getName()打印出a的原因:理由是此时的obj并不调用getName(),而是全局变量window,因此,结果是a
最后,obj2中,因为采用了立即执行函数,它返回一个函数,而当我们调用时,调用它的也是全局变量window,所以打印结果也是a。
需要注意的是,匿名函数内的this是指向window的,更准确的说是指向调用者。
1、必须了解new的过程
function Test() {
console.log(this.name);
}
Test(); // a
var test=new Test(); // undefined
function Test2() {
this.name='c';
}
var test2=new Test2();
console.log(test2.name); // c
为什么这个例子最终打印出来的是c呢?这里new的过程不可忽略,new Test2() 其实执行了三步动作:
首先,创建一个空对象obj(var obj={};),然后,将这个空对象的[[Prototype]](__proto__)成员指向了Test2函数对象prototype成员对象最后,将Test2函数对象的this指针替换成obj:
obj.__proto__=Test2.prototype;
Test2.call(obj);
当没有显式返回值或者返回为基本类型、null时,默认将 this 返回(参考2)
因此,在这个例子中,当你使用new的时候,函数内部的this指向已经改变了,不再指向window。
主要应该搞清楚new的过程,然后就知道打印出c的原因了。
2、奇怪的return
看过上面的例子后,你是否觉得已经掌握了new的函数内的this指向?千万别掉以轻心,在JavaScript中,多的是例外,如:
function Test3() {
this.name='d';
return {};
}
var test3=new Test3();
console.log(test3.name); // undefined
function Test4() {
this.name='d';
return 1;
}
var test4=new Test4();
console.log(test4.name); // d
可以再试试return 'd' | null | undefined中的一个或更多类型。
结论:如果return的是一个对象(null除外),那么this指向的这个对象,否则this依旧指向实例对象。
当下 Web 编程大行其道时,JavaScript无疑是使用最为广泛的前端语言。JavaScript为我们提供了极佳的体验,同时,我们在使用JavaScript时,又经常会因为JavaScript的一些奇怪的特性所坑。当然我们可能用一些简单的方法即可避免这些问题。通过这些简单的做法,你可以使JavaScript成为一门更好的语言,也让你自己成为一个更好的程序员。
==、!=操作符
JavaScript有两组相等运算符:===和!==,以及它们邪恶的李生兄弟==和!=。===和!==这一组运算符会按照你期望的方式工作。如果两个运算数类型一致且拥有相同的值,那么===返回true, !==返回false。而它们邪恶的孪生兄弟只有在两个运算数类型一致时才会做出正确的判断,如果两个运算数是不同的类型,它们试图去强制转换值的类型。转换的规则复杂且难以记忆。这里有一些有趣的例子:
''=='0' //false
0=='' //true
0=='0' //true
false=='false' //false
false=='0' //true
false==undefined //false
false==null //false
null==undefined //true
' \t\r\n'==0 //true
==运算符对传递性的缺乏值得我们警惕。我的建议是永远不要使用那对邪恶的孪生兄弟。相反,请始终使用===和 !==。如果以上所有的比较使用===运算符,结果都是 false。
with 语句
JavaScript提供了一个with语句,本意是想用它来快捷地访问对象的属性。不幸的是,它的结果可能有时不可预料,所以应该避免使用它。下面的语句:
with (obj) { a * b;}
和下面的代码做的是同样的事情:
if (obj.a===undefined) {
a * obj.b===undefined ? b : obj.b;
} else {
obj.a=obj.b===undefined ? b : obj.b;
}
所以,它等于这些语句中的某一条:
a=b
a=obj.b;
obj .a=b;
obj.a=obj.b;
通过阅读程序代码,你不可能辨别出你会得到的是这些语句中的哪一条。它可能随着程序运行到下一步时发生变化。它甚至可能在程序运行过程中就发生了变化。如果你不能通过阅读程序而了解它将会做什么,你就无法确信它会正确地做你想要做的事情。
With语句在这门语言里存在,本身就严重影响了 JavaScript处理器的速度,因为它阻断了变量名的词法作用域绑定。它的本意是好的,但如果没有它,JavaScript语言会更好一点。
eval 语句
eval函数传递一个字符串给JavaScript编译器,并且执行其结果。它是一个被滥用得最多的JavaScript特性。那些对JavaScript语言一知半解的人们最常用到它。例如,如果你知道点表示法,但不知道下标表示法,就可能会这么写:
eval ("myValue=myObject." + myKey + ";")
而不是这么写:
myvalue=myObject[myKey];
使用eval形式的代码更加难以阅读。这种形式使得性能显著降低,因为它需要运行编译器,但也许只是为了执行一个微不足道的赋值语句。它也会让JSLint 失效,让此工具检测问题的能力大打折扣。eval函数还减弱了你的应用程序的安全性,因为它给被求值的文本授予了太多的权力。而且就像with语句执行的方式一样,它降低了语言的性能。Function构造器是eval的另一种形式,同样也应该避免使用它。浏览器提供的setTimeout和setlnterval函数,它们能接受字符串参数或函数参数。当传递的是字符串参数时,setTimeout和setlnterval会像eval那样去处理。同样也应该避免使用字符串参数形式。
位运算符
JavaScript有着与Java相同的一套位运算符:
& and按位与
| or按位或
^ xor按位异或
~ not按位非
>> 带符号的右位移
>>> 无符号的(用0补足的) 右位移
<< 左位移
在Java里,位运算符处理的是整数。JavaScript没有整数类型,它只有双精度的浮点数。因此,位操作符把它们的数字运算数先转换成整数,接着执行运算,然后再转换回去。在大多数语言中,这些位运算符接近于硬件处理,所以非常快。但JavaScript的执行环境一般接触不到硬件,所以非常慢。JavaScript很少被用来执行位操作。还有,在JavaScript程序中,& 非常容易被误写为 && 运算符。位运算符出现在JavaScript中降低了这门语言的冗余,使得bug更容易被隐藏起来。
function 函数对比 function 表达式
JavaScript既有function语句,同时也有function表达式。这令人困惑,因为它们看起来好像就是相同的。一个function语句就是其值为一个函数的var语句的速记形式。
下面的语句:
function foo () {}
意思相当于:
var foo=function foo () {};
我们建议使用的是第2种形式,因为它能明确表示foo是一个包含一个函数值的变量。要用好这门语言,理解函数就是数值是很重要的。
function语句在解析时会发生被提升的情况。这意味着不管function被放置在哪里,它会被移动到被定义时所在作用域的顶层。这放宽了函数必须先声明后使用的要求,而我认为这会导致混乱。在if语句中使用function语句也是被禁止的。结果表明大多数的浏览器都允许在if语句里使用function语句,但它们在解析时的处理上各不相同。这就造成了可移植性的问题。
一个语句不能以一个函数表达式开头,因为官方的语法假定以单词 function 开头的语句是一个function语句。解决方法就是把函数调用括在一个圆括号之中。
(function () {
var hidden_variable;
//这个函数可能对环境有一些影响,但不会引入新的全局变量,
})();
于程序员来说,this这个关键字应该是再熟悉不过了,在C++、C#以及Java中,它的身影随处可见,this也可以说是编程语言JavaScript的精髓,但是一定也有不少JavaScript开发者都遇到过this的陷阱。
在C++、C#、Java中,this拥有十分明确的含义,就是单纯指向当前的对象实例,但是,在动态编程语言JavaScript中,相同的this写法,却有不同的含义。
JavaScript中的this代表函数运行时自动生成的一个内部对象,只能在函数内部使用。并且,你不能在创建统计this的时候就确定它,而应该在函数执行时进行确定,所以,一不留神,你就会掉进意想不到的坑!
0、谁调用了就谁来负责
我们先来看看代码:
var name='a';
var obj={
name: 'b',
getName: function() {
console.log(this.name);
}
};
obj.getName(); // b
var getName=obj.getName;
getName(); // a
var obj2=(function() {
return function() {
console.log(this.name);
}
})();
obj2(); // a
首先,obj.getName()打印出来b,因为,obj调用了getName(),所以其this指向obj,this.name就是b。
其次,getName()打印出a的原因:理由是此时的obj并不调用getName(),而是全局变量window,因此,结果是a
最后,obj2中,因为采用了立即执行函数,它返回一个函数,而当我们调用时,调用它的也是全局变量window,所以打印结果也是a。
需要注意的是,匿名函数内的this是指向window的,更准确的说是指向调用者。
1、必须了解new的过程
function Test() {
console.log(this.name);
}
Test(); // a
var test=new Test(); // undefined
function Test2() {
this.name='c';
}
var test2=new Test2();
console.log(test2.name); // c
为什么这个例子最终打印出来的是c呢?这里new的过程不可忽略,new Test2() 其实执行了三步动作:
首先,创建一个空对象obj(var obj={};),然后,将这个空对象的[[Prototype]](__proto__)成员指向了Test2函数对象prototype成员对象最后,将Test2函数对象的this指针替换成obj:
obj.__proto__=Test2.prototype;
Test2.call(obj);
当没有显式返回值或者返回为基本类型、null时,默认将 this 返回(参考2)
因此,在这个例子中,当你使用new的时候,函数内部的this指向已经改变了,不再指向window。
主要应该搞清楚new的过程,然后就知道打印出c的原因了。
2、奇怪的return
看过上面的例子后,你是否觉得已经掌握了new的函数内的this指向?千万别掉以轻心,在JavaScript中,多的是例外,如:
function Test3() {
this.name='d';
return {};
}
var test3=new Test3();
console.log(test3.name); // undefined
function Test4() {
this.name='d';
return 1;
}
var test4=new Test4();
console.log(test4.name); // d
可以再试试return 'd' | null | undefined中的一个或更多类型。
结论:如果return的是一个对象(null除外),那么this指向的这个对象,否则this依旧指向实例对象。
*请认真填写需求信息,我们会在24小时内与您取得联系。