chenguanqin

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

实例

image-20211125230050795

1. undefined与null的区别?

image-20211124235958476

2. 什么时候给变量赋值为null呢?

初始赋值,表明将要赋值为对象;结束前,让对象成为垃圾对象(被垃圾回收器回收)。初始化赋值:将要作为引用变量使用, 但对象还没有确定。结束时:将变量指向的对象成为垃圾对象。

var a = null // a将指向一个对象,但对象此时还没有确定
....
a = null // 让a指向的对象成为垃圾对象

image-20211125000246282

3. 严格区别变量类型与数据类型?

js的变量本身是没有类型的,变量的类型实际上是变量内存中数据的类型(js是弱类型的语言)。var a; 判断变量类型,实际上 是判断值的类型。

数据的类型(数据对象):

  • 基本类型
  • 对象类型

变量的类型(变量内存值的类型):

  • 基本类型:保存基本类型的数据(保存基本类型数据的变量)。
  • 引用类型:保存对象地址值(保存对象地址值的变量)。

2

练习

<!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. 内存,数据, 变量三者之间的关系
  * 内存是一个容器, 用来存储程序运行需要操作的数据
  * 变量是内存的标识, 我们通过变量找到对应的内存, 进而操作(读/写)内存中的数据

image-20211125230158823

问题1:变量在内存中保存的值

var a = xxx(赋值操作),a内存中到底保存的是什么?

image-20211125004318057

问题2:关于引用变量赋值

关于引用变量赋值问题

  • 2个引用变量指向同一个对象(保存的内容是同一个对象的地址值),通过一个引用变量修改对象内部数据,另一个引用变量也看得见(看见的是修改之后的数据)。
  • 2个引用变量指向同一个对象,让一个引用变量指向另一个对象,另一个引用变量还是指向原来的对象。

5

image-20211125004800747

<!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>

image-20211125230235583

问题3:数据传递问题

在js调用函数时传递变量参数时,是值传递还是引用传递?

理解1:都是值(基本/地址值)传递。
理解2:可能是值传递,也可能是引用传递(地址值)。
只有值传递,没有引用传递,传递的都是变量的值,只是这个值可能是基本数据,也可能是地址(引用)数据。
如果后一种看成是引用传递,那就值传递和引用传递都可以有。

因为函数里的a是函数内的局部变量,换成this.a = a + 1,这时候this.a就是你最开始定义的a。

假设形参是x,调用函数传参时,发生了两件事:1、读取全局变量a的值;2、将值赋值给x ,然后执行函数内代码。

括号里的a是全局里的a的值copy给他的。

image-20211125005949258

问题4:JS引擎如何管理内存?

1. 内存生命周期
分配小内存空间,得到它的使用权(分配需要的内存)。
存储数据,可以反复进行操作(使用分配到的内存)。
释放小内存空间(不需要时将其释放/归还)。

2. 释放内存
局部变量:函数执行完 自动释放(为执行函数分配的栈空间内存)。
对象:成为垃圾对象==>垃圾回收器回收(存储对象的堆空间内存:当内存没有引用指向时,对象成为垃圾对象,垃圾回收器后面就会回收释放此内存。)

image-20211125010405863

问题5:什么是对象

  • 多个数据的封装体(集合体)。

  • 用于保存多个数据的容器。

  • 一个对象代表现实中的一个事物(代表现实中的某个事物,是该事物在编程中的抽象)。

问题6:为什么要用对象?

  • 便于对多个数据进行统一管理。

问题7:对象的组成

对象中的函数叫方法;对象中的字符串、数字等等叫属性。

  • 属性:属性名(字符串)和属性值(任意)组成。(代表现实事物的状态数据;属性名都是字符串类型,属性值是任意类型)
  • 方法:一种特别的属性(属性值是函数)。(代表现实事物的行为数据)

