JavaScript高级篇
一、javaScript基础总结
数据类型分类
基本(值)类型
- Number:任意数值
- String:任意文本
- Boolean:true / false
- undefined:undefined
- null:null
对象(引用)类型
- Object:任意对象([]、函数...)
- Array:特别的对象类型(数值下标 / 内部数据有序)
- Function:特别的对象类型(可执行)
注:除了基本数据类型,其余类型都是对象类型
数据类型 判断
typeof
- 返回数据类型的字符串表达;
- 可以区别:数值、字符串、布尔值、undefined、function;
- 不能区别:null与object、一般object与array。
instanceof
- 专门用来判断对象数据的类型:Object、Array与Function。
===
- 可以判断:undefined和null。
<script type="text/javascript">
// typeof: 返回的是数据类型的字符串表达形式
//1. 基本类型
var a
console.log(a, typeof a, a === undefined) // undefined \'undefined\' true
console.log(a === typeof a) // false, (typeof a) 返回的是字符串类型的\'undefined\'
a = 3
console.log(typeof a === \'number\') //true
a = \'atguigu\'
console.log(typeof a === \'string\') //true
a = true
console.log(typeof a === \'boolean\') //true
a = null
console.log(a === null) // true
console.log(typeof a) // \'object\'
var b = undefined;
console.log(b === undefined); //true
console.log(typeof b); //undefined
console.log(\'--------------------------------\')
// //2. 对象类型
var b1 = {
b2: [2, \'abc\', console.log],
b3: function () {
console.log(\'b3()\')
}
}
console.log(b1 instanceof Object, typeof b1) // true \'object\'
console.log(b1.b2 instanceof Array, typeof b1.b2) // true \'object\'
console.log(b1.b3 instanceof Function, typeof b1.b3) // true \'function\'
console.log(typeof b1.b2[2]) // \'function\'
console.log(b1.b2[2](\'abc\')) // \'abc\' undefined
</script>
实例
1. undefined与null的区别?
2. 什么时候给变量赋值为null呢?
初始赋值,表明将要赋值为对象;结束前,让对象成为垃圾对象(被垃圾回收器回收)。初始化赋值:将要作为引用变量使用, 但对象还没有确定。结束时:将变量指向的对象成为垃圾对象。
var a = null // a将指向一个对象,但对象此时还没有确定
....
a = null // 让a指向的对象成为垃圾对象
3. 严格区别变量类型与数据类型?
js的变量本身是没有类型的,变量的类型实际上是变量内存中数据的类型(js是弱类型的语言)。var a; 判断变量类型,实际上 是判断值的类型。
数据的类型(数据对象):
- 基本类型
- 对象类型
变量的类型(变量内存值的类型):
- 基本类型:保存基本类型的数据(保存基本类型数据的变量)。
- 引用类型:保存对象地址值(保存对象地址值的变量)。
练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>01_相关问题</title>
</head>
<body>
<!--
1. undefined与null的区别?
* undefined代表定义未赋值
* nulll定义并赋值了, 只是值为null
2. 什么时候给变量赋值为null呢?
* 初始赋值, 表明将要赋值为对象
* 结束前, 让对象成为垃圾对象(被垃圾回收器回收)
3. 严格区别变量类型与数据类型?
* 数据的类型
* 基本类型
* 对象类型
* 变量的类型(变量内存值的类型)
* 基本类型: 保存就是基本类型的数据
* 引用类型: 保存的是地址值
-->
<script type="text/javascript">
// 实例: 实例对象
// 类型: 类型对象
function Person(name, age) {// 构造函数 类型
this.name = name
this.age = age
}
var p = new Person(\'tom\', 12) // 根据类型创建的实例对象
// Person(\'jack\', 12)
// 1. undefined与null的区别?
var a
console.log(a) // undefined
a = null
console.log(a) // null
//起始
var b = null // 初始赋值为null, 表明将要赋值为对象
//确定对象就赋值
b = [\'atguigu\', 12]
//最后
b = null // 让b指向的对象成为垃圾对象(被垃圾回收器回收)
// b = 2
var c = function () {
}
console.log(typeof c) // \'function\'
</script>
</body>
</html>
数据、变量、内存管理
概述
1. 什么是数据?
* 存储于内存中代表特定信息的\'东东\', 本质就是0101二进制
* 具有可读和可传递的基本特性
* 万物(一切)皆数据, 函数也是数据
* 程序中所有操作的目标: 数据
* 算术运算
* 逻辑运算
* 赋值
* 调用函数传参
...
2. 什么是内存?
* 内存条通电后产生的存储空间(临时的)
* 产生和死亡: 内存条(集成电路板)==>通电==>产生一定容量的存储空间==>存储各种数据==>断电==>内存全部消失
* 内存的空间是临时的, 而硬盘的空间是持久的
* 一块内存包含2个数据
* 内部存储的数据(一般数据/地址数据)
* 内存地址值数据
* 内存分类
* 栈: 全局变量, 局部变量 (空间较小)
* 堆: 对象 (空间较大)
3. 什么是变量?
* 值可以变化的量, 由变量名与变量值组成
* 一个变量对应一块小内存, 变量名用来查找到内存, 变量值就是内存中保存的内容
4. 内存,数据, 变量三者之间的关系
* 内存是一个容器, 用来存储程序运行需要操作的数据
* 变量是内存的标识, 我们通过变量找到对应的内存, 进而操作(读/写)内存中的数据
问题1:变量在内存中保存的值
var a = xxx(赋值操作),a内存中到底保存的是什么?
问题2:关于引用变量赋值
关于引用变量赋值问题
- 2个引用变量指向同一个对象(保存的内容是同一个对象的地址值),通过一个引用变量修改对象内部数据,另一个引用变量也看得见(看见的是修改之后的数据)。
- 2个引用变量指向同一个对象,让一个引用变量指向另一个对象,另一个引用变量还是指向原来的对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>02_关于引用变量赋值问题</title>
</head>
<body>
<script type="text/javascript">
//1. 2个引用变量指向同一个对象, 通过一个引用变量修改对象内部数据, 另一个引用变量也看得见
var obj1 = {}
var obj2 = obj1
obj2.name = \'Tom\'
console.log(obj1.name) // Tom
function f1(obj) {
obj.age = 12
}
f1(obj2)
console.log(obj1.age) // 12
//2. 2个引用变量指向同一个对象,让一个引用变量指向另一个对象, 另一个引用变量还是指向原来的对象
var obj3 = {
name: \'Tom\'
}
var obj4 = obj3
obj3 = {
name: \'JACK\'
}
console.log(obj4.name) // Tom
function f2(obj) {
obj = {
name: \'Bob\'
}
}
f2(obj4)
console.log(obj4.name) // Tom
var a = {age: 12}; // 2个引用变量指向同一个对象
var b = a; // 2个引用变量指向同一个对象
a = {name: \'Bob\', age: 13}; // 将新的对象赋值给a
b.age = 14; // 另一个引用变量还是指向原来的对象
console.log(b.age, a.name, a.age); // 14 "Bob" 13
function fn2 (obj) {
obj = {age: 15}; // 新的对象(垃圾对象)
}
fn2(a); // 函数执行完,函数内部的局部变量(obj)会自动释放
//总结:obj引用的地址值发生改变,不再引用传进来的形参地址,并且函数作用域的原因,这个对象只能在函数内使用。
console.log(a.age); // 13
</script>
</body>
</html>
问题3:数据传递问题
在js调用函数时传递变量参数时,是值传递还是引用传递?
理解1:都是值(基本/地址值)传递。
理解2:可能是值传递,也可能是引用传递(地址值)。
只有值传递,没有引用传递,传递的都是变量的值,只是这个值可能是基本数据,也可能是地址(引用)数据。
如果后一种看成是引用传递,那就值传递和引用传递都可以有。
因为函数里的a是函数内的局部变量,换成this.a = a + 1,这时候this.a就是你最开始定义的a。
假设形参是x,调用函数传参时,发生了两件事:1、读取全局变量a的值;2、将值赋值给x ,然后执行函数内代码。
括号里的a是全局里的a的值copy给他的。
问题4:JS引擎如何管理内存?
1. 内存生命周期
分配小内存空间,得到它的使用权(分配需要的内存)。
存储数据,可以反复进行操作(使用分配到的内存)。
释放小内存空间(不需要时将其释放/归还)。
2. 释放内存
局部变量:函数执行完 自动释放(为执行函数分配的栈空间内存)。
对象:成为垃圾对象==>垃圾回收器回收(存储对象的堆空间内存:当内存没有引用指向时,对象成为垃圾对象,垃圾回收器后面就会回收释放此内存。)
问题5:什么是对象
-
多个数据的封装体(集合体)。
-
用于保存多个数据的容器。
-
一个对象代表现实中的一个事物(代表现实中的某个事物,是该事物在编程中的抽象)。
问题6:为什么要用对象?
- 便于对多个数据进行统一管理。
问题7:对象的组成
对象中的函数叫方法;对象中的字符串、数字等等叫属性。
- 属性:属性名(字符串)和属性值(任意)组成。(代表现实事物的状态数据;属性名都是字符串类型,属性值是任意类型)
- 方法:一种特别的属性(属性值是函数)。(代表现实事物的行为数据)
问题8:如何访问对象内部数据?
-
对象.属性名: 编码简单, 有时不能用
-
对象[\'属性名\']: 编码麻烦, 能通用
问题9:什么时候必须使用[\'属性名\']的方式?
-
属性名包含特殊字符: - 空格
-
属性名不确定
函数
1.什么是函数?
- 实现特定功能的n条语句的封装体;
- 只有函数是可执行的,其它类型的数据是不能执行(不用管);
- 函数也是对象。
2. 为什么要用函数?
-
提高代码复用
-
便于阅读交流
3.如何定义函数?
- 函数声明:整体会被提升到当前作用域顶部。
- 表达式:也会提升到顶部,但是只有变量名提升。
4. 如何调用(执行)函数?
- test():直接调用
- obj.test():通过对象调用
- new test():new 调用
- test.call/apply(obj):临时让test成为obj的方法进行调用
5.什么函数才是回调函数?
- 用户定义的
- 用户没有直接调用
- 但最终它执行了(在某个时刻或某个条件下 在特定条件或时刻)
6.常见的回调函数?
- dom事件回调函数 ==> 发生事件的dom元素
- 定时器回调函数 ==> window
- 超时定时器
setTimeOut(function(){},延迟时间) - 循环定时器
setInterval(function(){},定时)
- 超时定时器
- 后面学:ajax请求回调函数、生命周期回调函数
7.匿名函数(IIFE)
1. 理解
- 全称:Immediately-Invoked Function Expression,立即调用函数表达式
- 别名:匿名函数自调用
2. 作用
- 隐藏内部实现
- 不会污染外部(全局)命名空间
- 用它来编码js模块
匿名函数自调用,并且只能执行一次。匿名函数得是一个表达式,不能是函数声明。
(function () { //匿名函数自调用
var a = 3
console.log(a + 3)
})()
console.log(a) // a is not defined
//此处前方为何要一个`;`-->因为自调用函数外部有一个()包裹,可能与前方以()结尾的代码被一起认为是函数调用
//不加分号可能会被认为这样 console.log(a)(IIFE)
;(function () {//不会污染外部(全局)命名空间-->举例
let a = 1;
function test () { console.log(++a) } //声明一个局部函数test
window.$ = function () { return {test: test} }// 向外暴露一个全局函数
})()
test () //test is not defined
$().test() // 1. $是一个函数 2. $执行后返回的是一个对象
函数中的this
① this是什么?
- 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
- 所有函数内部都有一个变量this
- 它的值是
调用函数的当前对象
② 如何确定this的值?
- test(): window
- p.test(): p
- new test(): 新创建的对象
- p.call(obj): obj
③ 代码举例详解
function Person(color) { console.log(this) this.color = color; this.getColor = function () { console.log(this) return this.color; }; this.setColor = function (color) { console.log(this) this.color = color; }; } Person("red"); //this是谁? window const p = new Person("yello"); //this是谁? p p.getColor(); //this是谁? p const obj = {}; //调用call会改变this指向-->让我的p函数成为`obj`的临时方法进行调用 p.setColor.call(obj, "black"); //this是谁? obj const test = p.setColor; test(); //this是谁? window -->因为直接调用了 function fun1() { function fun2() { console.log(this); } fun2(); //this是谁? window } fun1();//调用fun1
关于语句分号
- js一条语句的后面可以不加分号
- 是否加分号是编码风格问题, 没有应该不应该,只有你自己喜欢不喜欢
- 在下面2种情况下不加分号会有问题
小括号开头的前一条语句中方括号开头的前一条语句
- 解决办法: 在行首加分号
- 强有力的例子: vue.js库
- 知乎热议: https://www.zhihu.com/question/20298345
二、函数高级
1、原型与原型链
Ⅰ-原型 [prototype]
- 函数的
prototype属性
- 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
- 原型对象中有一个属性constructor, 它指向函数对象
- 给原型对象添加属性(
一般都是方法)
- 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
- 代码示例
// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象) console.log(Date.prototype, typeof Date.prototype) function Fun () { } console.log(Fun.prototype) // 默认指向一个Object空对象(没有我们的属性) // 原型对象中有一个属性constructor, 它指向函数对象 console.log(Date.prototype.constructor===Date) console.log(Fun.prototype.constructor===Fun) //给原型对象添加属性(一般是方法) ===>实例对象可以访问 Fun.prototype.test = function () { console.log(\'test()\') } var fun = new Fun() fun.test()
Ⅱ-显式原型与隐式原型
每个函数function都有一个
prototype,即显式原型(属性)每个实例对象都有一个[
__ proto __],可称为隐式原型(属性)对象的隐式原型的值为其对应构造函数的显式原型的值
内存结构
总结:
- 函数的[
prototype]属性: 在定义函数时自动添加的, 默认值是一个空Object对象- 对象的[
__ proto __]属性: 创建对象时自动添加的,默认值为构造函数的prototype属性值- 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
- 代码示例:
//定义构造函数 function Fn() { // 内部默认执行语句: Fn.prototype = {} } // 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象 console.log(Fn.prototype) // 2. 每个实例对象都有一个__proto__,可称为隐式原型 //创建实例对象 var fn = new Fn() // 内部默认执行语句: this.__proto__ = Fn.prototype console.log(fn.__proto__) // 3. 对象的隐式原型的值为其对应构造函数的显式原型的值 console.log(Fn.prototype===fn.__proto__) // true //给原型添加方法 Fn.prototype.test = function () { console.log(\'test()\') } //通过实例调用原型的方法 fn.test()
Ⅲ-原型链
① 原型链
- 原型链
- 访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着[
__ proto __]这条链向上查找, 找到返回- 如果最终没找到, 返回undefined
- 别名: 隐式原型链
- 作用: 查找对象的属性(方法)
②构造函数/原型/实例对象的关系(图解)
var o1 = new Object();
var o2 = {};
function Foo(){ } //相当于 var Foo = new Function(),说明Foo函数是Function的实例对象
function f1(){} //相当于var f1 = new Function(),
console.log(Foo.__proto__ === f1.__proto__) //true,说明任何函数的隐式原型属性 __proto__ 都指向相同的地址(该地址指向Function.prototype的值)
ps:所有函数的__proto__都是一样的
var Function = new Function(); //Function函数自己创建自己,Function既是示例对象也是构造函数 Function.__proto__ = Function.prototypeps:所有函数的[
__ proto __]都是一样的
③ 属性问题
读取对象的属性值时: 会自动到原型链中查找
设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
代码示例
function Fn() { } Fn.prototype.a = \'xxx\' var fn1 = new Fn() console.log(fn1.a, fn1) //xxx Fn{} var fn2 = new Fn() fn2.a = \'yyy\' console.log(fn1.a, fn2.a, fn2) //xxx yyy Fn{a: "yyy"} function Person(name, age) { this.name = name this.age = age } Person.prototype.setName = function (name) { this.name = name } var p1 = new Person(\'Tom\', 12) p1.setName(\'Bob\') console.log(p1) //Person {name: "Bob", age: 12} var p2 = new Person(\'Jack\', 12) p2.setName(\'Cat\') console.log(p2) //Person {name: "Cat", age: 12} console.log(p1.__proto__===p2.__proto__) // true -->所以方法一般定义在原型中
Ⅳ-instanceof
- instanceof是如何判断的?
- 表达式: A instanceof B
- 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
- Function是通过new自己产生的实例
/* 案例1 */ function Foo() { } var f1 = new Foo() console.log(f1 instanceof Foo) // true console.log(f1 instanceof Object) // true /* 案例2 */ console.log(Object instanceof Function) // true console.log(Object instanceof Object) // true console.log(Function instanceof Function) // true console.log(Function instanceof Object) // true function Foo() {} console.log(Object instanceof Foo) // false
总结:
xxx instanceof Object //永远为true,因为Object.prototype为原型链终点
xxx(函数名) instanceof Function //永远为true,var xxx = new Function(),xxx.__proto__ 等于 Function.prototyoe,一句话:实例对象的隐式原型属性等于构造函数的显式原型属性
Ⅴ-相关面试题
测试题1:
/* 测试题1 */ function A () {} A.prototype.n = 1 let b = new A() A.prototype = { n: 2, m: 3} let c = new A() console.log(b.n, b.m, c.n, c.m) // 1 undefined 2 3测试题2:原理看②构造函数/原型/实例对象的关系(图解)
/* 测试题2 */ function F (){} Object.prototype.a = function(){ console.log(\'a()\') } Function.prototype.b = function(){ console.log(\'b()\') } let f = new F() f.a() //a() f.b() //f.b is not a function -->找不到 F.a() //a() F.b() //b() console.log(f) console.log(Object.prototype) console.log(Function.prototype)结果图例
2、执行上下文与执行上下文栈
当代码在 JavaScript 中运行时,执行代码的环境非常重要,并将概括为以下几点:
全局代码——第一次执行代码的默认环境。
函数代码——当执行流进入函数体时。
(…) —— 我们当作 执行上下文 是当前代码执行的一个环境与范围。
换句话说,当我们启动程序时,我们从全局执行上下文中开始。一些变量是在全局执行上下文中声明的。我们称之为全局变量。当程序调用一个函数时,会发生什么?
以下几个步骤:
- JavaScript 创建一个新的执行上下文,我们叫作本地执行上下文。
- 这个本地执行上下文将有它自己的一组变量,这些变量将是这个执行上下文的本地变量。
- 新的执行上下文被推到到执行堆栈中。可以将执行堆栈看作是一种保存程序在其执行中的位置的容器。
函数什么时候结束?当它遇到一个 return 语句或一个结束括号}。
当一个函数结束时,会发生以下情况:
- 这个本地执行上下文从执行堆栈中弹出。
- 函数将返回值返回调用上下文。调用上下文是调用这个本地的执行上下文,它可以是全局执行上下文,也可以是另外一个本地的执行上下文。这取决于调用执行上下文来处理此时的返回值,返回的值可以是一个对象、一个数组、一个函数、一个布尔值等等,如果函数没有 return 语句,则返回 undefined。
- 这个本地执行上下文被销毁,销毁是很重要,这个本地执行上下文中声明的所有变量都将被删除,不在有变量,这个就是为什么 称为本地执行上下文中自有的变量。
此图出于CSDN的Free Joe
Ⅰ-变量提升与函数提升
- 变量声明提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值: undefined
- 函数声明提升
- 通过function声明的函数, 在之前就可以直接调用
- 值: 函数定义(对象)
- 引出一个问题: 变量提升和函数提升是如何产生的?
/* 面试题 : 输出 undefined */ var a = 3 function fn () { console.log(a) var a = 4 //变量提升 } fn() //undefined \'--------------------------------------------\' console.log(b) //undefined 变量提升 fn2() //可调用 函数提升 // fn3() //报错 dmeo.html:22 Uncaught TypeError: fn3 is not a function 变量提升 var b = 3 function fn2() { console.log(\'fn2()\') } var fn3 = function () { console.log(\'fn3()\') }
Ⅱ-执行上下文
- 代码分类(位置)
- 全局代码
- 函数(局部)代码
- 全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
- var定义的全局变量==>undefined, 添加为window的属性
- function声明的全局函数==>赋值(fun), 添加为window的方法
- this==>赋值(window)
- 开始执行全局代码
- 函数执行上下文
- 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
- 对局部数据进行预处理
- 形参变量>赋值(实参)>添加为执行上下文的属性
arguments==>赋值(实参列表), 添加为执行上下文的属性 -->不懂的同学看这里- var定义的局部变量==>undefined, 添加为执行上下文的属性
- function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
- this==>赋值(调用函数的对象)
- 开始执行函数体代码
Ⅲ-执行上下文栈
- 在全局代码执行前, JS引擎就会创建一个栈空间来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)-->
所以栈底百分百是[window]- 在函数执行上下文创建后, 将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后, 栈中只剩下window
上下文栈数==函数调用数+1//1. 进入全局执行上下文 var a = 10 var bar = function (x) { var b = 5 foo(x + b) //3. 进入foo执行上下文 } var foo = function (y) { var c = 5 console.log(a + c + y) } bar(10) //2. 进入bar函数执行上下文
此处用一个动态图来展示:
举个栗子:
//栗子 <!-- 1. 依次输出什么? gb: undefined fb: 1 fb: 2 fb: 3 fe: 3 fe: 2 fe: 1 ge: 1 2. 整个过程中产生了几个执行上下文? 5 --> <script type="text/javascript"> console.log(\'gb: \'+ i) var i = 1 foo(1) function foo(i) { if (i == 4) { return } console.log(\'fb:\' + i) foo(i + 1) //递归调用: 在函数内部调用自己 console.log(\'fe:\' + i) //出栈 所以会 3 2 1这样的结果 } console.log(\'ge: \' + i) </script>
Ⅳ-相关面试题
函数提升优先级高于变量提升,且不会被变量声明覆盖,但是会被变量赋值覆盖/* 测试题1: 先执行变量提升, 再执行函数提升 */ function a() {} var a console.log(typeof a) // \'function\' /* 测试题2: */ if (!(b in window)) { var b = 1 } console.log(b) // undefined /* 测试题3: */ var c = 1 function c(c) { console.log(c) var c = 3 //与此行无关 } //相当于 /* function c(c) { console.log(c) var c = 3 //与此行无关 } var c c = 1 //此处的c函数变覆盖变成了c变量 */ c(2) // 报错 c is not a function
3、作用域与作用域链
Ⅰ-作用域
- 理解
- 就是一块"地盘", 一个代码段所在的区域
- 它是静态的(相对于上下文对象,上下文对象是动态的), 在编写代码时就确定了
- 分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了) -->(java语言也有)
- 作用
- 隔离变量,不同作用域下同名变量不会有冲突
/* //没块作用域 if(true) { var c = 3 } console.log(c) */ var a = 10, b = 20 function fn(x) { var a = 100, c = 300; console.log(\'fn()\', a, b, c, x) //100 20 300 10 function bar(x) { var a = 1000, d = 400 console.log(\'bar()\', a, b, c, d, x) } bar(100)//1000 20 300 400 100 bar(200)//1000 20 300 400 200 } fn(10)
Ⅱ-作用域与执行上下文的区别与联系
- 区别1:
- 全局作用域之外,每个函数都会创建自己的作用域,
作用域在函数定义时就已经确定了。而不是在函数调用时- 全局执行上下文环境是在全局作用域确定之后 并且在js代码马上执行之前 创建
- 函数执行上下文是在调用函数时 并且在函数体代码执行之前 创建
- 区别2:
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
- 联系:
- 执行上下文(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
Ⅲ-作用域链
- 理解
- 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的
- 查找一个变量的查找规则
- 1.在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
- 2.在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
- 3.再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
var a = 1 function fn1() { var b = 2 function fn2() { var c = 3 console.log(c) console.log(b) console.log(a) console.log(d) } fn2() } fn1()
Ⅳ-相关面试题
① 作用域在函数定义时就已经确定了。而不是在函数调用时
作用域1:
作用域在函数定义时就已经确定了。而不是在函数调用时var x = 10; function fn() { console.log(x); } function show(f) { var x = 20; f(); //相当于执行fn(),变量x不在fn函数的作用域内,所以往外找全局作用域x,全局作用域中有x = 10 } show(fn); //输出10
② 对象变量不能产生局部作用域
ES5里只有全局作用域和函数作用域,只有函数具有切割作用域的功能
var fn = function () { console.log(fn) } fn() var obj = { //对象变量不能产生局部作用域,所以会找到全局去,导致报错 fn2: function () { console.log(fn2) //console.log(this.fn2) } } obj.fn2()
4、闭包预备知识点梳理
在进入闭包之前,你要确保上面知识点你能掌握.你不确定 ? 噢好吧,那你就跟着我看下这部分梳理(如果懂得直接跳过即可)
Ⅰ- 举个栗子分析执行上下文
在讨论闭包之前,让我们看下下方的代码(建议先只看代码自己头脑风暴再看笔记中的描述),也算是对上面知识点的梳理回顾:
1: let a = 3 2: function addTwo(x) { 3: let ret = x + 2 4: return ret 5: } 6: let b = addTwo(a) 7: console.log(b)为了理解 JavaScript 引擎是如何工作的,让我们详细分析一下:
- 在第 1 行,我们在全局执行上下文中声明了一个新变量 a,并将赋值为 3。
- 接下来就变得棘手了,第 2 行到第 5 行实际上是在一起的。这里发生了什么?
- 我们在全局执行上下文中声明了一个名为
addTwo的新变量,我们给它分配了什么? -->一个函数定义。- 两个括号{}之间的任何内容都被分配给
addTwo,函数内部的代码没有被求值,没有被执行,只是存储在一个变量中以备将来使用。
- 现在我们在第 6 行。
- 它看起来很简单,但是这里有很多东西需要拆开分析。首先,我们在全局执行上下文中声明一个新变量,并将其标记为[
b],变量一经声明,其值即为 undefined。- 接下来,仍然在第 6 行,我们看到一个赋值操作符。我们准备给变量
b赋一个新值,接下来我们看到一个函数被调用。当您看到一个变量后面跟着一个圆括号(…)时,这就是调用函数的信号,接着,每个函数都返回一些东西(值、对象或 undefined),无论从函数返回什么,都将赋值给变量b。
- 但是首先我们需要调用标记为
addTwo的函数。JavaScript 将在其全局执行上下文内存中查找名为addTwo的变量。噢,它找到了一个,它是在[步骤 2(或第 2 - 5 行)中定义]的。变量[addTwo]包含一个函数定义。
- 注意:
变量[a]作为参数传递给函数。- JavaScript 在全局执行上下文内存中搜索变量
a,找到它,发现它的值是 3,并将数字 3 作为参数传递给函数,准备好执行函数。
- 现在执行上下文将切换,创建了一个新的本地执行上下文,我们将其命名为[“
addTwo 执行上下文”,执行上下文被推送到调用堆栈上。在 addTwo 执行上下文中,我们要做的第一件事是什么?
- 你可能会说,“在 addTwo 执行上下文中声明了一个新的变量 ret”,
这是不对的。正确的答案是:我们需要先看函数的参数。在 addTwo 执行上下文中声明一个新的变量[x],因为值 3 是作为参数传递的,所以变量 x 被赋值为 3。- 下一步才是在 addTwo 执行上下文中声明一个新的变量
ret。它的值被设置为 undefined(第三行)。
- 仍然是第 3 行,需要执行一个相加操作。
- 首先我们需要
x的值,JavaScript 会寻找一个变量x,它会首先在addTwo执行上下文中寻找,找到了一个值为 3。第二个操作数是数字 2。两个相加结果为 5 就被分配给变量ret。
- 第 4 行,我们返回变量
ret的内容,在 addTwo 执行上下文中查找,找到值为 5,返回,函数结束。- 第 4 - 5 行,函数结束。
addTwo 执行上下文被销毁,变量x和ret被消去了,它们已经不存在了。addTwo 执行上下文从调用堆栈中弹出,返回值返回给调用上下文,在这种情况下,调用上下文是全局执行上下文,因为函数addTwo是从全局执行上下文调用的。
- 现在我们继续第 4 步的内容,返回值 5 被分配给变量
b,此时实际上程序仍然在第 6 行(盗梦空间既视感