全局变量
可以先看W3C:JavaScript 全局对象、MDN:this
全局变量,W3C里说得很清楚(JavaScript Window - 浏览器对象模型):
alert(window.eval===eval); // true
alert(window.Object===Object); // true
alert(window.Math===Math); // true
alert(window.document===document); // true
// 我们一开始就使用那些函数方法,属性都是window对象的属性吗?
浏览器对象模型
BOM
(Browser Object Model) 使 JavaScript 有能力与浏览器“对话”。
还有一个DOM
(文档对象模型,这是对HTML
进行操作的根本)这里略过。
JavaScript就是那么奇葩,我们在函数外部声明的所有全局变量,其实都是全局对象的属性!JavaScript除了数值、布尔、字符串、null、undefined
之外,都是对象(可以添加属性)!
// 预定义的全局变量、函数(方法)都是window对象的属性
alert(typeof window.Number) // function
alert(typeof window.Math) // object
alert(typeof window.Math.random) // function
// 更改全局变量
NaN = 11;
eval = 22;
Object = 33;
Math = 44;
console.log(window.Math); // 44
console.log(typeof window.Math) // number 全局对象被改写
alert(NaN); // NaN
alert(eval); // 22
alert(Object); // 33
alert(Math); // 44
// 这里可能有一个误区:全局对象不是window吗?
// 实际上:window也是另外一个全局对象,但是前提是运行在浏览器端
// window全局对象提供了与当前窗口、页面有关的诸多属性与方法。
// 除了这些与浏览器有关的全局属性和方法,window对象还封装了JS全局对象,并向外暴露JS全局对象的属性与接口
看,这些预定义好的全局对象、方法我们都可以直接改写(当然没人去这么干),当然有些不能改写(NaN
),不同浏览器也有不同支持。
具体可以看这两位大神的总结,相当好:JavaScript中的全局对象介绍
浅析JavaScript中两种类型的全局对象/函数
这里开始以this
的应用为主,杂揉了一些知识。因为我第一次写这类感想文,编排不好后续会慢慢更改。
有关全局变量的测试:
var a = 1;
alert(this.a); // 1
alert(this); // "[object Window]"
这里定义了一个全局变量a,之后调用this.a
和this
。结果大家看到了。全局变量a
的作用域是整个全局对象内可见,或者说变量a是全局对象的属性。不信?看下面:
var woshiSB = 1;
console.log(this);
var write = "";
for (var i in this) {
write += i+"<br />"
}
document.write(write);
看到了下面那个了?没错,还记得for-in
语句干啥的嘛?遍历一个对象里的可枚举属性(图中只截取了部分全局对象(变量)),之后我们发现了了它woshiSB
,这可以说在外部环境、全局上下文环境中的全局变量是全局对象的属性。反过来,我们也可以通过在全局对象里创建一个全局变量。且,如果能确定上下文(在任何函数体外部),满足这个前提条件,这个this
指的就是全局对象了。且,我们可以:
console.log(this.document === document); // true
console.log(this.Number===Number); // true
// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true
this.a = 37; // 此时this引用指向全局对象,所以a是全局对象的属性
console.log(window.a); // 37
window.b = 38; // 同样的,全局对象window下的属性是全局变量
console.log(this.b); // 38
this.x = 1;
alert(this.x===window.x) // true
this
引用的几种情况(依据上下文)
第一种:
全局上下文(引用自MDN)
在全局运行上下文中(在任何函数体外部),
this
指代全局对象,无论是否在严格模式下。
console.log(this.document === document); // true
// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
关于严格模式,请看真•究极大神 阮一峰:Javascript 严格模式详解
function f1(){
return this;
}
f1() === window; // true
不是在严格模式下执行,this
的值不会在函数执行时被设置,此时的this
的值会默认设置为全局对象。
这里提醒一下:严格的说,this
全称叫this
引用。注意引用二字。
function f2(){
"use strict"; // 这里是严格模式
return this;
}
f2() === undefined; // true
在严格模式下,如果this
未被执行的上下文环境定义,那么它将会默认为undefined
。
所谓上下文,加几个字理解,想到语文里的“联系上下文”了吗?对,this
引用所处的环境,或者说它位于的作用域。
第二种:
函数上下文
在函数内部,
this
的值取决于函数是如何调用的。
包括引用的文章也提到:
如果这个
this
属于某个function
,那么this
指代的就是调用该function
的对象。若这种情况下function
只是一个普通的函数,而不是某个类的方法,那么this
的指代存在两种可能:1.在ECMAScript 3标准,以及ECMAScript 5标准的非严格模式下,
this
指代全局对象。2.在ECMAScript 5标准的严格模式下,
this
指代undefined
。
函数上下文 —— 对象方法中的
this
:
var obj = {
x:"x",
fn:function () {
return this.x;
}
};
console.log(obj.fn())
此时,obj.fn()
中的this
引用将会指向obj
这个全局对象,也就是obj.x
var obj = {x:"x"};
function test(obj) {
return this.x;
};
obj.fn = test;
alert(obj.fn());
在何处或者如何定义调用函数完全不会影响到this的行为。我们在定义obj的时候定义了一个obj.fn()
方法。但是,我们也可以首先定义函数然后再将其附属到o.f
。这样做this
的行为也一致。
来看另一个例子:
var f = function (x) {
this.y = x+1;
};
var a = {y:10,op:f};
var b= {y:20,increment:f};
a.op(100); // 通过a来调用f,this引用指向a引用的对象
alert(a.y); // 101
b.increment(43); // 通过b来调用,this引用指向b引用的对象
alert(b.y); // 44
f
引用的匿名函数里的this
引用指向调用它的对象。
函数上下文 —— 构造函数中的
this
:
我们来看CDN的例子(改写过):
function c(){
this.a = 37;
};
c.prototype.test = "test";
var o = new c();
o.sb = "Sb"; // o也是对象
console.log(o.a); // logs 37
console.log(o.__proto__===c.prototype); // true
console.log(o.sb); // "Sb"
Object.prototype.op = "op";
console.log(c.prototype.__proto__===Object.prototype) // true
console.log(Object.prototype.__proto__); // null
console.log(Object.prototype.constructor===Object); // true
看到这的的默认你已经懂了构造函数大部分哈。(我们先读一遍,对,首先要看懂。)
-
声明一个
c
函数,同时内建一个this
引用,当然我们并不知道这个引用指向谁。将c
函数作为构造函数调用,(此时:构造函数会隐式生成一个对象,对象内建的隐式链接指向构造函数的prototype
对象),这个新对象的引用赋值给全局变量o
,注意是引用赋值,这个变量同时也是对象,Sb
那里也看到了。现在你脑子里是不是生成了一副引用指向图了呢?把它画出来你就都知道了。
变量o
引用了新对象,则构造函数里的this
引用就是指向o
,这里展现的是原型链,关于原型链我还不够了解后续会继续学习;
再来看另一个:
function c2(){
this.a = 37;
return {a:38};
};
o = new c2();
console.log(o.a); // logs 38
在最后的例子中(
C2
),因为在调用构造函数的过程中,手动的设置了返回对象,与this
绑定的默认对象被取消(本质上这使得语句this.a = 37
成了“僵尸”代码,实际上并不是真正的“僵尸”,这条语句执行了但是对于外部没有任何影响,因此完全可以忽略它)。———————————— 引自CDN
其实我对CDN的解释也不太懂,可以认为,重新改变o
引用的对象,则也改变了this
引用的对象。则this.a
肯定指向新对象。
再来看书中的例子:
var Point = function (x,y) {
this.x = x;
this.y = y;
};
var p = new Point(4,-5);
var q = Point(3,8);
console.log(typeof q); // "undefined"
p变量引用了构造函数新生成的变量,this.x
、this.y
所以相当于p.x
与p.y
。调用构造函数,传参后,就赋值等等。
而变量q
,注意这里只是将Point
当做普通函数调用而已,执行函数主体后,也没有返回值。So,q
的值类型是undefined
。
再来看另一个例子:
var obj = {
x:3,
doit:function () {
console.log("method is called."+this.x);
}
};
// 1
var fn = obj.doit; // 将obj.doit引用的Function对象赋值给全剧变量
fn(); // "method is called.undefined"
// 2
var x = 5;
fn(); // "method is called.5"
// 3
var obj2 = {x:4,doit2:fn};
obj2.doit2(); // "method is called.4"
全局变量obj
引用对象,内含obj.x
和一个方法,方法内存在this
引用。
第一次调用:将对象内部doit
方法赋值给全部变量fn
,此时调用fn
。那么我想问,是谁调用fn
呢?是全局对象。而全局变量是全局对象的属性。也就是说上下文内存在的全局变量都对全局对象可见,作用域嘛。此时,既然全局对象调用函数方法,则this
引用当然指向全局对象。可是,全局对象并没有属性x(或者说没有全局变量x)所以是undefined
。
第二次调用//2
:就是反证第一次的,说明this
引用的确引用了全局对象,或者我们看这个:
var test = {
z:"test-z",
fn:function () {
console.log(this.z)
}
};
window.fun = test.fn; // 全局对象的fun方法
var z = "var z"; // 全局变量z
window.fun(); // result:"var z" this引用指向全局对象
var z1 = "var z1"; // 全局变量z1
test.z1 = "test.z1"; // 属性z1
test.fn2 = function () {console.log(this.z1)}
test.fn2(); // result:"test.z1" this引用指向test对象
所以当第三次调用时,doit2
引用的Funcion
对象内部的this
引用指向的是obj2
对象。相当于obj2.x
这里要提一下之后要学到的apply
和call
方法,它们作用就是显式的指定this
引用要引用的对象,或者说显式指定接收方对象。
嵌套一个来试试:
var x = "var x";
var fn = function () {alert(this.x)};
var obj = {
x:"obj.x",
fn:function () {alert(this.x)},
obj2:{
x:"obj2.x",
fn2:function () {alert(this.x)}
}
};
// 直接调用
obj.obj2.fn2(); // obj2.x
obj.fn(); // obj.x
fn(); // var z
哪怕跨级调用,由于搜索都是按作用域由内之外,所以引用的是最近的对象。所以这应该是MDN里那句话的意思了。
类似的,
this
的绑定只受最靠近的成员引用的影响
再来看一个变形:
var obj = {
x:"obj.x",
doit:function () {
console.log("doit is called."+this.x); // 这里的this.x:obj.x
this.test.doit2();
// 这里的this.x:"test.x"
// "true"
},
test:{
x:"test.x",
doit2:function () {
var x = "doit2-x";
console.log("doit2 is called."+this.x+" | "+this);
// 这里的this.x:"test.x"
console.log(this===window.obj.test && this===obj.test);
// "true" this引用指向嵌套对象:test
}
}
};
obj.doit(); // this.x: "obj.x"
var x = "var z";
window.obj.test.doit2(); // this.x:"test.x"
最后提一下原型链中的this
引用:
var o = {
f : function(){
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
alert(p.__proto__===o); // true
如果该方法存在于一个对象的原型链上,那么
this
指向的是调用这个方法的对象,表现得好像是这个方法就存在于这个对象上一样。在这个例子中,对象
p
没有属于它自己的f属性,它的f属性继承自它的原型。但是这对于最终在o
中找到f
属性的查找过程来说没有关系;查找过程首先从p.f
的引用开始,所以函数中的this
指向p
。也就是说,因为f
是作为p
的方法调用的,所以它的this
指向了p
。这是JavaScript的原型继承中的一个有趣的特性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。