1.关于声明提前
- js中,允许变量使用在声明之前,不过此时为undefined
console.log(a); // undefined
var a = 1;
- 变量不管在哪里声明,都会在任意代码执行前处理。在es5 strict mode,赋值给未声明的变量将报错。
- 显式声明:带有关键字 var 的声明,作用域就是当前执行上下文,即某个函数,或者全局作用域(声明在函数外,即变量会挂在在window对象上)
- 隐式声明:如果一个变量没有使用var声明,window便拥有了该属性,因此这个变量的作用域不属于某一个函数体,而是window对象。
function varscope(){
foo = "I'm in function"; //直接赋值 没有声明
console.log(foo);//I'm in function
}
varscope();
console.log(window.foo); //I'm in function
- 关于声明提前的例子
function testOrder(arg) {
console.log(arg); // arg是形参,不会被重新定义
console.log(a); // 因为函数声明比变量声明优先级高,所以这里a是函数
var arg = 'hello'; // var arg;变量声明被忽略, arg = 'hello'被执行
var a = 10; // var a;被忽视; a = 10被执行,a变成number
function a() {
console.log('fun');
} // 被提升到作用域顶部
console.log(a); // 输出10
console.log(arg); // 输出hello
};
testOrder('hi');
/* 输出:
hi
function a() {
console.log('fun');
}
10
hello
*/
2.关于作用域
-
函数作用域
函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域
function bar() { var testValue = 'inner'; } console.log(testValue); // 报错:ReferenceError: testValue is not defined
通过 return 访问函数内部变量:
function bar(value) { var testValue = 'inner'; return testValue + value; } console.log(bar('fun'));// "innerfun"
通过 闭包 访问函数内部变量:
function bar(value) { var testValue = 'inner'; var rusult = testValue + value; function innser() { return rusult; }; return innser(); } console.log(bar('fun')); // "innerfun"
-
立即执行函数作用域
这是个很实用的函数,很多库都用它分离全局作用域,形成一个单独的函数作用域;它能够自动执行
(function() { //... })()
里面包裹的内容,能够很好地消除全局变量的影响;<script type="text/javascript"> (function() { var testValue = 123; var testFunc = function () { console.log('just test'); }; })(); console.log(window.testValue); // undefined console.log(window.testFunc); // undefined </script>
-
块级作用域
在 ES6 之前,是没有块级作用域的概念的。
for(var i = 0; i < 5; i++) { // ... } console.log(i) // 5
很明显,用 var 关键字声明的变量,在 for 循环之后仍然被保存这个作用域里;
这可以说明: for() { }仍然在,全局作用域里,并没有产生像函数作用域一样的封闭效果;
如果想要实现 块级作用域 那么我们需要用 let 关键字声明
for(let i = 0; i < 5; i++) { // ... } console.log(i) // 报错:ReferenceError: i is not defined
在 for 循环执行完毕之后 i 变量就被释放了,它已经消失了!!!
同样能形成块级作用域的还有 const 关键字:
if (true) { const a = 'inner'; } console.log(a); // 报错:ReferenceError: a is not defined
let 和 const 关键字,创建块级作用域的条件是必须有一个 { } 包裹:
-
词法作用域
当我们要使用声明的变量时:JS引擎总会从最近的一个域,向外层域查找
testValue = 'outer'; function afun() { var testValue = 'middle'; console.log(testValue);// "middle" function innerFun() { var testValue = 'inner'; console.log(testValue);// "inner" } return innerFun(); } afun(); console.log(testValue); // "outer"
当 JS 引擎查找变量时,发现全局的 testValue 离得更近一些,则取全局的testValue的值即 outer
var testValue = 'outer'; function foo() { console.log(testValue);// "outer" } function bar() { var testValue = 'inner'; foo(); } bar();
-
动态作用域
动态作用域,作用域是基于调用栈的,而不是代码中的作用域嵌套;
作用域嵌套,有词法作用域一样的特性,查找变量时,总是寻找最近的作用域;
3.关于this
关键字
在一个函数中,this总是指向当前函数的所有者对象,this总是在运行时才能确定其具体的指向, 也才能知道它的调用对象。
window.name = "window";
function f(){
console.log(this.name);
}
f();//window
var obj = {name:'obj'};
f.call(obj); //obj
在执行f()时,此时f()的调用者是window对象,因此输出”window”
f.call(obj) 是把f()放在obj对象上执行,相当于obj.f(),此时f中的this就是obj,所以输出的是”obj”
对比以下两段代码:
var foo = "window";
var obj = {
foo : "obj",
getFoo : function(){
return function(){
return this.foo;
};
}
};
var f = obj.getFoo();
f(); //输出'window'
/* 分析
执行var f = obj.getFoo()返回的是一个匿名函数,相当于:
var f = function(){
return this.foo;
}
f() 相当于window.f(), 因此f中的this指向的是window对象,this.foo相当于window.foo, 所以f()返回"window"
*/
var foo = "window";
var obj = {
foo : "obj",
getFoo : function(){
var that = this;
return function(){
return that.foo;
};
}
};
var f = obj.getFoo();
f(); //输出'obj'
/* 分析
执行var f = obj.getFoo() 同样返回匿名函数,即:
var f = function(){
return that.foo;
}
唯一不同的是f中的this变成了that, 要知道that是哪个对象之前,先确定f的作用域链:f->getFoo->window 并在该链条上查找that,此时可以发现that指代的是getFoo中的this, getFoo中的this指向其运行时的调用者,从var f = obj.getFoo() 可知此时this指向的是obj对象,因此that.foo 就相当于obj.foo,所以f()返回"obj"
*/
4.关于箭头函数
箭头函数有两种格式:
var fn = x => x * x; //只包含一个表达式,连{ ... }和return都省略掉了
x => { //还有一种可以包含多条语句,这时候就不能省略{ ... }和return:
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。
对比以下两个例子
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth;
};
return fn();
}
};
//箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 29
//由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略:
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);
}
};
obj.getAge(2015); // 25
箭头函数与this结合例子
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1() //person1
person1.show1.call(person2) //person2
person1.show2() //window
person1.show2.call(person2) //window
person1.show3()() //window
/*person1.show3是一个高阶函数,它返回了一个函数,分步走的话,应该是这样:
var func = person3.show()
func()
从而导致最终调用函数的执行环境是window,但并不是window对象调用了它。所以说,this总是指向调用该函数的对象,这句话还得补充一句:在全局函数中,this等于window。
*/
person1.show3().call(person2)//person2 通过person2调用了最终的打印方法
person1.show3.call(person2)()//window 先通过person2调用了person1的高阶函数,然后再在全局环境中执行了该打印方法。
person1.show4()() //person1 箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
person1.show4().call(person2) //person1 箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象,用person2去调用这个箭头函数,它指向的还是person1。
person1.show4.call(person2)() //person2 箭头函数的this指向的是谁调用箭头函数的外层function,箭头函数的this就是指向该对象,如果箭头函数没有外层函数,则指向window
例题:
var number = 5;
var obj = {
number: 3,
fn1: (function () {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
输出10 9 3 27 20