换个角度看javascript中的this
不论是面向对象,还是基于对象的语言,都会有this,我更喜欢叫他this指针,如果你不理解指针,认为它是个引用也无妨。
这一片文章就是整理一下在各个情况下的this到底引用的是谁。一次来明白this的用法,下面将是一段段的代码,每段代码后面可能有简短的说明,就是这样简单粗暴。
说明一下,这篇文章是基于浏览器的,不是原生js,区别在于浏览器全局中的this是Window,而原生js中是global。其次,博主使用的控制台输出,如果你使用document.write方法或alert输出this,由于这两个方法会调用对象的toString方法,你会得到[object window]或[object object]。
注意:本文中对一般函数和普通函数的措辞,这个只是博主个人的说法,由于上下文(context)的解释并不是很容易懂,博主自定义了这2个说法,帮助理解。
普通函数中的this
function f(){
console.log(this); //Window
}
在js中,凡是没有定义在对象、构造函数或prototype中的函数,其中的this都是全局对象Window。下文把这样的函数称为一般函数
var a = [1,2,3,4,5];
var b = a.map(function(x){
console.log(this); //Window
return x * 2;
});
同理上面这个函数也没有定义在对象、构造函数或者prototype里,所以得到的依然是Window。
注意:Array.prototype.map是定义在数组原型中的,但是给map传进去的参数函数就是一个一般函数
构造函数中的this
function Person(n, a, g){
this.name = n;
this.age = a;
this.gender = g;
console.log(this);
}
//作为构造函数使用
var o = new Person("Lily", 18, "F"); //this为当前对象 Person {name: "Lily", age: 18, gender: "F"}
//作为普通函数使用(**非构造函数使用方式(new方式)以外的函数都是普通函数**)
Person("Lily", 18, "F"); //Window
上面代码说明一下几点:
- 用new创建对象的时候调用了构造函数。
- 构造函数和普通函数的区别在于调用方式,而不是定义方式,如果按第10行的方式调用,他就是个普通函数,由于普通函数中的this是于Window,所以上面函数在第8行调用后创建了3个全局变量。
- new关键字改变了函数内this的指向,使其指向刚创建的对象。
function Person(n, a, g){
this.name = n;
this.age = a;
this.gender = g;
this.speak = function (){ //这里只是说明this,实际应该在prototype上定义对象方法
console.log(this);
};
}
//作为构造函数使用
var o = new Person("Lily", 18, "F");
o.speak(); //Person {name: "Lily", age: 18, gender: "F"}
//作为普通函数使用
Person("Lily", 18, "F");
speak(); //Window
- 对象方法中的this同样指向当前对象
- 第14行之所以可以调用speak(),是因为第13行执行后在全局创建了speak函数,印证了之前的说法。
多说一句,为什么11行得到的是Person{...},而不是Object{...}。其实这里显示的本来就应该是构造函数的名字,如果你通过var o = {};创建的对象,相当于o = new Object();,这时显示的才是Object
function Person(n, a, g){
this.name = n;
this.age = a;
this.gender = g;
}
Person.prototype.speak = function (){ //这里只是说明this,实际应该在prototype上定义对象方法
console.log(this);
};
//作为构造函数使用
var o = new Person("Lily", 18, "F");
o.speak(); //this为当前对象 Person {name: "Lily", age: 18, gender: "F"}
//作为普通函数使用
Person("Lily", 18, "F");
speak(); //ReferenceError: speak is not defined
由此可见prototype中的方法和构造函数中直接定义方法中this是一样的。
最后一行出现错误,这个不难理解,这里不多说了。
区分一下面这个具体问题:
<html>
<body>
<button onclick="clickme()">Click Here</button>
<button id="btn">Click Here</button>
<body>
<script>
function click(){
console.log(this); //window
}
var btn = document.getElementById("btn");
btn.onclick = function(){
console.log(this);
};
</script>
</html>
第一个按钮得到Window,而第二个得到input元素!为什么!
再想想,click函数定义在全局,不在对象上。而btn.onclick = function(){}中的函数明显是在btn对象上定义的。
对象方法中的闭包
说闭包前先理解一个简单的:
var o = {
name: "Lily",
age: 18,
gender: "F",
speak: function (){
function fun(){
console.log(this);
}
fun();
}
};
o.speak(); //Window
什么,这里是Window了?对!我们仔细想想,这个fun函数是对象的方法吗?显然不是,它是个一般函数。它仅仅是在另一个函数中的一个函数,显然符合上文描述的:“凡是没有定义在对象、构造函数或prototype中的函数,其中的this都是Window”
如果想在内部函数访问这个对象,也很好解决:
var o = {
name: "Lily",
age: 18,
gender: "F",
speak: function (){
var _this = this; //首选_this,有的资料上会用self。
function fun(_this){
console.log(_this);
}
fun();
}
};
o.speak(); //Object {name: "Lily", age: 18, gender: "F"}
下面做个闭包,为了说明this的值,这里不定义太多变量,如果对闭包和作用域有疑惑可以参看博主的另一篇文章:作用域链与闭包
var o = {
name: "Lily",
age: 18,
gender: "F",
speak: function (){
return function(){
console.log(this);
}
}
};
o.speak()(); //Window
这个难理解吗?返回的函数依然是个定义在别的函数里面的一般函数。如果想让返回的函数可以继续访问该对象,依然使用上面的var _this = this解决。不过这里引出了一个新的问题:
var o = {
name: "Lily",
age: 18,
gender: "F",
speak: function (){
console.log(this);
}
};
var fun = o.speak;
fun(); //Window
什么?这里还是Window!o.speak明显是一个对象方法啊!那么问题来了?第10行调用的是谁?是fun函数。那么fun函数怎么定义的?对,fun的定义决定它是一个一般函数。那怎么解决?这个不用解决,没人会试图在对象外获取对象方法,即便是有需要也应该获取对象方法内的闭包。当然,如果你要强行解决它,那就用bind方法吧。
bind call和apply方法
这3个方法用来改变调用函数内的this值
bind方法
将对象绑定到函数,返回内部this值为绑定对象的函数。
如果我们不能修改库中对象的方法,我们就不能用var _this = this;的方法改变this值,那么我们换个角度考虑上面的问题:
var o = {
name: "Lily",
age: 18,
gender: "F",
speak: function (){
return function(){
console.log(this);
}
}
};
o.speak()(); //Window
最后一行中,o.speak()执行完后得到一个函数,这是个临时函数,定义在全局作用域,如果我们把这个临时函数绑定到o对象上,再继续调用这个函数不就可以了么:
var o = {
name: "Lily",
age: 18,
gender: "F",
speak: function (){
return function(){
console.log(this);
}
}
};
o.speak().bind(o)(); //Object {name: "Lily", age: 18, gender: "F"}
call方法 和 apply方法
这里举几个和上文不一样的例子
function Animal(){
this.name = "Animal";
}
Animal.prototype.showName = function(){
alert(this.name);
};
function Cat(){
this.name = "cat";
}
var cat = new Cat();
这里Cat没有showName方法,怎么实现输出名字呢?
有c++和java经验的人会认为猫属于动物,所以Cat应该继承Animal,所以我们可以这样修改:
function Animal(){
this.name = "Animal";
}
Animal.prototype.showName = function(){
alert(this.name);
};
function Cat(){
this.name = "cat";
}
Cat.prototype = Animal.prototype;
var cat = new Cat();
cat.showName(); //Cat
或者:
function Animal(){
this.name = "Animal";
}
Animal.prototype.showName = function(){
alert(this.name);
};
function Cat(){
Animal.call(this, "cat"); //继承
}
var cat = new Cat();
cat.showName(); //Cat
有c++和java经验就会知道,在做一个大型项目之前都是要做UML设计的,用例图、活动图、类图、状态图等等十几种图,对于没有一定经验的开发者做这个简直就是噩梦,而js把各种类或模块独立出来,需要的时候用call、bind、apply把多个类联系起来,这样的做法即简化了设计,又简化了维护。
所以js里面很少有上面的写法,怎么写看下面:
function Animal(){
this.name = "Animal";
}
Animal.prototype.showName = function(){
alert(this.name);
}
function Cat(){
this.name = "Cat";
}
var cat = new Cat();
Animal.prototype.showName.call(cat); //cat
Animal.prototype.showName.apply(cat); //cat
对,不过感觉那里怪怪的,call和apply一样?他们功能上一样,只是接受的参数不同,简单写就是下面这样:
func.call(func1,var1,var2,var3);
func.apply(func1,[var1,var2,var3]);
它们的第一个参数都是指定调用该函数的对象,如果为空就是全局对象。后面的时传入该函数的参数,区别在于使用call时参数逐一传入,而使用apply时参数构成一个数组或类数组对象传入。
实例
例子1:
//求下列数组元素的最大值
var numbers = [5, 6, 9, 3, 7];
var numbers = [5, 6, 9, 3, 7];
var maxValue = Math.max(numbers);
alert(maxValue); //NaN
maxValue = Math.max.apply(null, numbers);
alert(maxValue); //9
//否则你只能这么写:
var max = +Infinity;
for (var i = 0; i < numbers.length; i++) {
if (numbers[i] > max)
max = numbers[i];
}
例子2
//自定义typeof函数(注意,系统自带的typeof是运算符,不是函数)
function typeOf(o){
return Object.prototype.toString.call(o).slice(8,-1);
}
//自定义typeOf函数测试
console.log(typeOf (2.1)); //Number
console.log(typeOf (undefined)); //Undefined
console.log(typeOf ({})); //Object
console.log(typeOf ("hello")); //String
console.log(typeOf (false)); //Boolean
console.log(typeOf (typeOf)); //Function
console.log(typeOf (null)); //Null
console.log(typeOf ([])); //Array
console.log(typeOf (new Date)); //Date
console.log(typeOf (/\d/)); //RegExp
console.log(typeOf (document. getElementsByTagName('body')[0])); //HTMLBodyElement
//系统typeof运算符测试
console.log(typeof (2.1)); //number
console.log(typeof (undefined)); //Undefined
console.log(typeof ({})); //object
console.log(typeof ("hello")); //string
console.log(typeof (false)); //boolean
console.log(typeof (typeOf)); //function
console.log(typeof (null)); //object
console.log(typeof ([])); //object
console.log(typeof (new Date)); //object
console.log(typeof (/\d/)); //object
console.log(typeof (document. getElementsByTagName('body')[0])); //object
//明显比系统自己的实用多了
例子3
//把类数组对象转为数组(类数组对象就是属性key为0,1,2,...,还具有一个key为length的可以像数组一样动态改变的值的对象)
function(){
return Array.prototype.slice.call(arguments);
}
例子4
//用js访问元素伪类
function getRuleSelector(selector){
return Array.prototype.filter.call(getCssList(), function(x){
return pure(x.selectorText) === pure(selector);
});
function pure(selector){
selector.replace(/::/g, ":"); //把双冒号替换为单冒号
}
function getCssList(){
return Array.prototype.concat.apply([], Array.prototype.map.call(document.styleSheets, function(x){
return Array.prototype.slice.call(x.cssRules);
}));
}
}

浙公网安备 33010602011771号