let、const、var及其区别,变量提升
代码获取到后:
词法解析(AST):把代码拆成对应的字符,并且识别成浏览器可以解析的对象。
上下文 --> 【初始化】作用域链、【初始化】this、形参赋值......【最后】变量提升 --> 代码执行
/*
- JS中声明变量或者函数的方式
- 【传统】
- var n = 10;
- function func(){} -> var func=function(){};
- 【ES6】
- let n = 10;
- const m = 20;
- let func = () => {};
- import xxx from 'xxx';
*//*
const设置的是常量,存储的值不能被改变 【不对】
const创建的变量,它的指针指向一旦确定,不能再被修改【正确】
let设置的是变量,存储的值可以改变 【正确】
*/
const n = 10;
n = 20; // => Uncaught TypeError: Assignment to constant variable.
console.log(n);
const obj = {
name: 'xxx'
};
obj.name = "哈哈嘿嘿";
console.log(obj); // => {name:"哈哈嘿嘿"}
// ------------------------------
/*
* let 和 var 的区别?
* => let不存在变量提升
* => let不允许重复声明
* => let会产生块级作用域
* => 暂时性死区的问题
*/
// 变量提升:在当前上下文代码自上而下执行之前,会把所有带var/function关键字的进行提前的声明或者定义(带var是只声明,带function是声明+定义(赋值)都完成了)
/*
* EC(G)
* 变量提升: var a; func=AAAFFF000;
* 代码执行:
*/
console.log(a); // => undefined
func(); // => "OK"
var a = 12;
function func() {
console.log('OK');
}
// ------------------------
/*
* EC(G)
* 变量提升:--
* 代码执行
*/
console.log('STRAT');
console.log(a); // => Uncaught ReferenceError: Cannot access 'a' before initialization 代码执行中遇到输出a,检测到下面有基于let声明的操作,则提示不允许在声明之前使用这个变量
func();
let a = 12;
let func = () => {
console.log('OK');
};
// ------------------------
// Uncaught SyntaxError: Identifier 'a' has already been declared 重复声明的检测和报错,不是发生在代码执行阶段,发生在词法解析阶段(不论基于什么声明的变量,只要上下中有这个变量,都不能再基于let重复声明了)
console.log('START');
var a = 12;
let a = 13;
console.log(a);
// ------------------------
/*
* 暂时性死区(浏览器的BUG)
* 《深入理解ES6》:使用 let 或 const 声明的变量,在达到声明处之前都是无法访问的,试图访问会导致一个引用错误,即使在通常是安全的操作时(例如使用 typeof 运算符),也是如此。
*/
console.log(a); //Uncaught ReferenceError: a is not defined
console.log(typeof a); // => 检测一个未被声明的变量,不会报错,结果是"undefined"
typeof window !== "undefined" // => 说明当前环境下存在window(浏览器环境) JQ源码中也是基于这样的方式处理的
console.log(typeof a); // => Uncaught ReferenceError: Cannot access 'a' before initialization
let a;
前端代码中的上下文(作用域)
/*
* 前端代码中的上下文(作用域)
* 1. 全局上下文
* 2. 函数执行形成的私有上下文
*/
var a = 12;
if (1 == 1) {
console.log(a); // 12
var a = 13;
console.log(a); // 13
}
console.log(a); // 13
// ------------------------
// 如果代码块中出现了 let、const、function,则当前代码块会产生一个 块级上下文(词法、块级作用域) => 私有的上下文
let a = 12;
if (1 == 1) {
// console.log(a); // => Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 13;
console.log(a); // 13
}
console.log(a); // 12
// ------------------------
// 即使混在一起,跨级作用域只对let、const、function生效,对var不生效
var n = 12;
let m = 13;
if (1 == 1) {
var n = 120;
let m = 130;
console.log(n, m); // 120 130
}
console.log(n, m); // 120 13
// ------------------------
// 循环
for (var i = 0; i < 5; i++) {
// i 都是全局的
}
console.log(i); // => 5
for (let i = 0; i < 5; i++) {
// 私有的块级上下文
// 循环几次会产生几个块级上下文 【上下文是平级的,不嵌套。】
}
console.log(i); // => Uncaught ReferenceError: i is not defined
循环中的 IIFE、块级作用域
// 设置5个1s后执行的定时器
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// i是全局变量
// 第一轮循环 i=0 设置定时器 1000MS => {} 【任务队列】 i++
// 第二轮循环 i=1 ...
// 循环结束 i=5
// -----
// 达到时间后,依次把任务队列中的五个定时器到时候后要做的事情去执行
// () => { console.log(i); } i不是自己私有上下文中的变量,则找其上级上下文(全局),但是此时全局的 i = 5
// ------------------------
// 设置5个1s后执行的定时器
for (var i = 0; i < 5; i++) {
// 每一轮循环:把自执行函数执行
// EC(AN1) [不会释放]
// 作用域链:<EC(AN1),EC(G)>
// 形参赋值:i=0
(function (i) {
// 设置一个定时器(异步任务:任务队列)
setTimeout(() => {
console.log(i);
}, 1000);
})(i);
}
// ------------------------
function func(i) {
return function anonymous() {
console.log(i);
}
}
for (var i = 0; i < 5; i++) {
// 第一轮循环:i=0 设置定时器的时候,把func函数执行,传递0进去,把返回的匿名函数anonymous设置给定时器 这样func执行形成的上下文是不被释放的(形参i=0也是不释放的)
setTimeout(func(i), 1000); // 执行func(i)返回函数anonymous
}
// 1000MS后执行的是绑定的anonymous
// ------------------------
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
循环绑定事件的优化
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
<title>哈哈哈</title>
<!-- IMPORT CSS -->
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
overflow: hidden;
}
button {
padding: 5px 10px;
cursor: pointer;
}
</style>
</head>
<body>
<button value="pink">红</button>
<button value="yellowgreen">绿</button>
<button value="skyblue">蓝</button>
</body>
</html>
<script>
// 补充:var 和 function 的提升顺序
// 结论:先var a = undefined;然后 a = 函数a的地址 0x111
console.log(a) // 函数a
var a = 11
function a() {
console.log(22)
}
console.log(b) // 函数b
function b() {
console.log(66)
}
var b = 55
</script>
<script>
// 基于事件委托实现多元素的事件绑定,要比传统循环一个个的给元素进行事件绑定,性能提高40%~60%
document.body.onclick = function (ev) {
var target = ev.target;
if (target.tagName === "BUTTON") {
// this -> body
this.style.background = target.value;
}
};
// ------------------------------
var body = document.querySelector('body')
var buttons = document.querySelectorAll('button')
var arr = ['pink', 'yellowgreen', 'skyblue']
for (var i = 0; i < buttons.length; i++) {
var item = buttons[i];
item.myIndex = i; // => 在循环的时候,把每一个按钮的索引赋值给当前按钮(元素对象)的myIndex自定义属性
item.onclick = function () {
// this => 当前点击的这个按钮
body.style.background = arr[this.myIndex];
};
}
// ------------------------------
// 都是利用闭包的机制去解决的
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = (function (i) {
// IIFE中必须 return一个函数,作为IIFE的返回值
return function anonymous() {
body.style.background = arr[i];
};
})(i);
}
// ------------------------------
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = function () {
body.style.background = arr[i];
};
}
</script>