js作用域使用方法说明

作者:袖梨 2022-11-14

一个面试题以它来详细介绍js作用域

代码如下 复制代码

function foo(){
foo.abc = function(){alert('Alibaba')}
this.abc = function(){alert('Alimama')}
abc = function(){alert('Alipay')};
var abc = function(){alert('Taobao')}
}
foo.prototype.abc = function(){alert('Alisoft');}
foo.abc = function(){alert('Yahoo');}
var obj = new foo();
obj.abc();
foo.abc();
abc();


我们就直接针对这输出按顺序进行分析,

执行顺序

代码如下 复制代码

1.foo.prototype.abc = function(){alert('Alisoft');}

//这样之后,我们就可以用obj.abc();不懂prototype原型的,可以看这个prototype原型继承

2.foo.abc = function(){alert('Yahoo');}

//alert yahoo

3.var obj = new foo();

//创建一个foo的实例obj,同时,执行了foo函数,也就是obj.abc() = function(){alert('Alimama')}

4.foo.abc = function(){alert('Alibaba')}

//foo.abc是foo类的静态方法,在实例化foo后执行了代码片段foo.abc = function()

//覆盖了原来的foo.abc = function(){alert('Yahoo');},所以foo.abc()输出alibaba

5.this.abc = function(){alert('Alimama')}

//这句话把function(){alert('Alimama')赋给了obj.abc,所以obj.abc输出alimama

6.abc = function(){alert('Alipay')}; var abc = function(){alert('Taobao')};

//这两句一起分析,如果没有下一句,那么abc是个全局变量,abc输出alipay

//但是因为下一句var abc之后,abc的作用域被限制在foo类里,所以外部的abc()会显示未定义.

那么,答案就是

alimama

alibaba

undefine

闭包:表示能访问和操作其他本地作用域的变量的表达式(通常是函数)。

作用域

表示变量或函数起作用的区域,指代了它们在什么上下文中执行。Javascript总作用域一共有两种:全局作用域和本地作用域。在Javascript中本地作用域是按照函数来区分的。

代码如下 复制代码

var global = "11"; //全局作用域

function fun() {

var local = "22";//本地作用域1

}

function fun2() {

var local2 = "22";//本地作用域2

for (var i=0;i<100;i++) {

//本地作用域2

}

}

不象Java语言那样for循环也是一个作用域,在Javascript中本地作用域是按照函数来区分的。Javascript的作用域概念类似词法作用域。
词法作用域:也叫做静态作用域,变量的作用域是在定义时决定而不是执行时决定。即词法作用域取决于源码,通过静态分析就能确定。

作用域链

与“原型链”类似,Javascript的作用域链也是按有顺序查询的。在访问变量时,先查看本地作用域是否有此变量,如果没有则依次向上一级作用域查找直到全局作用域。如果全局作用域也没有此变量,那么将会在全局作用域中声明这个变量。如下的代码:

代码如下 复制代码

function f() {

name = "global";

var local = "local";

}

f();

console.log(name); //global

console.log(local);//undefine


name变量被声明在全局作用域。

上下文

又可以理解为上下文对象,表示当前代码执行时所处的环境。即是this变量所指代的对象;这么理解比较困难,直接看个例子:

代码如下 复制代码

function Test() {

console.log(this);

}

Test(); //window

new Test();//Object


在执行Test()时,此时的上下文对象是window,即Javascript的全局对象!在执行new Test();时新建了一个Object,此时执行Test函数的上下文对象就是Object。如果还不理解,可以看我的如何用Javascript实现面向对象编程一文中有一段代码如下:

代码如下 复制代码

function newObj(Fun,arguments) {

var o = {};

if (Fun && typeof Fun === "function") {

o.__proto__ = Fun.prototype;

Fun.apply(o, arguments);

return o;

}

}


这主要是new函数操作的模拟,可以看到Fun.apply(o,arguments);这一步,apply的作用是指定执行Fun函数并制定其上下文为o,输入参数为arguments对象。所以new Test()得到的结果是Object。

闭包

表示能访问和操作其他内部作用域的变量的表达式(通常是函数)。用简单的语言描述就是:能操作其他本地作用域变量的函数就是闭包。还是看代码:

代码如下 复制代码

function fun() {

var i = 1;

return function() {

i++;

console.log(i);

}

}

var closure = fun();

closure(); //2

closure(); //3

请先按Java语言或其他非闭包语言的逻辑思考。当fun函数执行完毕时,其内部变量i应该会被释放,当closure函数再此执行并调用i时输出错误(因为i未被声明)。但是在Javascript中却打印出了值,这一切都归功于闭包。在执行fun()函数时,闭包就被创建。此时i并没有被释放,closure依然可以被访问到。

另外注意闭包的本质是表达式(通常是函数),所以闭包是在函数生成时定义的。(上面代码的第3行定义闭包)!

闭包的作用

闭包最大的作用就是来阻止外部程序随意访问内部变量,只能通过提供的接口来访问和操作。如下面一个记数器的实现:

代码如下 复制代码

var counter = (function() {

var i=0;

return {

add:function() {

i++;

return this;

},

get:function() {

return i;

}

}

})();

counter.add().add().add();

console.log(counter.get()); //3


如果不用闭包直接暴露变量,那就会出现如下的情况产生bug:

代码如下 复制代码

i=0;

function add() {

i++;

}

add();

add();

i+="1";

add();

console.log(i);//22

至于为什么产生这么奇怪的结果就要归功与+=和++这两个操作符了。


举一个简单的例子:

代码如下 复制代码

function test(){


alert(this);

}

var obj=function(){

var name='testObj';

}

