一、普通函数this的指向
1、概述
JavaScript的this无论对于高级还是初学开发人员都是一个比较难理解的知识点,可能因为是实际 的开发项目对JavaScript的使用不是那么的深,只是使用JavaScript的常用知识点。其实,真正对JavaScript使用比较深是做框架开发和封装的。但是,有些 知识点或多或少的会使用到,特别是面试的时候,面试官一般都会问些JavaScript的高级知识点,无论实际项目用不用,这样才能显示公司的实力。所以,我们有必要弄明白一些JavaScript的高级知识点。本章节我们首先讲解this的相关知识点。
2、普通函数的this
2、1 我们先看一个实例:
var a = "1";
function demo() {
var a = "2";
alert(this.a);
}
new demo();
demo();
上面的实例如果你能把两个结果正确的输出就说明你对this掌握的差不多了。我们来具体解析一下:
this永远指向的是直接调用它的那个对象, 这句话理解起来可能不是非常费劲。我们从另一个方面考虑,如果没有实例化对象, 那么this指向的是谁?
首先 ,我们说一下作用域链:一个执行环境的作用域是把自己的作用域放在最顶端,其次是嵌套它的函数也就是父级,最后是 window全局作用域,这样就形成了一个作用域链。下面我们来说一下this的 指向:如果使用了new 实例化了一个对象,那么this就指向实例化的对象,如果没有出现new 实例化,this用于指向的是window。
实例中 new demo(),实例化了对象,这个时候this 就指向了实例化的对象,但是demo并没有使用this关键字定义a变量,虽然使用var定义了a变量,但是var 定义的变量,并不会放在原型链中,也就是它是私有的,外部无法访问。这个时候通过原型链找不到a属性,所以输出:undefined.
实例中demo()直接调用了函数,并没有new实例化对象,这个时候this指向的是window,通过作用域链这个时候this.a==window.a,这个时候this==window,所以输出外侧的变量:1.
我们把上面的实例修改一下进一步的体验:
var a = "1";
function demo() {
var a = "2";
alert(this==window);
alert(this.a);
}
new demo();
demo();
1、new demo()执行第一个alert弹出false,第二个弹出:undefined
2、demo()执行第一个alert弹出true,第二个弹出:1
2、2 另外一个实例
function demo() {
a = "2";
this.a = "11";
alert(a);
}
demo();
alert(a);
上面实例弹出的都是11,这里要说一下变量提升的概念,大家都知道声明变量一般都用var,在函数外部使用var声明的变量,作用域是window, 函数内部声明的变量,如果不使用 var声明的,变量就会提升到 window作用域,使用了var声明的变量作用域就是当前函数的作用域。
这个时候this仍然指向的是window,所以外侧的alert(a) 同样也可以访问a这个变量。
2、3 call修改this指向
var a = 1;
function test() {
var a = 2;
console.log(this.a); // 1
}
test.a = 3;
test();
这个实例中this指向的还是window,所有test.a=3不会有任何的作用,最终,输出的还是1。
那么如何修改this的指向拿,这个时候可以使用call和apply方法,来改变this的指向。这里我们只用call说明实例:
var a = 1;
function test() {
var a = 2;
console.log(this.a); // 3
}
var obj = {
a: 3
};
test.call(obj);
上面实例中,我们把test中的this通过call()方法,指向了obj对象了,所以这个时候this就是 obj了。
二、字面量对象中的this
1、概述
面向字面量对象中的this,也是 一个非常重要的知识点,实际项目开发过程 中使用的面向字面量对象比原型比较多,大部分的图片轮播插件都是使用的它。所以,理解面向字面量对象的this也是刻不容缓的。
2、实例
var age=12;
var people={
age:24,
sayAge:function () {
return age;
},
sayAge1:function () {
return this.age;
}
};
alert(people.sayAge());//12
alert(people.sayAge1());//24
上面实例中分别调用了sayAge()和sayAge1()方法,它们返回的age一个没有this关键字,一个有this关键字。但是,就是因为this关键字它们弹出的结果却不一样,sayAge()弹出:12,sayAge1() 弹出:24.
我们来仔细的分析一下:
首先,上个章节我们说过this永远指向调用它的对象,如果没有实例化对象this永远指向window。面向字面量对象是一个特殊的对象,内部函数已经帮助我们通过 new Object()实例化了字面量对象,所以,我们在使用的时候不需要再次实例化了。
其次,我们知道,在对象的内容调用其他的方法或者属性,一定要使用this关键字,同时,声明的时候如果方法和属性 能够被外侧使用也要使用this关键字。没有用this声明的变量或者方法,外侧是无法访问到的??。
最后,sayAge()方法 直接使用了age,这个时候people在自己的作用域链里面没有查找到age,所以就进行window作用域链的查找,结果找到了。而sayAge1()因为使用了this,people就 查找自己的作用域链,结果就查找到了24.
3、闭包中的this
var age=12;
var people={
age:24,
sayAge:function () {
function innerAge() {
return this.age;
}
return innnerAge;
},
sayAge1:function () {
return this.age;
}
};
alert(people.sayAge());
alert(people.sayAge()());//12,注意是2个括号people.sayAge()()
上面实例 我们在sayAge()方法里面,声明了innerAge()内部函数,并且返回这个函数,这就是所谓的闭包, 调用people.sayAge()()最终弹出的结果是:12。
可能有人会问我这里明明使用了this关键字,为什么返回的最外侧的那个age=12的变量拿?之前我们已经说过了,this永远指向的是调用它的对象,我们虽然通过people.sayAge()(),调用了sayAge()方法,但是因为sayAge() 方法返回的是一个函数,然后我们在执行返回的函数,这就不是对象的调用了,只是普通函数的运行,之前我们说过,普通函数的this永远指向window。所以,innerAge()中的this==window.
如果我们想 使用people的age,可以把上面的代码修改为:
var age=12;
var people={
age:24,
sayAge:function () {
var that=this;
function innnerAge() {
return that.age;
}
return innnerAge;
},
sayAge1:function () {
return this.age;
}
};
alert(people.sayAge()());//24,注意是2个括号people.sayAge()()
把this赋给that变量,这样that就引用了this,然后在进行使用:that.age, 这样获取的就是people里面的age=24的 属性了。
4、作为对象的方法调用
var age = 12;
function sayAge() {
return this.age;
}
var people = {
age: 24,
sayAge: sayAge,
sayAge1: function() {
return this.age;
}
};
alert(people.sayAge());//24
alert(sayAge());//12
上面实例中,我们把普通函数sayAge()作为了people对象的方法,这个时候sayAge()方法中的this,就会指向people。
5、嵌套函数作用域中的this
var a = 1;
function test(){
console.log(this.a); // 2
function test2(){
console.log(this.a); // 1
}
test2();
}
var obj = {a: 2, fn: test};
obj.fn();
上面的例子说明,嵌套函数被调用时并没有继承被嵌套函数的this引用,在嵌套函数被调用时,this指向全局对象。在有些应用中,我们需要在嵌套函数中读取调用被嵌套函数的对象的属性,此时可以声明一个局部变量保存this引用,代码如下所示:
var a = 1;
function test(){
console.log(this.a); // 2
var self = this;
function test2(){
console.log(self.a); // 2
}
test2();
}
var obj = {a: 2, fn: test};
obj.fn();
6、多个对象嵌套中的this
例子1:
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
o.b.fn();
情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。
情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象??。
情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,例子1可以证明,如果不相信,那么接下来我们继续看几个例子。
var o = {
a:10,
b:{
// a:12,
fn:function(){
console.log(this.a); //undefined
}
}
}
o.b.fn();
尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。
还有一种比较特殊的情况,例子2:
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();
这里this指向的是window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。
this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子2中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window,这和例子1是不一样的,例子1是直接执行了fn。
构造函数中的this
1、正常实例
var age = 12;
function people() {
age=50;//对全局变量的修改
this.age=24;//静态属性
}
people.sayAge=function(){//静态方法
console.log(age);
}
people.prototype.sayAge = function () {//实例方法
return this.age;
}
var p=new people();
console.log(people,p);
console.log(p.sayAge());//24
people.sayAge();//50
这里之所以对象p可以点出函数sayAge里面的age是因为new关键字可以改变this的指向,将这个this指向对象p,为什么我说p是对象,因为用了new关键字就是创建一个对象实例。相当于已经复制了一份sayAge函数到对象p中,用了new关键字就等同于复制了一份。下面是new实例化的过程:
new的过程拆分成以下三步:
(1) var p={}; 也就是说,初始化一个对象p
(2) p.proto = Person.prototype;
(3) Person.call(p); 也就是说构造p,也可以称之为初始化p。
从上面的过程就可以知道为什么this会指向p?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。
2、构造函数return实例
var age = 12;
function people() {
age=50;
this.age=24;
return {};
//return {a:'aaa'};
}
people.sayAge1=function(){
console.log(age);
}
people.prototype.sayAge = function () {
return this.age;
}
var p=new people();
console.log(p.sayAge());//p.sayAge is not a function
这个实例 我们在构造函数people里面返回了一个空对象,这个时候调用 p.sayAge()会出现:p.sayAge is not a function错误。为什么会这样?我们再来修改一下代码:
var age = 12;
function people() {
age=50;
this.age=24;
return 1;
//return undefined;
//return null;
//return ;
}
people.sayAge1=function(){
alert(age);
}
people.prototype.sayAge = function () {
return this.age;
}
var p=new people();
alert(p.sayAge());//24
这里我们在构造函数里面return一个数字1、undefined、null、(空),这个时候调用p.sayAge()就能输出24。
下面我们再来修改一下:
var age = 12;
function people() {
age=50;
this.age=24;
return {age:444,sayAge:function(){
return this.age;
}
};
}
people.sayAge1=function(){
console.log(age);
}
people.prototype.sayAge = function () {
return this.age;
}
var p=new people();
console.log(p.sayAge());//444
这里我们返回undefined,p.sayAge()同样可以输出:24。
通过上面的实例,我们可以总结:如果构造函数返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。还有一点就是null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。
3、静态方法中的this
var age = 12;
function people() {
this.age=24;
}
people.sayAge=function(){
console.log(this.age);
}
people.prototype.sayAge = function () {
return this.age;
}
var p=new people();
people.sayAge();
console.log(people.prototype.sayAge());
上面实例第一个 输出:undefined, 第二个也输出:undefined。我们来解释一下:
静态方法不属于实例化对象,是共有的,所以不能有代表某个对象的this。这句话很好理解就是静态方法直接通过函数名就能使用 ,实例化的对象无法访问静态方法。因此, 也就没有所谓的this, 同理也就无法使用this了。
可能还有人会问, 既然sayAge()的this 不是实例化的this, 那么这个时候this应该指向的是window,应该输出:12。好的针对这个问题,我们修改一下方法,打印出this:
people.sayAge=function(){
console.log(this);
alert(this.age);
}
可以看到 结果打印出来的是 function people(){},那就说明this指向的是people这个函数,之前我们说过谁调用的this,this就会指向谁。但是现在是我们直接通过函数people直接调用的方法,这个时候根本就不会在people函数中找到this,所以,不会打印出window的age。
下面我们来总结一下:this为何不能用在静态方法中
在类里面的静态方法是不能访问类的非静态成员(实例成员)的,原因很简单,我们要想在本类的方法中访问本类的其它成员,我们需要使用this这个引用,而this这个引用指针是代表调用此方法的对象,我们说了静态的方法是不用对象调用的,而是使用类名来访问,所以根本就没有对象存在,也就没有this这个引用了,没有了this这个引用就不能访问类里面的非静态成员,又因为类里面的静态成员是可以不用对象来访问的,所以类里面的静态方法只能访问类的静态的属性,既然this不存在,在静态方法中访其它静态成员我们使用的是一个特殊的类“self”;self和this相似,只不过self是代表这个静态方法所在的类。所以在静态方法里,可以使用这个方法所在的类的“类名”,也可以使用“self”来访问其它静态成员。
var age = 12;
function people() {
this.age=24;
}
people.sayAge=function(){
console.log(self.age);
}
people.prototype.sayAge = function () {
return self.age;
}
var p=new people();
people.sayAge();
console.log(people.prototype.sayAge());
console.log(p.sayAge());
终极测试
1、普通函数this
function t(){
this.x=2;
}
t();
console.log(window.x);
输出结果是:2,因为调用t(),这里的this就是window
2、return this的实例
function a(xx) {
this.x = xx;
return this;
};
var x = a(5);
console.log(x);
var y = a(6);
console.log(x,y);
console.log(x.x);
console.log(y.x);
这里主要考虑变量提升和变量重命名的相关知识。
分析如下:
开始执行代码时,会创建一个全局对象window
js执行代码过程可以分: 词法分析期和执行期
第一步词法分析包括:形参分析、实参分析、变量声明分析、函数声明分析。分析出的结果作为对象的属性和方法
window对象在词法分析期 得到的属性和方法有:
window.a=function(xx){this.x=xx;return this}
window.x=undefined
window.y=undefined
第二步代码执行期:
x = a(5); 先执行 window.a(5) –> window.a=function(xx) this.x=xx;return this} ,函数中的this指代对象是window.
得出window.x=5, 此时,全局域中window.x=undefined 变成 window.x=5
然后 return window 赋值给 x 即:x=window ,即window.x=window, window.x=5被替换
y = a(6);—>先执行 window.a(6) –>window.a=function(xx){this.x=xx;return this} ,函数中的this指代对象是window.
得出window.x=6, 把全局域中window.x=window变成 window.x=6
然后 return window 赋值给 y 即:y=window ,即把window.y=undefined 变成了 window.y=window
此时:window.x=6 window.y=window
console.log(x.x); //输出x.x 相当于:(window.x).x=6.x —> window对象中没有 6.x属性 则输出undefined
console.log(y.x); //输出 y.x 相当于:(window.y).x=window.x —> window对象中有window.x这个属性 则输出6
3、面向字面量 this
var obj = {
x: 1,
y: 2,
t: function() {
console.log(this.x)
}
}
obj.t();//1
var dog={x:11};
dog.t=obj.t;
dog.t();//11
show=function(){
console.log('show'+this.x);
}
dog.t=show;
dog.t();//11
作为对象的方法来调用,this指向方法的调用者,即母体对象,不管被调用的函数,声明的时候属于方法,还是函数
function cat(name,age){
this.name=name;
this.age=age;
this.bark=function(){
console.log('i am '+this.name);
}
}
var cat=new cat('huzi',2);
cat.bark();//i am huzi
function pig(){
this.age=99;
return 'abc';
}
var pig=new pig();
console.log(pig);//pig对象
new cat发生以下步骤:
a:系统创建空对象{}。空对象construcor属性指向cat函数
b.把函数的this指向该空对象
c.执行该函数
d.返回该对象
name = 'this is window';
var obj1 = {
name: 'php',
t: function() {
console.log(this.name)
}
};
var dog1 = {
name: 'huzi'
};
obj1.t();//php
dog1.t = obj1.t;
dog1.t();//huzi
var tmp = dog1.t;
tmp(); //this is window
(dog1.t = obj1.t)();//this is window
console.log(dog1.t = obj1.t,(dog1.t = obj1.t));
dog1.t.call(obj1);//php
说一下(dog1.t = obj1.t),这表达式,一定有返回值,在这里返回值肯定是t函数的结果。既然强调是值,说明是立即使用函数,其值本身效果等同于(f(){})(),在这里就立即调用this,指向的是null,被解释器解释成 window。
所以说,有this操作的,如this.age=xx的函数最好不要直接调用,要用new调用,因为直接调用的话,this指向window,会污染全局变量。
自执行、闭包
var number=2;
var obj={
number:4,
fn1:(function(){ // 匿名函数1
var number;
this.number*=2;// (1)
number=number*2;// (2)
number=3;
return function(){ // 匿名函数(2)
var num=this.number;
this.number*=2;
console.log(num);
number*=3;
alert(number);
}
})(),
db2:function(){
this.number*=2;
}
}
var fn1=obj.fn1; // (3)
alert(number);// (4)
fn1();// (5)
obj.fn1();// (6)
alert(window.number);
alert(obj.number);
当定义obj的时候执行了匿名函数1(自执行),此时处于全局作用域内,因此上下文this是window。执行完语句(1)导致全局变量number的值变为4(this.number,number为局部变量var number;);执行语句(2)时临时变量number还没有被赋值,所以是NaN,但下一句会将其赋值为3;最后,匿名函数1返回了匿名函数2,因此obj.fn1=匿名函数2。(注意匿名函数2里面会用到临时变量number,老生常谈的闭包)
来到语句(3),这句会把fn1这个变量赋值为obj.fn1,也就是匿名函数2
由于全局变量number已经在语句(1)中变为了4,所以语句(4)弹出的对话框结果为4
语句(5)执行的是fn1(),它与执行obj.fn1()的区别是两者this不一样。前者为null,而后者this为obj。但是又由于JS规定,this为null时相当于全局对象window,所以这句代码执行时函数的this为window。在匿名函数2里会将全局变量number更新为8,同时将匿名函数1中被闭包的临时变量number更新为9
语句(6)的效果在上面已经分析过了,this是obj,所以obj.number更新为8,闭包的number更新为27