变量提升
定义:变量提升是当栈内存作用域形成时,JS代码执行前,浏览器会将带有var, function
关键字的变量提前进行声明 declare(值默认就是 undefined),定义 defined(就是赋值操作),这种预先处理的机制就叫做变量提升机制也叫预定义。
词法作用域,不会被提升,只会在定义以后才有;同一作用域禁止重复声明。
例: var a=1; let a=1; 报语法错误。
const定义常量,在使用时必须初始化值,常量也不会被提升到做作用域顶部。const和let都是块级标识符。const 是常量禁止再赋值。当const声明对象时,可以修改对象属性值。
例: const person = { name:' lisi' }; person.name='wangwu';(正确) person={name:'wangwu'} (报错)
临时死区(TDZ):在let或const的作用域,提前引用变量会报错,不在同一作用域不会;
例: console.log(typeof value) ; let value = 'dasd' //引用错误
console.log(typeof value) //undefined
if(true){let value = 'asd'} //不会报错
注意项:var 提升机制,在作用域内部,无论在哪定义,都会提升到顶部,在条件判断语句里,就算判断不成功,解析时也会在作用域头部初始个undefined的变量,如:var value;所以变量无论在哪都是被定义的 ;常见问题,用for循环输出0-9,如果用var会输出10个10,因为变量的引用一直是头部定义的 i, 用let则不会出现。const常量不允许变化,但for in 和for of则不会报错,因为每次迭代会创建一个新的绑定。
全局作用域的绑定:
let 和const与var的另一个区别是他们在全局作用域中的行为。当var被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器环境中的window对象)的属性,这意味着var很可能会无意中覆盖一个已经存在的全局属性。而let和const 在全局作用域下使用时,会创建一个新的绑定,但该绑定不会添加为全局属性对象,换句话说,let或const不能覆盖全局变量,而只能遮蔽它, 例如
window下变量a等于‘ hello’'; window.a => hello ; let a = '呵呵'; a=>'呵呵'; window.a=>'hello';
当用frame或跨window访问代码时仍然可以使用var来方便访问
当前使用块级绑定最佳实践是:默认使用const,只在确认需要改变变量的值的时候使用let,这样就可以在某种程度上实践代码的不可变,从而防止某些错误的产生。
下面看些涉及到变量提升的题:
if( !("a" in window) ){
var a = '加载完成'
}
console.log(a) // undefined
因为判断语句不管执行不执行,内部得var变量都会提升,所以window.a已经是undefined了,判断语句变成false不执行,输出undefined。
再来:
console.log(a)
var a = '我执行'
function fn(){
console.log(a)
var a = '我改变'
}
fn()
console.log(a)
输出: undefined, undfined, 我执行,因为函数内部得var a,并不是对全局a变量的赋值操作,而是在函数内部又定义了一个a变量,此时变量提升到函数顶部,是undefined。外部的不受局域变量影响,还是输出我执行。下边再来:
console.log(a)
a = '林一一'
function fn(){
console.log(a)
a = 12
}
fn()
console.log(a)
是不是有点懵? 这段代码会报错,a是undefined。同时思考情况,我前面文章介绍的 function fn(){}。对比一下全局函数提前使用就不会报错,因为它执行了, 可以把function关键词理解为var定义提升。且函数已经定义,fn不是undefined.
再看:
var a=12, b = 13, c = 14
function fn(a){
a = 0
var b = 0
c = 0
}
fn(a)
console.log(a)
console.log(b)
console.log(c)
输出:12,13,0。 知识点: 形参不会改变外部变量值。
搞懂这些基本变量提升就毕业了。
闭包
说闭包前,先说一下作用域链:作用域链就是函数当前作用域内的用到的变量会按照当前作用域向上一层层查找,直到顶层作用域(这不是啥官方语言,我自己理解的,如果不对,欢迎同学指出。)
闭包是指有权访问另一个函数作用域变量的函数,创建闭包的通常方式,是在一个函数内部创建另一个函数;由于作用域链的结构,外围函数是无法访问内部变量的,为了能够访问内部变量,我们就可以使用闭包,闭包的本质还是函数;
闭包的执行环境会被销毁,但活动变量不会销毁,所以不要滥用闭包。
Tip: 在js中,没有块级作用域 ,只有函数作用域。可以采用“立即执行函数Immediately-Invoked Function Expression (IIFE)”的方式创建作用域。
理解概念了,下边看题:
function fn(i){
return function(n){
console.log(n+ (i++))
}
}
var f = fn(10)
f(20) // 30
fn(20)(40) // 60
fn(30)(40) // 70
f(30) // 41
顺题梳理,当定义f时,fn传入参数10,此时已经形成闭包,i值10是fn返回函数内可访问到的一个变量值,但它对外界是不可访问的。
f(20)传入,此时n为20,i为10,输出30 .(i++和++i的区别就是i++后执行加法运算,++i先执行加法运算)
fn(20)(40), fn(30)(40)都是新调用的fn函数,彼此互不关联,所以输出,60,70.
f(30) 因为f还保存着fn返回的函数,闭包内的变量10还在。此时10已经在++运算后变成了11,所以返回41。
this指向
从朋友处得来一张图,具体作者是谁不知道了,如有冒犯,立即删除。
基本上这张图就将this的指向囊括了。
那么直接看题:
var name = '王大拿'
var obj = {
name: '王晓拿',
prop: {
getName: function(){
return this.name
}
}
}
console.log(obj.prop.getName())
var a = obj.prop.getName
console.log(a())
/*
* undefined
* 王大拿
/
解释: obj.prop.getName()调用时,this指向的是obj.prop,然而prop下没有name属性,所以输出undefined。
那么为什么 定义a = obj.prop.getName 又会输出王大拿呢? 调用a的时候当前执行环境已经变为全局了,this指向的是window,所以会是王大拿。
var num = 10 // 60; 65
var obj = {
num: 20
}
obj.fn = (function (num){
this.num = num * 3
num++ // 21
return function(n){
this.num += n // 60 + 5 = 65;20 + 10 =30
num++ // 21 + 1 = 22;22 + 1 = 23
console.log(num)
}
})(obj.num)
var fn = obj.fn
fn(5) // 22
obj.fn(10) // 23
console.log(num, obj.num) // 65, 30
解释: 因为obj.fn是立即执行函数,所以函数里this.num = num*3, this指向window, 全局作用域下得num被赋值了。全局作用域下得num变成了60.闭包内得num形参变成了21,所以console.log 输出21,fn(5) ,函数的this,还是指向window, num从60变65,闭包内的num变为22,输出22。 obj.fn(10) 此时函数内的this.num+=n 这个this,已经不是指向window了,它指向的是obj,而obj的num是20,闭包内的一开始形参那个变量没变,继续执行加法,输出23,全局num是65,obj.num是30。
that's all,thanks。。