obj.objTest=test;

test();

obj.objTest();


把这段代码放到HTML中运行这个页面,你会看到首先提示一个警告[object window],然后第二个警告

代码如下 复制代码

var obj=function(){


var name='testObj';

}


我们先定义了一个test()方法,并在方法内部调用alert()方法将this显示出来,然后定义了一个obj函数对象,并给它加了一个私有的字段name,同时给它加了一个静态的方法objTest(),而这个函数则直接指向test()函数。

分别调用test()和obj.objTest()方法,第一次警告框提示的是Window对象,而第二次提示的是我们定义的obj这个函数的代码。这说明了test函数在两次执行的时候this的值是不同的!

这就说明了当调用函数的对象不同的时候,其内部的this关键字指代的对象是不同的。这里需要值得注意的是Javascript是基于对象的语言,当我们的变量或者函数定义在标签的根下的时候其实相当于给window对象加了相应的属性或方法,所以当我们利用function test(){}代码定义一个函数的时候,其实相当于给window对象添加了一个新的函数,即window.test()函数。
我们可以做一个实验:

代码如下 复制代码

function test(){


alert(this);

}

alert(test===window.test);

警告框提示的将是true,这说明当我们在调用test()这个函数时相当于调用的是window.test()。所以当我们调用test()函数的时候调用这个函数的对象其实是window对象,this指代的是window对象,所以我们在alert(this)的时候弹出的警告窗口内容是[object Window]。我们将obj.objTest=test相当于把obj.objTest()指向test(),所以当我们调用obj.objTest()函数时相当于在obj调用了test()这个函数,所以现在this指代的是obj对象,提示的就是obj这个Function也就是我们看到的代码。

说到这应该也解释的差不多了,可能上面的例子太抽象,想象不出来它能在什么情况下用到,那我们现在就假设一个需求,做一个贴近实用一点的例子。
假设我们现在页面中的所有超链接在点击之后颜色要改为红色,用Javascript实现。大体的思路应该是获取页面中所有的标签,然后遍历所有的标签,给每一个注册一个click事件,事件触发后我们将它的color值设为red。
示例代码如下:

代码如下 复制代码

function changeColor(){

this.style.color='#f00';

}

//初始化,给所有 a 标签注册事件

function init(){

var customLinks=document.getElementsByTagName('a');

for(i in customLinks){

//你也可以使用事件侦听器方式来注册事件

//由于要兼容IE,FF等浏览器可能需要更多代码,您可以自行编写

customLinks[i].onclick=changeColor;

}

}

window.onload=init;


将这段代码添加到HTML文档中,并在文档中添加一些超链接,当超链接点击后颜色会变成红色,这里我们定义的changeColor()函数中this关键字在点击超链接触发函数的时候它指代的是当前这个超链接。而如果你直接调用changeColor()函数浏览器会报错,提示Error: ‘this.style’ is null or not an object或者undefined之类的错误。

不知道说到这能不能让正在看文章的你对Javascript中的this关键字有了一些自己的了解呢?或者你已经不耐烦了?(:P)

其实要想真正对这个问题有更深入的理解那么必须对Javascript的作用域和作用域链有深入的理解。

作用域,顾名思义就是指某一属性或方法具有访问权限的代码空间,简单的说也就是这个变量或方法它在代码中的的适用范围。在大多数的OOP中主要有public,private,protect三种作用域,对着三种作用域在这里就不详细解释了,如果有OOP的经验应该都有深入的了解。在这里我要说的是这三种作用域类型对Javascript来说几乎是毫无意义的,因为Javascript中只有一种公共作用域,在Javascript中作用域是在函数中进行维护的。举个例子:

代码如下 复制代码


var test1='globle variable';


function example(){

var test2='example variable';

alert(test1);

alert(test2);

}

example();

alert(test1);

alert(test2);

根据我们前面解释的,这里的test1变量相当于window的一个属性,所以它会在整个window作用域内起作用,而test2则在example()函数的内部声明,所以它的作用域也就维持在example()方法的内部,如果在函数的外部调用test2浏览器会提示出错。而在example()内部调用test1则没问题。
根据这个我们再举一个例子:

代码如下 复制代码


var test='globle variable';

function example(){

var test='example variable';

}

example();

alert(test);

这个例子运行会是什么结果呢?对,警告框会提示“globle variable”,因为example()函数内部的test变量其作用域只维持在内部,不会影响外部的test变量。如果我们将example()内部test变量的var关键字去掉呢?你可以自己试试。

说到这就有牵扯出另外一个概念,那就是作用域链的概念。作用域链就是可以确定变量值的路径。由上面一个例子可以看出,var关键字是用来维护作用域链的,如果变量使用了var关键字声明那么他就可以看作为作用域链的终点。同样函数的形参的定义也会起到类似的作用。

说到这你对this这个精灵古怪的家伙有了比较清晰的认识了吧?根据它简单的一个诠释,this总是指向调用它所在函数的对象,根据作用域和作用域链,我们会很清晰的确定this的真面目。临末尾再来一个开始那个例子的简单变化:

代码如下 复制代码

function test(){


alert(this);

}

var obj=function(){

var name='testObj';

}

obj.objTest=test;

obj.objTest2=function(){

test();

}

test();

obj.objTest();

obj.objTest2();


你猜会提示什么内容呢?你可以运行一下试试(:P);

既然this是根据调用其所在函数的对象的改变而改变的,那我们可不可以强制改变它的调用对象呢?答案是肯定的,以后的文章会介绍一下这部分内容,以及Javascript中不同类型的数据成员的实现方式,闭包等概念。

相关文章

精彩推荐