问题8:如何访问对象内部数据?

  • 对象.属性名: 编码简单, 有时不能用

  • 对象[\'属性名\']: 编码麻烦, 能通用

image-20211125221602637

问题9:什么时候必须使用[\'属性名\']的方式?

  1. 属性名包含特殊字符: - 空格

  2. 属性名不确定

6

image-20211125220606475

函数

1.什么是函数?

  • 实现特定功能的n条语句的封装体;
  • 只有函数是可执行的,其它类型的数据是不能执行(不用管);
  • 函数也是对象。

7

image-20211125230315460

2. 为什么要用函数?

  • 提高代码复用

  • 便于阅读交流

3.如何定义函数?

  • 函数声明:整体会被提升到当前作用域顶部。
  • 表达式:也会提升到顶部,但是只有变量名提升。

image-20211125223851927

4. 如何调用(执行)函数?

  • test():直接调用
  • obj.test():通过对象调用
  • new test():new 调用
  • test.call/apply(obj):临时让test成为obj的方法进行调用

10

5.什么函数才是回调函数?

  1. 用户定义的
  2. 用户没有直接调用
  3. 但最终它执行了(在某个时刻或某个条件下 在特定条件或时刻)

6.常见的回调函数?

  • dom事件回调函数 ==> 发生事件的dom元素
  • 定时器回调函数 ==> window
    • 超时定时器 setTimeOut(function(){},延迟时间)
    • 循环定时器 setInterval(function(){},定时)
  • 后面学:ajax请求回调函数、生命周期回调函数

image-20211125230348887

7.匿名函数(IIFE)

1. 理解

  • 全称:Immediately-Invoked Function Expression,立即调用函数表达式
  • 别名:匿名函数自调用

2. 作用

  • 隐藏内部实现
  • 不会污染外部(全局)命名空间
  • 用它来编码js模块

匿名函数自调用,并且只能执行一次。匿名函数得是一个表达式,不能是函数声明。

image-20211125231114328

    (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

关于语句分号

  1. js一条语句的后面可以不加分号
  2. 是否加分号是编码风格问题, 没有应该不应该,只有你自己喜欢不喜欢
  3. 在下面2种情况下不加分号会有问题
  • 小括号开头的前一条语句
  • 中方括号开头的前一条语句
  1. 解决办法: 在行首加分号
  2. 强有力的例子: vue.js库
  3. 知乎热议: https://www.zhihu.com/question/20298345

二、函数高级

1、原型与原型链

Ⅰ-原型 [prototype]

  1. 函数的prototype属性
  • 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
  • 原型对象中有一个属性constructor, 它指向函数对象

image-20210714201049312

  1. 给原型对象添加属性(一般都是方法)
  • 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
  1. 代码示例
 // 每个函数都有一个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()

Ⅱ-显式原型与隐式原型

  1. 每个函数function都有一个prototype,即显式原型(属性)

  2. 每个实例对象都有一个[__ proto __],可称为隐式原型(属性)

  3. 对象的隐式原型的值为其对应构造函数的显式原型的值

  4. 内存结构

  5. 总结:

  • 函数的[prototype]属性: 在定义函数时自动添加的, 默认值是一个空Object对象
  • 对象的[__ proto __]属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
  1. 代码示例:
  //定义构造函数
  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()

Ⅲ-原型链

原型链

  1. 原型链
  • 访问一个对象的属性时,
  • 先在自身属性中查找,找到返回
  • 如果没有, 再沿着[__ proto __]这条链向上查找, 找到返回
  • 如果最终没找到, 返回undefined
  • image-20210714210912653
  • 别名: 隐式原型链
  • 作用: 查找对象的属性(方法)

构造函数/原型/实例对象的关系(图解)

var o1 = new Object();
  var o2 = {};

image-20210714212928432

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__都是一样的

image-20210714212945164

var Function = new Function(); //Function函数自己创建自己,Function既是示例对象也是构造函数
Function.__proto__ = Function.prototype  

ps:所有函数的[__ proto __]都是一样的

image-20211127165915255

属性问题

  • 读取对象的属性值时: 会自动到原型链中查找

  • 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值

  • 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上

  • 代码示例

 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

image-20211127180617423

  1. instanceof是如何判断的?
  • 表达式: A instanceof B
  • 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
  1. 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)

结果图例

image-20210723173855550

2、执行上下文与执行上下文栈

当代码在 JavaScript 中运行时,执行代码的环境非常重要,并将概括为以下几点:

全局代码——第一次执行代码的默认环境。

函数代码——当执行流进入函数体时。

(…) —— 我们当作 执行上下文 是当前代码执行的一个环境与范围。

换句话说,当我们启动程序时,我们从全局执行上下文中开始。一些变量是在全局执行上下文中声明的。我们称之为全局变量。当程序调用一个函数时,会发生什么?

以下几个步骤:

  • JavaScript 创建一个新的执行上下文,我们叫作本地执行上下文。
  • 这个本地执行上下文将有它自己的一组变量,这些变量将是这个执行上下文的本地变量。
  • 新的执行上下文被推到到执行堆栈中。可以将执行堆栈看作是一种保存程序在其执行中的位置的容器。

函数什么时候结束?当它遇到一个 return 语句或一个结束括号}。

当一个函数结束时,会发生以下情况:

  • 这个本地执行上下文从执行堆栈中弹出。
  • 函数将返回值返回调用上下文。调用上下文是调用这个本地的执行上下文,它可以是全局执行上下文,也可以是另外一个本地的执行上下文。这取决于调用执行上下文来处理此时的返回值,返回的值可以是一个对象、一个数组、一个函数、一个布尔值等等,如果函数没有 return 语句,则返回 undefined。
  • 这个本地执行上下文被销毁,销毁是很重要,这个本地执行上下文中声明的所有变量都将被删除,不在有变量,这个就是为什么 称为本地执行上下文中自有的变量。

此图出于CSDN的Free Joe

image-20210723181727823

Ⅰ-变量提升与函数提升

  1. 变量声明提升
  • 通过var定义(声明)的变量, 在定义语句之前就可以访问到
  • 值: undefined
  1. 函数声明提升
  • 通过function声明的函数, 在之前就可以直接调用
  • 值: 函数定义(对象)
  1. 引出一个问题: 变量提升和函数提升是如何产生的?
/*
面试题 : 输出 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()\') }

Ⅱ-执行上下文

  1. 代码分类(位置)
  • 全局代码
  • 函数(局部)代码
  1. 全局执行上下文
  • 在执行全局代码前将window确定为全局执行上下文
  • 对全局数据进行预处理
  • var定义的全局变量==>undefined, 添加为window的属性
  • function声明的全局函数==>赋值(fun), 添加为window的方法
  • this==>赋值(window)
  • 开始执行全局代码
  1. 函数执行上下文
  • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  • 对局部数据进行预处理
  • 形参变量>赋值(实参)>添加为执行上下文的属性
  • arguments==>赋值(实参列表), 添加为执行上下文的属性 -->不懂的同学看这里
  • var定义的局部变量==>undefined, 添加为执行上下文的属性
  • function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
  • this==>赋值(调用函数的对象)
  • 开始执行函数体代码

Ⅲ-执行上下文栈

  1. 在全局代码执行前, JS引擎就会创建一个栈空间来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)-->所以栈底百分百是[window]
  3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
  4. 在当前函数执行完后,将栈顶的对象移除(出栈)
  5. 当所有的代码执行完后, 栈中只剩下window
  6. 上下文栈数==函数调用数+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函数执行上下文

image-20210723183046182

此处用一个动态图来展示:

执行栈与事件队列

举个栗子:

//栗子
<!--
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、作用域与作用域链

Ⅰ-作用域

  1. 理解
  • 就是一块"地盘", 一个代码段所在的区域
  • 它是静态的(相对于上下文对象,上下文对象是动态的), 在编写代码时就确定了
  1. 分类
  • 全局作用域
  • 函数作用域
  • 没有块作用域(ES6有了) -->(java语言也有)
  1. 作用
  • 隔离变量,不同作用域下同名变量不会有冲突
/*  //没块作用域
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. 区别1:
  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后 并且在js代码马上执行之前 创建
  • 函数执行上下文是在调用函数时 并且在函数体代码执行之前 创建
  1. 区别2:
  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
  1. 联系:
  • 执行上下文(对象)是从属于所在的作用域
  • 全局上下文环境==>全局作用域
  • 函数上下文环境==>对应的函数使用域

image-20210727141319410

Ⅲ-作用域链

  1. 理解
  • 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
  • 查找变量时就是沿着作用域链来查找的
  1. 查找一个变量的查找规则
  • 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

image-20210726192714660

② 对象变量不能产生局部作用域

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. 在第 1 行,我们在全局执行上下文中声明了一个新变量 a,并将赋值为 3。
  2. 接下来就变得棘手了,第 2 行到第 5 行实际上是在一起的。这里发生了什么?
  • 我们在全局执行上下文中声明了一个名为addTwo的新变量,我们给它分配了什么? -->一个函数定义
  • 两个括号{}之间的任何内容都被分配给addTwo函数内部的代码没有被求值,没有被执行,只是存储在一个变量中以备将来使用
  1. 现在我们在第 6 行。
  • 它看起来很简单,但是这里有很多东西需要拆开分析。首先,我们在全局执行上下文中声明一个新变量,并将其标记为[b],变量一经声明,其值即为 undefined
  • 接下来,仍然在第 6 行,我们看到一个赋值操作符。我们准备给变量b赋一个新值,接下来我们看到一个函数被调用。当您看到一个变量后面跟着一个圆括号(…)时,这就是调用函数的信号,接着,每个函数都返回一些东西(值、对象或 undefined),无论从函数返回什么,都将赋值给变量b
  1. 但是首先我们需要调用标记为addTwo的函数。JavaScript 将在其全局执行上下文内存中查找名为addTwo的变量。噢,它找到了一个,它是在[步骤 2(或第 2 - 5 行)中定义]的。变量[addTwo]包含一个函数定义。
  • 注意:变量[a]作为参数传递给函数
  • JavaScript 在全局执行上下文内存中搜索变量a,找到它,发现它的值是 3,并将数字 3 作为参数传递给函数,准备好执行函数。
  1. 现在执行上下文将切换,创建了一个新的本地执行上下文,我们将其命名为[“addTwo 执行上下文”,执行上下文被推送到调用堆栈上。在 addTwo 执行上下文中,我们要做的第一件事是什么?
  • 你可能会说,“在 addTwo 执行上下文中声明了一个新的变量 ret”,这是不对的
  • 正确的答案是:我们需要先看函数的参数。在 addTwo 执行上下文中声明一个新的变量[x],因为值 3 是作为参数传递的,所以变量 x 被赋值为 3。
  • 下一步才是在 addTwo 执行上下文中声明一个新的变量ret。它的值被设置为 undefined(第三行)。
  1. 仍然是第 3 行,需要执行一个相加操作。
  • 首先我们需要x的值,JavaScript 会寻找一个变量x,它会首先在addTwo执行上下文中寻找,找到了一个值为 3。第二个操作数是数字 2。两个相加结果为 5 就被分配给变量ret
  1. 第 4 行,我们返回变量ret的内容,在 addTwo 执行上下文中查找,找到值为 5,返回,函数结束。
  2. 第 4 - 5 行,函数结束。
  • addTwo 执行上下文被销毁,变量xret被消去了,它们已经不存在了。addTwo 执行上下文从调用堆栈中弹出,返回值返回给调用上下文,在这种情况下,调用上下文是全局执行上下文,因为函数addTwo 是从全局执行上下文调用的。
  1. 现在我们继续第 4 步的内容,返回值 5 被分配给变量b此时实际上程序仍然在第 6 行(盗梦空间既视感

分类:

技术点:

相关文章:

  • 2021-12-16
  • 2021-12-05
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-08-14
  • 2022-12-23
  • 2021-12-04
  • 2021-07-03
  • 2021-06-01
相关资源
相似解决方案