var,let,const 区别
var
在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量
注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象,但是Node环境并不会挂载到global上
var a = 10;
console.log(window.a); // 10
// 变量提升
console.log(b); // undefined 执行过程:var b -> console.log -> b=20
var b = 20;
// var可以重复定义
var c = 30;
var c = 40;
console.log(c); // 40
var d = 10;
function fun() {
d = 20;
}
fun();
console.log(d); // 20
// 如果改为以下,则函数内的变量变为局部变量
var d = 10;
function fun2() {
var d = 20;
}
fun2();
console.log(d); // 10
let
let是ES6新增的命令,用来声明变量
用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效
// 只在所处代码块有效
{
let a = 10;
}
console.log(a); // ReferenceError: a is not defined.
// 不存在变量提升
console.log(b);
let b = 20; // ReferenceError: Cannot access 'b' before initialization
// 作用域内存在let时,作用域不受外界影响 —— 暂时性死区
var c = 123;
if (true) {
c = 'abc'; // ReferenceError: Cannot access 'c' before initialization
let c;
}
// 【同一个作用域内】不允许重复定义
let d = 30;
let d = 40;
console.log(d); // SyntaxError: Identifier 'd' has already been declared
// 这种情况没有问题,因为不是同一个作用域内
let d = 20;
{
let d = 30;
console.log(d); //30
}
console.log(d); // 20
const
const声明一个只读的常量,一旦声明,常量的值就不能改变(针对基础类型)
const a = 10;
a = 20; // TypeError: Assignment to constant variable.
// 声明的时候必须定义值
const b; // SyntaxError: Missing initializer in const declaration
// 定义对象
const c = {};
// 对对象重新赋值是不允许的,但是可以对对象的值进行赋值
c = {}; // TypeError: Assignment to constant variable.
c.prop = 30;
console.log(c.prop); // 30
能够为const的对象添加新的值的原因
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动
对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量
对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变
总结
- 变量提升
- 暂时性死区
- 块级作用域
- 重复声明
- 修改声明的变量
- 使用 尽量使用
const,减少使用var
this指向
-
普通函数
this指向window
-
对象
this在对象调用的时候指向对象- 先赋值再调用,看调用的地方
-
new- 创建临时对象
- 将
this指向临时对象 - 执行构造函数
- 返回临时对象
function fun() {
console.log(this); // 普通函数
}
fun(); // 浏览器环境-Window,Node环境-global
const Obj = {
name: "Kevin",
showName: function () {
console.log(this.name, this);
},
};
Obj.showName(); // Kevin Obj
const getName = Obj.showName; // undefined Window(Node环境 - global)
getName();
function ShowName() {
this.name = "Kevin";
}
const GetName = new ShowName();
console.log(GetName.name); // Kevin
console.log(ShowName.name); // ShowName 函数的属性
闭包
闭包是指在函数内部创建的函数,它可以访问并持有其所在作用域内的变量,即使在其所在作用域外部被调用时仍然有效。换句话说,闭包是函数以及其相关的引用环境的组合体。
闭包通常在以下情况下出现:
- 在一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量。
- 内部函数被返回,或者被传递给其他函数作为参数,从而在外部函数的作用域之外被调用。
// 举个🌰
function outerFunction() {
let outerVariable = 'Kevin';
return function innerFunction() {
console.log(outerVariable);
}
}
let closure = outerFunction();
closure(); // Kevin
闭包的特点包括:
- 可以访问和持有外部函数的变量和参数,即使外部函数已经执行完毕。
- 外部函数的变量在闭包中被保存,形成了一个闭包环境。
- 闭包可以在外部函数的作用域之外被调用,延长了变量的生命周期。
- 闭包可以访问不同实例中的不同变量,每个闭包都有独立的引用环境。
通过使用闭包,我们可以创建私有变量、实现函数柯里化、实现模块化等功能。闭包在JavaScript中是一个非常强大和常用的特性,它使得函数可以拥有状态并且能够保持对其相关变量的访问。
const myModule = (function() {
let privateVar = "private Variable";
function printPrivate() {
console.log(privateVar);
}
return {
printPublic: function() {
printPrivate();
},
publicVar: 'public Var'
}
})();
/**
* 立即执行函数(Immediately Invoked Function Expression,IIFE)在闭包模块化中的使用是为了创建一个独立的作用域。
* 为什么要使用立即执行函数呢?主要有以下几个原因:
* 1. 创建独立的作用域:立即执行函数可以创建一个私有的作用域,避免全局命名空间污染和变量冲突。
* 2. 封装实现细节:通过将模块的实现细节封装在立即执行函数内部,可以隐藏模块的内部实现,只暴露需要对外公开的接口。
* 3. 避免变量提升问题:立即执行函数可以防止变量和函数在外部被意外访问或修改,保持模块的封装性。
*/
myModule.printPublic(); // "private Variable"
console.log(myModule.privateVar, myModule.publicVar); // undefined "public Variable"
const curry = function(fn) {
return function curried(...args) {
// 如果当前柯里化函数参数个数多于或等于fn的参数个数(fn.length),说明已经成功柯里化
if(args.length >= fn.length) {
// 为fn入参
return fn.apply(this, args);
} else {
return function(...args2) {
// 否则,链接上一次的参数与新的参数,并且继续调用curried函数
return curried.apply(this, args.concat(args2));
}
}
}
}
const add = function(a, b, c) {
return a+b+c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2,3)); // 6