复习路线:https://ot7ehe3n3i.feishu.cn/mindnotes/bmncnCJmBALBBsRoMWcZ0KvXeme#mindmap
| 区别 | let | const | var |
|---|---|---|---|
| 重复声明 | 不能重复声明,会报SyntaxError错 | const 定义常量,值不能修改的变量叫做常量,一定要赋初始值,因为不能修改。 | 可以重复声明 |
| 变量提升 | 不存在 | 不存在 | 存在 |
| 块级作用域 | 拥有 | 拥有 | 不拥有 |
| 会不会污染全局变量(挂载在window上) | 不会 | 不会 | 会 |
| 补充 | 如果常量是个数组或对象,对其内部元素修改,不算对常量的修改,不会报错。因为常量的值报错的是地址,地址并没有改变 |
变量声明升级
通过var定义(声明)的变量,在定义语句之前的就可以访问到
但是值是undefined
函数声明提升
通过function声明的函数,在之前就可以直接调用。
值是函数体
//变量提升先于函数提升,提升后被函数声明function覆盖,所以就算换了顺序也是function
function a(){
}
var a ;
console.log(typeof a); //function
var f1 = function () {
console.log(1);
}
function f1 () {
console.log(2);
}
f1() ; //1
//变量提升后
var f1;//变量提升
function f1(){};//函数提升
f1 = function () {
console.log(1);
}
f1() ;
理解:一个代码段所在的区域,是静态的,在编写代码时就确定了。
作用:变量绑定在这个作用域内有效,隔离变量,不同作用域下同名变量不会有冲突。
作用域分类
作用域链:多个作用域嵌套,就近选择,先在自己作用域找,然后去就近的作用域找。
函数的作用域在声明的时候就已经决定了,与调用位置无关
所以执行aaa()的时候先在aaa的作用域里面找,没有找到a,再去父级作用域window里面找,找到a=10
var a = 10;
function aaa() {
alert(a);
}
function bbb() {
var a = 20;
aaa();
}
bbb();
对当前JavaScript的执行环境的抽象,每当JavaScript开始执行的时候,它都在执行上下文中运行。
对全局数据进行预处理
var定义的全局变量 --> undefined,添加为window的属性
function声明的全局函数 --> 赋值(函数体),添加为window的方法
this --> 赋值window
开始执行全局代码
函数执行上下文:在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象
对局部数据进行预处理
执行上下文栈
1.在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中(压栈)
4.在当前函数执行完成后,将栈顶的对象移除(出栈)
5.当所有的代码执行完后,栈中只剩下window
| 作用域 | 执行上下文 |
|---|---|
| 定义了几个函数 + 1 = 几个作用域 | 执行了几个函数 + 1 = 几个执行上下文 |
| 函数定义时就确定了,一直存在,不会再变化,是静态的 | 全局执行上下文环境实在全局作用域确定之后,js代码执行之前创建的 调用函数时创建,函数调用结束被释放,是动态的 |

var foo = 1;
function bar () {
console.log(foo);
var foo = 10;
console.log(foo);
}
bar();
//变量提升后
var foo = 1;
function bar () {
var foo = undefined;
console.log(foo); //undefined
foo = 10;
console.log(foo);//10
}
bar();
let :使用立即执行函数创造出一个块级作用域
(function(){
var a = 1;
console.log('内部a:', a);
})();
const
1.使用立即执行函数创造出一个块级作用域。
2.对于不可变性,可以利用Object.defineProperty 将变量挂载在对象上
var __const = function __const(data, value) {
this.data = value // 把要定义的data挂载到某个对象,并赋值value
Object.defineProperty(this,data, { // 利用Object.defineProperty的能力劫持当前对象,并修改其属性描述符
enumerable: false,
configurable: false,
get: function () {
return value
},
set: function (data) {
if (data !== value) { // 当要对当前属性进行赋值时,则抛出错误!
throw new TypeError('Assignment to constant variable.')
} else {
return value
}
}
})
}
//然后和立即执行函数结合
(function(){
var obj = {}
_const.call(obj,'a',10)
})()
//当执行完毕后,全局上就不会有obj,也不会有obj.a这个变量,进而实现了块级作用域的功能
function a(){
a.name ='aaa';
return this.name;
}
var b = {
a,
name:'bbb',
getName:function(){
return this.name;
}
}
var c =b.getName;
console.log(a()); //this指向window,window上没有name,所以输出undefined
console.log(b.a()); //b.a 是function,b调用a函数,所以this指向b,所以输出'bbb'
console.log(b.getName);//通过b调用getName,所以getName指向b,所以输出'bbb'
console.log(c());//c是function,this指向window,window上没有name,所以输出undefined
apply、call、bind 函数可以改变 this 的指向。
| 区别 | call | apply | bind |
|---|---|---|---|
| 调用函数 | √ | √ | × |
| 参数 | 从第二个参数开始依次传递 | 封装成数组传递 | 从第二个参数开始依次传递 |
bind函数的特殊点
多次绑定,只指向第一次绑定的obj对象。
多次绑定,一次生效。
原因:返回函数,后续bind修改的是返回函数的this
call函数的实现
//从第二个参数开始依次传入,所以接收时使用rest参数
Function.prototype.call=function(obj,...args){
obj = obj || window;
args = args ? args : [];
//给obj新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
obj[key] = this;
const res = obj[key](...args);
delete obj[key];
return res;
}
apply函数的实现
Function.prototype.apply=function(obj,args){
obj = obj || window;
args = args ? args : [];
//给obj新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
obj[key] = this;
const res = obj[key](...args);
delete obj[key];
return res;
}
bind函数的实现
Function.prototype.bind(obj,...args)=function(){
obj = obj || window;
args = args ? args : [];
return (...args2) => {
return this.apply(obj,[...args,...args2])
}
}
箭头函数是普通函数的语法糖,书写要更加简洁
| 区别 | 一般函数 | 箭头函数 |
|---|---|---|
| this指向 | 调用时确定 | 定义时确定,没有自己的this,沿着作用域链找父级的this |
| 改变this指向 | call,apply,bind | 不能改变,静态 |
| arguments | 有 | 没有,可以用rest参数代替 |
| 作为构造函数 | √ | × 没有prototype属性 |
| 匿名函数 | 可以匿名可以不匿名 | 匿名函数 |
是什么
闭包就是在函数中能够读取其他函数内部变量
本质就是上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。
正常情况下,代码执行完成之后,函数的执行上下文出栈被回收。但是如果当前函数执行上下文执行完成之后中的某个东西被执行上下文以外的东西占用,则当前函数执行上下文就不会出栈释放,也就是形成了不被销毁的上下文,闭包。
有什么用
1.可以读取函数内部的变量
2.使函数的内部变量执行完后,仍然存活在栈内存中(延长了局部变量的生命周期)。
JavaScript闭包就是在另一个作用域中保存了一份它从上一级函数或者作用域得到的变量,而这些变量是不会随上一级函数的执行完成而销毁
常用场景:节流防抖
缺点是什么
1.函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
2.容易造成内存泄露
怎么解决
及时释放:让内部函数成为垃圾对象(将闭包手动设置为null)–> 回收闭包