1. var
声明 vs
直接声明
var number = 999; // var 声明
number = 999; // 直接声明,严格模式下,会抛出异常
var
方法声明的变量符合链式作用域- 直接声明的变量,也就是不加
var
关键字声明的便令会被直接添加到global
(浏览器中即window
)对象中
2. 变量的链式作用域(Chain Scope
)
链式作用域:子对象会一级一级的向上寻找所有父对象的变量。
也就是说,父对象的所有变量对子对象可见,但是子对象的变量对父对象不可见。这是一个非常通俗的解释,详细的内存管理方式需要参加这篇文章:blog.csdn.net/whd526/arti…
Demo
function parent() {
var number1 = 999;
function child() {
console.log(number1); // 999
var number2 = 1000;
}
console.log(number2) // error, cannot access variable declared in child object.
}
3. javascript
的{}
作用域
javascript
一个非常重要的特点是,javascipt
在设计之初是没有{}
作用域的。简单来说,在一个函数内部,如果你在一个if
分支中用var
关键字声明了变量,那么,在if
分支外部,这个变量依然可以访问。
var condition = true;
if(condition) {
var varNumber = 999;
}
console.log(varNumber); // 999
上面代码中,varNumber
虽然是在if(condition)
中定义的,但是在if
分支外部,依然可以访问,这是因为javascript
中没有{}
作用域。
4. let
关键字的引入
上面一点提到过,javascript
在设计之初是没有{}
作用域的,之所以强调在设计之初,是因为,C/C++/C#/java之类的语言是存在{}
作用域的,而且愈来愈多的这类程序员使用javascript
开发,因此,为了方便大家使用,在es6
中引入了let
关键字。
let
关键字支持了{}
作用域,我们可以看一下一下的demo
。
// es6
let number = 999;
if(number > 0) {
let number = 1000;
console.log(number); // 1000
}
console.log(number); // 999
child();
// 编译为es5后的代码
var number = 999;
if (number > 0) {
var _number = 1000;
console.log(_number); // 1000
}
console.log(number); // 999
之所以能够将let
声明的变量限制在{}
作用域中,是因为es6->es5
之后,声明了一个新的变量。
5. 闭包(closure
)
个人理解闭包有两个方面的作用,还有一点需要注意的方面。
闭包:由于在Javascript
语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成定义在一个函数内部的函数
。
function closureDemo() {
var n = 999;
function display() {
console.log(n);
}
function increase() {
n++;
}
return { display, increase };
}
var result = closureDemo();
result.display(); // 999
result.increase(); // n++
result.display(); // 1000
- 闭包的两点作用
- 读取其他函数定义的变量
- 读取的其他函数内部定义的变量的值一直驻留在内存中
其实这点非常重要,第一点可以通过函数返回值也可以做到,但是有了第二点,就不一样了,因为在使用闭包时,返回的函数一直处于被引用状态,所以不会被垃圾回收器回收,因此,会一直得到其内部变量在内存中的变化值。
向上面示例代码中的例子,变量
n
一直驻留在内存中,因为result
对象,一直处于引用状态,所以,可以看到n
从999
变到了1000
。
- 闭包要注意的问题
但是也是因为第二点,闭包返回的函数以及闭包内声明的变量一直驻留在内存中,所以闭包不能被滥用,否则会导致内存溢出,闭包的使用应该谨慎。
Demo
// closures.js
function closureVariableTest1() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
return result;
}
function closureVariableTest2() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = (function () {
var num = i;
return num;
})(i)
}
return result;
}
var result1 = closureVariableTest1();
var result2 = closureVariableTest2();
for (var i = 0; i < result1.length; i++) {
console.log(result1[i]()); // 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
/**
* Since there is no brace scope for var, so, result1's element will store the function,
* and when the function executed, the i is always 10.
*/
}
for (var i = 0; i < result2.length; i++) {
console.log(result2[i]); // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
/**
* Every element store the execute result of the function, so it is expected.
*/
}
上面的例子中,
closureVariableTest1()
中,所有的element
都显示10
,因为数组中保存的是一个function
,当这个function
被调用的时候,i
已经变化为10
了,所以,数组的每个元素被调用的时候,显示的都是10
。
closureVariableTest2()
中,保存元素时,已经将i
这个数字赋值给数组的每个元素,因此,无论何时调用数组元素,都会输出正确的数组下标。
6. this
指针
了解了闭包后,this
指针的指向问题就很好理解了。我理解,简单来说就是两点:
-
那个对象调用了函数,函数内部的
this
对象就指向哪个对象 -
在严格模式下,函数内调用
this
,将为undefined
'use strict'; function foo() { try { console.log(this);// undefined } catch (err) { console.log(err); } } foo();
-
在非严格模式下,匿名函数中的
this
指针指向window
对象function useThisInsideObject() { name = 'The Window'; var object = { name: 'My Object', getNameFunc: function () { return function () { return this.name; } } }; console.log(object.getNameFunc()()); /** * `The Window`, since object.getNameFunc()()将调用getnameFunc中的匿名函数. * 匿名函数中的this指针将会指向window对象,window.name = "The Window". */ } function keepTheContextOfObject() { name = 'The Window'; var object = { name: 'My Object', getNameFunc: function () { var self = this; return function () { return self.name; } } }; console.log(object.getNameFunc()()); /** * `My Object`因为在调用匿名函数前,已经使用self记录了this对象的状态,所以self.name的值为`My Object`. */ } useThisInsideObject(); keepTheContextOfObject();
7. Call
, Apply
& Bind
通过Call
,Apply
与Bind
方法可以改变this
指针的指向。他们之间的区别在于:
方法名 | 参数接收 | 返回值 |
---|---|---|
call | 可变数组 e.g. Call (object , arg1 , arg2 , arg3 , ...) Call (object , [arg1 , arg2 , arg3 , ...]) 都是正确的 | 返回结果值 |
apply | 数组 e.g. Call (object , arg1 , arg2 , arg3 , ...) // 错误 Call (object , [arg1 , arg2 , arg3 , ...]) //正确 | 返回结果值 |
bind | 参数列表 e.g. //正确 bind (object , arg1 , arg2 , arg3 , ...) // 不会错误,但是会把数组当做对象处理,不会当做可变数组 bind (object , [arg1 , arg2 , arg3 , ...]) | 返回binding 了新对象的函数 |
// callBindApply.js
var user = {
user_name: "yafeya",
speak: function () {
console.log(this.user_name);
},
hello: function(word){
console.log(`${this.user_name} said ${word}`);
}
}
user.speak(); // yafeya
var speak = user.speak;
var hello = user.hello;
speak(); // undefined, 因为user.speak()被赋值给了speak对象,这里的speak对象是一个匿名函数,所以其中的this指向window对象。
console.log('call & apply');
speak.call(user); // yafeya, call方法将this指针指向user对象.
speak.apply(user); // yafeya, apply方法将this指针指向user对象.
console.log('call & apply with parameters');
hello.call(user, 'hello'); // call方法可以接受可变数组
hello.apply(user, ['hello']); // apply方法只能接受数组,如果填写可变数组,将会抛出异常.
console.log('bind demo')
hello.bind(user, 'hello')(); // yafeya, bind方法接收可变数组,返回binding了新对象的函数.