导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

⭐️ 不能在同一作用域中重复声明

function test(a) {
  let a = 10;
  console.log(a);
}
test();

// 答案:

// SyntaxError: Identifier 'a' has already been declared

⭐️ 没有声明提升,声明之前不可用,会产生一个暂时性死区

console.log(a);
let a = 1;

// 答案:

// Uncaught ReferenceError: Cannot access 'a' before initialization

function test() {
	console.log(a);
  let a = 10;
}
test();

// 答案:

// Uncaught ReferenceError: Cannot access 'a' before initialization at test
var a = a;
console.log(a);

// 答案:

// undefined

let b = b;

console.log(b);

// 答案:

// Uncaught ReferenceError: Cannot access 'b' before initialization
var a = 1;
{
	let a = a;
  console.log(a);
}

// 答案:

// Uncaught ReferenceError: Cannot access 'a' before initialization
console.log(typeof a);
let a = 10;

// 答案:

// Uncaught ReferenceError: Cannot access 'a' before initialization
function test(x = y, y = 2) {
	console.log(x, y); // 报错
	// Uncaught ReferenceError: Cannot access 'y' before initialization
}
test();

// 下面可行
function test(x = 1, y = x) {
  console.log(x, y);
}

test();

⭐️ 只在当前作用域下生效

if (true) {
	let a = 1;
}
console.log(a); // Uncaught ReferenceError: a is not defined
for (;1;) {
	let a = 1;
}
console.log(a); // 不会执行,所以也不会报错
// 因为 上边 for 循环会 死循环
for (var i = 0; i < 10; i++) {
	var i = 'a';
  console.log(i); // a
}
for (let i = 0; i < 10; i++) {
  
}
console.log(i); // Uncaught ReferenceError: i is not defined
for (var i = 0; i < 10; i++) {
  i = 'a';
  console.log(i);
}
// 打印1个a

// 解析:
var i = 0;
for (; i < 10;) {
	i = 'a';
  console.log(i);
  i++;
}

// 1. 第一次循环完毕,i = 'a' 打印 'a',i++ => NaN
// 2. 第二次循环 NaN < 10 不成立,不执行for循环里边代码

⭐️ let 本质上是就是为 js 增加了一个块级作用域

for (let i = 0; i < 10; i++) {
  i = 'a';
  console.log(i);
}

// 打印1个a
for (let i = 0; i < 10; i++) {
  let i = 'a';
  console.log(i);
}

// 打印10个a

if (1) {
	let a = 0;
  {
  	let a = 'a';
  }
}

// for循环的let声明相当于for循环内部的父级
// 所以内外不影响
for (let i = 0; i < 10; i++) {
  var i = 'a'; // i被重复定义
  console.log(i);
}

// SyntaxError: Identifier 'i' has already been declared

// for内部的var i为什么报错呢?可以类比成

if (1) {
	let a = 0;
  {
  	var a = 'a';
  }
}

// 而这个var是会有个类似dom冒泡的机制,有变量提升,会变成

if (1) {
  var a = undefined;
	let a = 0;
  {
  	 a = 'a';
  }
}

由于同一作用域下let不允许重复定义变量,所以报错

块级作用域没有返回值

if (1) {
    return '233';
} // 返回不出去

// 如何才能可以有返回值:用do,但是兼容性不好
do {
	return 1;
}

⭐️ 为什么 JS 有了 var 还要有 let、const

作用域

变量提升

声明重复

  1. var:允许在同一作用域内重复声明同一个变量。
  2. letconst:在同一作用域内不允许重复声明同一个变量。

应用

闭包陷阱

var arr = [];
for (var i = 0; i < 10; i++) {
  arr[i] = function() {
    console.log(i);
  }
}

for (var i = 0; i < 10; i++) {
  arr[i](); // 打印 0-9,原本如果打印应该是10个10、但由于此处
  // 第二个for循环中的i把上一个for循环最后的i=10覆盖了
  // 导致每次循环i都是当前循环的i值
}

let

for 循环中的 let 每次循环都是重新赋值的新变量

var arr = [];

for (let i = 0; i < 10; i++) {
	arr[i] = function() {
  	console.log(i);
  }
}

for (var j = 0; j < 10; j++) {
  arr[j]();
}

// 打印0-9、每次循环let都是一个新值,而不是全局的i。类似于闭包
// 会把i缓存下来

⭐️ let 声明的变量不能与同作用域下的 function 同名

let a = 0;

function a() {}

console.log(a);

// Identifier 'a' has already been declared
let a = 0;

{
  function a() {}
}

console.log(a);

// 打印0
let a = 0;

{
  var a = function a() {}
}

console.log(a);

// Uncaught SyntaxError: Identifier 'a' has already been declared
// let a = 0;

{
  function a() {}
}

console.log(a);

// [Function: a]

函数在当前作用域下才能提升

let a = 0;

{
	a();
  function a() {
  	console.log(10);
  }
}

console.log(a);

// 10
// 0

