一.作用域
作用域是结构化编程语言中的重要概念,它决定了变量的可变范围和生命周期。
和C、Java等语言不同的是,JavaScript不是以{}作为作用域的范围。
例如:
if(true){
var message = 'hello world';
}
console.log(message);//输出hello world
message的声明和赋值都在if的花括号内,但在if{}外却可以使用。
这是因为JavaScript的作用域完全是由函数来决定的,if、for语句中的花括号不是独立的作用域。
1.1 函数作用域
我们先来看一个例子:
var v1 = 'v1';
var f1 = function(){
console.log(v1);//输出v1
};
f1();
var f2 = function(){
var v1 = 'v11';
console.log(v1);//输出v11
};
f2();
两个函数都使用了v1变量,函数f1在它的函数作用域找不到v1,就搜索上层的作用域,找到v1,输出v1。函数f2在它的函数作用域找到了v1,故就屏蔽了全局作用域中的v1,输出v11。
再来看一个例子:
var v = 'v';
var f = function(){
console.log(v);//输出undefined
var v = 'v1';
};
f();
这个例子有点出人意料,因为既没有输出v,也没有输出v1,怎么会出现undefined呢?
这是因为在函数f()中,执行console.log(v)前会先搜索函数作用域中有没有变量v,找到了就屏蔽了全局变量v了,但是此时局部变量v还没被定义或者说初始化,所以输出了undefined。
再看一个例子:
var v = 'v';
var f1 = function(){
console.log(v);
};
f1();//输出v
var f2 = function(){
var v = 'v1';
console.log(v);
};
f2();//输出v
为什么f2()还是输出v呢?这是因为作用域的嵌套关系不是在调用时确定的,而是在定义时确定的。
1.2 全局作用域
满足一下条件的变量属于全局作用域:
(1)在最外层定义的变量
(2)全局对象的属性
(3)任何地方隐式定义的变量(为定义直接赋值的变量)
二.闭包
闭包:由函数(环境)及其封闭的自由变量组成的集合体。
看一个例子:
var generateClosure = function(){
var count = 0;
var get = function(){
count++;
return count;
}
return get;
}
var counter1 = generateClosure();
var counter2 = generateClosure();
console.log(counter1());//输出1
console.log(counter2());//输出1
console.log(counter1());//输出2
console.log(counter1());//输出3
console.log(counter2());//输出2
有点奇怪,count的作用域在generateClosure函数里面,当generateClosure从调用栈返回时count申请的空间就应该被释放,但是为什么没有呢?
这正是所谓闭包的特性。当函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境。
闭包的用途主要又两个:
(1)实现嵌套的回调函数。
(2)实现私有成员。
三.对象
3.1两种创建js对象的方法
(1)通过字面量来创建,即 var obj={};
再通过obj.attr/obj[attr]/attr:进行赋值和取值。
(2)使用 var obj = new Object()显示地创建一个对象。
eg:
var foo = {
'prop1':'bar',
'prop2':'false',
prop3:function(){
return 'hello world';
}
}
3.2 构造函数
如果想像Java和c++语言一样定义对象,怎么实现呢?那就使用js的构造方法。
function User(name,uri){
this.name = name;
this.uri = uri;
this.display = function(){
console.log(this.name);
}
}
eg:使用new语句来创建对象。
var someuser = new User('js','https://www.csdn.net');
然后就可以通过someuser来访问这个对象的属性和方法。
3.3 上下文对象
在这里介绍两个适应不同的对象作为上下文来调用函数的方法。
(1)call:func.call(thisArg[,arg1][,arg2][,…])
(2)apply:func.apply(thisArg[,argsArray])
其中,func是函数的引用,thisArg是被调用得分上下文对象,arg1,arg2,argsArray是传入func的参数。
这两个函数的功能是一致的,两者的差别在于接受参数的不同,call以参数表来接受被调用函数的参数,而apply是以数组形式。
举个例子:
var someuser = {
name:'abc',
display:function(words){
console.log(this.name + ' says ' + words);
}
}
var anotheruser = {
name:'efg'
}
someuser.display.call(anotheruser,'yyds');
这里会输出:efg says yyds
someuser.display是实际调用的函数,它通过call将上下文改变为anotheruser对象,因此在函数体内访问this.name时实际访问的是anotheruser.name,因此输出了efg。