预解释
1. JS 基础知识
1.1 先介绍 js 的基本数据类型
基本数据类型 — 值操作
有 number、string、boolean、null、undefined
引用数据类型 —- 引用地址
object、array、Date
1.2 执行环境
当浏览器加载 HTML 页面的时候,首先会提供一个供全局 JS 代码执行的环境 — 全局作用域(global/window)
如下代码是在 script 中
var num = 12;
var obj = {name:”houdashuaige”, age:18};
function fn() {
console.log(“ 好好学习 天天向上 ”);
}
console.log(fn) // 把整个函数定义的部分 (函数本身) 在控制台输出
console.log(fn()) // 输出当前函数的执行返回结果
fn()//return 后面是啥 返回啥 如果没有 return 返回 undefined
示意图如下
如果执行 obj.age = 20;
首先通过地址 xxxfff000 找到对应的空间 然后把空间中 age 属性对应的属性值修改为 20;
其中 fn 存储的是一个地址 代表的时当前函数的整体
2. 预解释
2.1 预解释的基本概念
在当前的作用域中,JS 代码执行之前,浏览器首先会默认地把所有带 var、function 的进行提前的声明或者定义
2.1.1 理解声明 (declare) 和定义(defined)
var num = 12;
声明: var num; // 告诉浏览器在全局作用域中有一个 num 的变量
定义: num = 12(发生在代码执行过程中 不在预解释中); // 给变量进行赋值
function fn() {
console.log(“this is a test”);
}
函数预解释
fn = xxxfff000;
声明: fn // 告诉浏览器在全局作用域中有一个 fn 的函数
定义: fn = xxxfff000; // 给 fn 赋值 指向函数的地址
注释: 所以对于带 var 和 function 关键字的在预解释的时候操作还是不一样的
**var: 对于带 var 的变量 预解释只会声明 不会进行定义 **
**function: 在预解释的时候声明 + 定义 一起完成了 **
附代码
// 函数只有在执行的时候才会对函数内部的代码进行预解释
console.log(num);// undefined 提前声明 但未定义 默认 undefined
var num = 12;
console.log(num);// 12
var obj = {name: “hou”, age: 7};
fn(100,200);// 代码可以在这执行 因为预解释的时候 声明 + 名义就已经完成了
function fn(num1,num2) {
var total = num1 + num2;
console.log(total);
}
附示意图
2.1.2 函数预解释(代码内部)
function fn(num1,num2) {
var total = num1 + num2;
console.log(total);
}
附示意图
2.1.3 JS 中内存的分类(预解释发生在栈内存)
// 栈内存: 用来提供一个供 JS 代码执行的环境 —> 作用域(全局作用域 / 私有作用域)
// 堆内存: 用来存储引用数据类型的值 —> 对象存储的时属性名和属性值
// 函数存储的是代码字符串
2.1.4 如何区分私有变量和全局变量(主要用来分析函数预解释)
注释: 下述都是为了更好地理解 ” 函数预解释 ”, 请耐心阅读
“ 全局作用域 ” 下声明 (预解释的时候) 的变量是全局变量
在 ” 私有作用域 ” 中声明的变量和 ” 函数的形参 ” 都是私有的变量
在私有作用域中,我们代码执行的时候遇到了一个变量,首先我们要确认它是否是私有的变量,如果是私有的变量,那么和外面的变量没有任何的关系,如果不是私有的,则往当前作用域的上级作用域查找,如果上级作用域没有则继续查找,一直找到 window 为止 —>(“ 作用域链 ”)
当函数执行的时候(直接目的: 让函数体中的代码执行),首先会形成一个新的私有作用域,然后按照如下步骤执行:a: 如果有形参,先给形参赋值 b: 进行私有作用域中的预解释 c: 私有作用域中的代码从上到下执行
函数形成一个新的私有作用域保护了里面的私有变量不受外界的影响 (外面修改不了私有的,私有的也修改不了外面的) 形成了 ” 闭包 ” —>(保护机制)
2.1.5 全局作用域中,带 var 和不带 var 的区别
区别 1: 带 var 的可以进行预解释,所以在赋值的前面执行不会报错,不带 var 的是不能进行预解释的,在前面执行会报错
代码如下
console.log(num);—>undefined
var num = 12;
console.log(num2); —> 直接报错 因为 num2 没有预解释
num2=13;
区别 2: 看下述代码
var num = 12;
console.log(num);//12
num2 = 12;
console.log(num2) //12 —> 相当于 window.num2
// 关系:num2 = 12 —> 相当于给 window 增加了一个叫 num2 的属性名,属性值是 12
//var num =12 —> 相当于给全局作用域增加了一个全局变量 num, 但是不仅如此,
// 它也相当于给 window 增加了一个属性名 num, 属性值是 12
函数内部不带 var 的代码如下
var total = 0;
function fn() {
console.log(total) // 0
total = 100;// 相当于修改了全局变量 total
}
fn();
console.log(total)//—>100
// 注释: 私有作用域中出现的一个变量不是私有的,则往上级作用域查找,上级没有则继续向上查找,一直找到 window
// 如果 window 下没有 分两种情况:
//1、如果是获取值:console.log(total) –> 直接报错
//2、如果是设置值:total = 100;—> 相当于给 window 加了 total 属性
//3、JS 中如果在不进行任何特殊处理的情况下, 上边的代码报错,下边的代码都不执行了
2.1.6 预解释是一种毫无节操的机制
预解释的时候不管你的条件是否成立,都要把带 var 的进行提前声明
具体代码如下
if(!(“num” in window)) {//===>if(false)
var num = 12;
}
console.log(num);//—> 实际输出 undefined
// 分析: 不管 if 的条件是否成立 都会把带 var 的进行预解释 (var num = 12)
// 所以 ”num” in window 是 true !(“num” in window) —>false
//if 条件是 false var num = 12 不会执行 所以 console.log(num) 是 undefined
匿名函数之函数表达式
把函数定义的部分当作一个值赋给我们的变量 / 或者赋给元素的某一个事件
fn();//====>fn 是 undefined 相当于 undefined() 报错 uncaught TypeError: fn is not a function
//window 下的预解释 var fn; 预解释的时候只会预解释 “=”(等号)左边的
// 右边的是值 不参与预解释
var fn = function() {
console.log(“ok”);
}
自执行函数
定义和执行一起完成的
// 自执行函数定义的那个 function 在全局作用域下不进行预解释,当代码执行到这个位置的时候,定义和执行一起完成了
// 自执行函数的 5 种形式
(function(num){})(100);// 常用形式
~function(){}(100);
+function(){}(100);
-function(){}(100);
!function(){}(100)
函数内部 return 相关
function fn() {
console.log(num);//—>undefined
return function() {};
var num = 100;
}
fn();
// 函数体种 return 下面的代码虽然不再执行了,但是需要进行预解释
//return 后边跟着的是我们的返回值,所以不进行预解释
预解释经典习题解析
// 注释: 在预解释的时候,如果名字已经声明过了,不需要重新声明,但是需要重新定义(赋值)
//JS 比较懒,声明过一次,便不再声明,但是可以重新定义
// 在 JS 中,如果变量的名字和函数的名字重复了,也算冲突
fn();
function fn() {console.log(1)};
fn();
var fn = 10;
fn();
function fn() {console.log(2)};
fn();
// 输出结果是 2、2、报错:fn is not a function 后边代码不执行
附解释图
(自己画预解释图 学习 分析会更快 更有效)