var、let、const
1. var变量提升:
(function shiftup(){
if(false){
var show1='a'
}
console.log(show1)
console.log(show)
})()
在这个函数中,正确的执行应该是这两条打印都会未定义的错误,但是现实是只有第二条打印报错,第一条打印出来是undefined,undefined意味着这个函数已经被定义但是没有被赋值。
这是因为var有变量提升,这段代码实际上等于
(function shiftup(){
var show1//变量提升
if(false){
show1='a'
}
console.log(show1)
console.log(show)
})()
2. let&const暂时性死区TDS
function func1(){
console.log(a)
// 这个往上的空间是暂时性死区,是读不到a变量的
let a='hello'
}
func1()
这里的a就会报错,这样的方式更好
在函数参数中也有这样的情况
function func2(a=b,b=3){}
func2()
编译器会报错:ReferenceError: Cannot access 'b' before initialization
这是因为对于b来说,a=b被调用是,b还没有被赋值,是在TDS中的
当改成:
function func3(a=3,b=a){}
func3()
就不会报错,此时的b=3
3. let、var、const的共同点
在全局定义都是可以被访问的
// const web='hello qiaomu'
// let web='hello qiaomu'
var web='hello qiaomu'
function show(){
console.log(web)
}
show()
如果在函数内有一个web变量时,情况就会发生改变
var web='hello qiaomu'
function show(){
var web='hello mumu'
console.log(web)//hello mumu
}
show()
console.log(web)//hello qiaomu
当内部不声明变量时,改的变量是全局的web变量
var web='hello qiaomu'
function show(){
web='hello mumu'
console.log(web)//hello mumu
}
show()
console.log(web)//hello mumu
练习:
const web='hello qiaomu'
function show(){
const web='mumu'
function run(){
var web='run mumu'
console.log(web)//run mumu
}
run()
console.log(web)//mumu
}
show();
console.log(web)//hello qiaomu
(要放在浏览器运行,在code run中会报错)
函数是一个私有领地,只有当私有领地中没有才会去全局找
4. 不同点1:块级作用域
var是没有块级作用域概念的
情况1:
var i=99
console.log(`全局${i}`)
for(var i=0;i<5;i++){
console.log(`局部${i}`)
}
console.log(`全局${i}`)
在上面的代码中,最后一个全局的i的值打印为5,也就是说for循环把全局i的值给改了,var是没有块级作用域的,这不是我们希望看到的结果,把for循环中的i用let声明就可以解决这个问题
情况2
在1.js中
var name='mumu'
在2.js中
var name='qiaomu'
先引入1.js,再引入2.js,当打印这个name时显示的是qiaomu,如果调换顺序,打印就被改成了mumu,这是因为这两个js是在一个作用域里面
方法1:立即执行函数
在原来要解决这个问题一般是用一个自执行函数
(function(){
var $=(window.$={})
$.web='qiaomu'
var url='qiaomu.com'
$.getUrl=function(){
return url;
}
}.bind(window)());
这里的变量都是定义在这个函数里面,通过var = ( w i n d o w . =(window. =(window.={})把变量开放给外部接口。在外部如果直接读url是读不了的,在外面***使用$.getUrl()***才能读到这个url的值。立即执行函数是控制作用域,防止全局污染。
方法2:使用{}
{
let $=(window.$={})
$.web='qiaomu'
let url='qiaomu.com'
$.getUrl=function(){
return url;
}
}
在外部访问时也是一样使用***使用$.getUrl()***才可以访问到url,要注意的是这里不可以使用var,因为使用var是没有块级作用域的
5. const详解
const variate1='qiaomu'
let variate2='mumu'
variate1='mumu'//报错
variate2='qiaomu'//不报错
const是不可以修改的(在同一个作用域中不可改),const实际上是对内存地址的引用
所以当const的值是个引用类型,该引用的数据是可以的,例如:
const CONFIG={}
CONFIG.url='qiaomumu 好可爱!'
console.log(CONFIG.url)//qiaomumu 好可爱!
因为声明一个变量的时候本质上是把一个地址赋值给这个变量,所以当这个值为一个基本类型时,这个变量中存储的地址是直接指向这个数字的,const声明的变量不可以改变内存地址,也就不可以改变这个值。但是如果这个变量是一个引用类型,引用类型本来就是一个地址,就相当于const声明的变量中保存一个地址,这个地址指向另一个地址,这个另一个地址才是真正的值,所以当改变这个另一个地址中的值的时候相当于改变这个const变量中的值。
Object freeze冻结变量
有的时候我不希望我的引用类型被改变,显然这时候用const是不可以的,因为const并不能控制自己存的地址的地址的变量不被改变,所以这时候使用Object.freeze(variate)
来使引用类型的变量的值不可以被改变
//"use strict"如果使用严格模式,HOST.port='3003'这句会报错
const HOST={
url:'www.qiaomu.com',
port:'8080'
}
Object.freeze(HOST)// 冻结变量
HOST.port='3003'
console.log(HOST)
6. 不同点2:var导致的window全局对象污染
var会把变量放到windows中,但是let不会
var m_name1='qiaomu_var'
console.log(window.m_name1)//qiaomu_var
let m_name2='mumu_let'
console.log(window.m_name2)//undefined
把变量放到window全局对象中是非常不合理的,因为当定义的变量与原本window的某些属性重名时,会导致dom或bom的操作错误。let可以避免这些问题。
7. 不同点3:var重复声明
const和let在同一作用域中重复声明是不可以的,会报错,但是如果是var是可以的
const web='hello qiaomu'
function show2(){
const web='mumu'
function run(){
let web='run mumu'
console.log(web)//run mumu
}
run()
console.log(web)//mumu
}
show2()
这里作用域不同所以声明没有关系
const a='qiaomu'
let a='mumu'
但是如果代码编程了这样就会报错:Identifier 'a' has already been declared
8. 标量与引用类型的传值与传址
对于基本类型来说,赋值是传值
let a=1;
let b=a;
console.log(a,b);//1 1
b=3;
console.log(a,b);//1 3
从上面的代码可以看出,b=a是把值给了b,因为如果是a把地址给了b,那么当b改变值时,a的值也会变,但是事实是a的值没有变,只是b的值变了,也就是说程序重新开辟了一块内存存储3,并把这个地址赋值给了b
但是对于引用类型不是这样,因为引用类型通常值很大,占用内存多,所以在赋值是会传址
let c={name:'mumu'};
let e=c;
console.log(e,c);//{ name: 'mumu' } { name: 'mumu' }
e.name='qiaomu';
console.log(e,c)//{ name: 'qiaomu' } { name: 'qiaomu' }
就像上面的打印结果是改了e.name
后c.name
也被改变了。
9、null和undefined
-
引用类型初值是null,基本类型初值是undefine
-
函数没有返回值时,默认返回undefined
-
函数没有传参是,默认值是undefined
-
声明了没有赋值的变量类型是undefined