// 和上边代码的区别:
// 在第一段代码中,外部作用域中的 a 被声明为 let,因此它是块级作用域变量,不会被块内的函数声明覆盖或改变。
// 在第二段代码中,外部作用域中没有任何变量 a,因此在块作用域结束后,a 被提升并初始化为块内声明的函数。

用 let、var 声明,加 () 会报错

let a;
{a} = {a: 1};
console.log(a);
// SyntaxError: Unexpected token '='
let a;
({a} = {a: 1}); // 加上括号使其成为表达式
console.log(a); // 1
let a = [1,2,3],
    obj = {};
[obj.a, obj.b, obj.c] = a;
console.log(obj.a, obj.b, obj.c); // 1 2 3
let ({a: b}) = {};
// ReferenceError: let is not defined

const

一旦定义,必须赋值

const a;
console.log(a);

// Uncaught SyntaxError: Missing initializer in const declaration

const 值不可被修改

const a = 0;
a = 1;

// TypeError: Assignment to constant variable.

不可重复声明

const a = 0;
var a = 1;

// SyntaxError: Identifier 'a' has already been declared

有块级作用域,不可提升

{
	const a = 0;
}

console.log(a);

// ReferenceError: a is not defined

同样有暂时性死区

{
  console.log(a);
	const a = 0;
}

// ReferenceError: Cannot access 'a' before initialization

const 存储引用类型值不保证不可被更改

const person = {};
person.name = 'Lance';
console.log(person);

// { name: 'Lance' }

Object.freeze() 冻结对象

const person = {};
Object.freeze(person);
person.name = 'Lance';
console.log(person);

// {}

只能冻结一层:

const person = {
  name: {
    c: "a",
  },
};
Object.freeze(person);
person.name.c = "1111";
console.log(person); // { name: { c: '1111' } }

冻结对象

function myFreeze(obj) {
	Object.freeze(obj);
  
  for (var key in obj) {
  	if (obj.hasOwnProperty(key)) {
    	if (typeof(obj[key]) === 'object' && obj[key] !== null) {
      	myFreeze(obj[key]);
      }
    }
  }
}

const person = {
	name: 'Lance',
  grade: {
  	Math: 100,
    IT: 90,
    Chinese: 100
  },
  a: {
  	b: {
    	c: 1
    }
  }
}

myFreeze(person);

person.a.b.c = 2333;
console.log(person);

全局变量和顶层对象

var a = 1;

// 等效于
window.a = 1;

⭐️ let、const、class 三个关键字不存储在顶层对象

const a = 1;
console.log(window.a); // undefined

var 和 let

var x = x;
let x = x;
// 两者区别

var x = x; // x有变量提升
// 1. var x = undefined;
// 2. x = undefined;

let x = x;
// 1. let x = x; 中,右侧的 x 试图在声明 x 之前使用它自己,但此时 x 还没有被初始化(因为变量的声明和赋值在同一行)。
// 2. 在 let 声明之前,变量 x 是未定义且不可用的,所以会报错。

Untitled

Untitled

⭐️ 题型

var x = 1;
function foo(x, y = function() { x = 2; console.log(x) }) {
	var x = 3;
  y();
  console.log(x);
}
foo();
console.log(x);

Untitled

接着函数执行,x赋值为3,执行函数y,函数内没有,函数参数中有y:

Untitled

执行y:

Untitled

把参数内部的x赋值为2:

Untitled

接着点击执行栈中的foo我们进行查看,会发现函数参数中y执行改掉的x是函数参数内部的x,而不是foo函数体中的变量x:

Untitled

所以最终结果:

Untitled

变体1

var x = 1;
function foo(x, y = function() { var x = 2; console.log(x) }) {
	var x = 3;
  y();
  console.log(x);
}
foo();
console.log(x);

// 答案:

// 2 3 1 和上面一样
可以把上面代码简化看成如下

{
	let x = 2;
  {
  	let x = 3;
    console.log(x)
  }
  console.log(x);
}

⭐️ 变体2

var x = 1;
function foo(x, y = function() { x = 2; console.log(x) }) {
	x = 3;
  y();
  console.log(x);
}
foo();
console.log(x);

// 答案:

// 打印 2 2 1

// 分析
// 1. 函数参数内部声明了x,y
// 2. 函数执行 x = 3
// 3. 由于函数内部没有声明x,所以去函数参数中找x,找到了,赋值x=3
// 4. 接着执行y(),y函数内部又把x从3改回了2
// 5. y函数内部打印x为2
// 6. foo函数内部打印x也是2
// 7. 全局GO中的x没有变化,仍为1
可以把上面代码简化看成如下

{
	let x = 2;
  {
  	x = 3;
    console.log(x)
  }
  console.log(x);
}

⭐️ 变体 3

var x = 1;
function foo(x, y = function() { var x = 2; console.log(x) }) {
	let x = 3;
  y();
  console.log(x);
}
foo();

// 报错:SyntaxError: Identifier 'x' has already been declared