banner
banner
banner
NEWS LETTER

JS-函数高级

Scroll down

原型 | 原型链

原型 prototype

  • 定义:每个函数都有一个 prototype 属性, 它默认指向一个 Object 空对象(即称为: 原型对象)
  • 当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性执行该原型对象,指向该构造函数的原型对象,这个隐含的属性可以通过proto来访问。
  • 原型对象中有一个属性 constructor, 它指向函数对象
  • 理解:
    • 原型对象就相当于一个公共区域,所有同一个类的实例都可以访问到这个原型对象
    • 创建构造函数时,可以将对象共有的属性和方法,统一添加到构造函数的原型对象中,可以使每个对象都具有这些属性和方法,也不会影响到全局作用域
  • 函数的所有实例对象自动拥有原型中的属性(方法)
  • 示例
1
2
3
4
5
6
7
8
9
function MyClass() {} // 普通函数
MyClass.prototype.a = 123 // 向MyClass的原型中添加属性a
MyClass.prototype.sayHello = function () {
// 向MyClass的原型中添加一个方法
alert('hello')
}
var mc = new MyClass() // 创建一个构造函数
此时mc.__proto__ == MyClass.prototype,
可以从MyClass的原型上拿属性a和方法sayHello
  • hasOwnProperty():检查对象自身中是否含有该属性,只有当对象自身中含有属性时,才会返回 true

显示原型 | 隐式原型

  • 每个函数function都有一个特别的属性:prototype,即显式原型(属性)
  • 每个实例对象都有一个特别的属性:__proto__,可称为隐式原型(属性)
  • 对象的隐式原型的值为其对应构造函数的显式原型的值
    内存结构
  • 总结:
    • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
    • 实例对象的__proto__属性: 创建对象时自动添加的, 默认值构造函数的prototype属性值
    • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6 之前)
    • 原型对象即为当前实例对象的父对象

原型链(隐式原型链)

原型链

  • 定义:
    • 当访问一个对象的属性或方法时会先在自身属性中寻找,找到返回,
    • 如果没有则去原型对象prototype中寻找,如果没有则去原型的原型__proto__中寻找,直到找到Object对象的原型Object.prototype
    • 如果在 Object 原型中依然没有找到,则返回undefined,这样通过__proto__属性就形成了一个链的结构
  • 作用: 查找对象的属性(方法)
  • 注:
    • 原型对象 prototype 也是对象,所以它也有原型
    • Object对象的原型没有原型,Object.prototype.proto = null
    • Function 是通过 new 自己产生的实例,所有函数都是 Function 的实例(包含 Function)
    • 所有的实例对象都有__proto__属性, 它指向的就是原型对象,Function.proto===Function.prototype
    • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作
    • 读取对象的属性值时: 会自动到原型链中查找
    • 设置对象的属性值时: 不会查找原型链,如果当前对象中没有此属性, 直接添加此属性并设置其值
    • 方法一般定义在原型中,属性一般通过构造函数定义在对象本身上

执行上下文与执行上下文栈

变量提升与函数提升

  • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
  • 函数提升: 在函数定义语句之前, 就执行该函数(函数定义对象)
  • 先有变量提升, 再有函数提升

理解

  • 执行上下文: 由 js 引擎自动创建的对象, 包含对应作用域中的所有变量属性
  • 执行上下文栈: 用来管理产生的多个执行上下文
  • 分类:
    • 全局: window
    • 函数: 对程序员来说是透明的
  • 生命周期
    • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
    • 函数 : 调用函数时产生, 函数执行完时死亡

全局执行上下文

  • 在执行全局代码前将 window 确定为全局执行上下文
  • 对全局数据进行预处理
    • var 定义的全局变量==>undefined, 添加为 window 的属性
    • function 声明的全局函数==>赋值(fun), 添加为 window 的方法
    • this==>赋值(window)
  • 开始执行全局代码

函数执行上下文

  • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  • 对局部数据进行预处理
    • 形参变量==>赋值(对应实参)==>添加为执行上下文的属性
    • arguments==>赋值(实参列表的伪数组), 添加为执行上下文的属性
    • var 定义的局部变量==>undefined, 添加为执行上下文的属性
    • function 声明的函数 ==>赋值(fun), 添加为执行上下文的方法
    • this==>赋值(调用函数的对象, 如果没有指定就是 window )
  • 开始执行函数体代码

执行上下文栈

  • 在全局代码执行前, JS 引擎就会创建一个栈来存储管理所有的执行上下文对象
  • 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  • 在函数执行上下文创建后, 将其添加到栈中(压栈)
  • 在当前函数执行完后,将栈顶的对象移除(出栈)
  • 当所有的代码执行完后, 栈中只剩下 window

执行上下文创建和初始化的过程

  • 全局:
  • 在全局代码执行前最先创建一个全局执行上下文(window)
  • 收集一些全局变量, 并初始化
  • 将这些变量设置为 window 的属性
  • 函数:
  • 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
  • 收集一些局部变量, 并初始化
  • 将这些变量设置为执行上下文的属性

例题

执行上下文与执行上下文栈面试题

作用域与作用域链

理解:

  • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
  • 作用域链: 多个上下级关系(嵌套)的作用域形成的链, 它的方向是从下向上的(从内到外),查找变量时就是沿着作用域链来查找的
  • 分类:
    • 全局
    • 函数
    • js 没有块作用域(在 ES6 之前)
  • 作用
    • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
    • 作用域链: 查找变量

区别作用域与执行上下文

  • 区别
    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
    • 全局执行上下文环境是在全局作用域确定之后, js 代码马上执行之前创建
    • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
    • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
    • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
  • 联系
    • 执行上下文(对象)是从属于所在的作用域
    • 全局上下文环境==>全局作用域
    • 函数上下文环境==>对应的函数使用域

作用域链查找一个变量的规则

  • 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入上一层作用域查找
  • 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入上一层作用域查找
  • 以此类推, 直到全局作用域, 如果还找不到就抛出找不到的异常

闭包

理解

  • 嵌套的内部函数引用 了外部函数的变量时就产生了闭包
  • 通过 chrome 工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
  • 在问题的上下文中,闭包被用于保持对定时器变量的引用,即使创建闭包的函数已经返回。这使得取消函数能够访问并修改定时器变量,有效地取消了延迟函数的执行。

产生闭包的条件?

  • 函数嵌套
  • 内部函数引用了外部函数的数据(变量/函数)
  • 执行外部函数

常见的闭包

  • 将函数作为另一个函数的返回值
  • 将函数作为实参传递给另一个函数调用
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 将函数作为另一个函数的返回值
function fn1() {
var a = 2 //此时闭包产生了,因为函数提升
function fn2() {
a++
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
结果:从以上函数克制,该函数产生多少个闭包,看调用了几次外部函数
// 2. 将函数的实参传递给内部的函数调用
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay('atguigu', 2000)
结论:有产生闭包,一外部函数里面嵌套内部函数,二内部函数调用外部函数的值msg,如果没有msg就不会产生闭包

作用:

  • 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  • 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
  • 注:
    • 函数执行完后, 函数内部声明的局部变量一般是不存在, 存在于闭包中的变量才可能存在
    • 在函数外部不能直接访问函数内部的局部变量, 但我们可以通过闭包让外部操作

生命周期

  • 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
  • 死亡: 在嵌套的内部函数成为垃圾对象时(让包含闭包的函数对象=null)

闭包应用

  • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为(对象或函数)
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
  • 循环遍历加监听
  • JS 框架(jQuery)大量使用了闭包

缺点

  • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  • 可能导致内存泄露
  • 解决:
    • 及时释放: f = null; //让内部函数对象成为垃圾对象

内存溢出与内存泄露

内存溢出

  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误

内存泄露

  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1. 内存溢出
var obj = {}
for (var i = 0; i < 10000; i++) {
obj[i] = new Array(10000000)
console.log('-----')
}
// 2. 内存泄露
// 意外的全局变量
function fn() {
a = new Array(10000000)
console.log(a)
}
fn()
// 没有及时清理的计时器或回调函数
var intervalId = setInterval(function () {
//启动循环定时器后不清理
console.log('----')
}, 1000)
// 闭包
function fn1() {
var a = 4
function fn2() {
console.log(++a)
}
return fn2
}
var f = fn1()
f()
其他文章
cover
JS-线程机制与事件机制
  • 24/10/31
  • 11:05
  • JavaScript
cover
JS-对象高级
  • 24/10/31
  • 11:05
  • JavaScript
目录导航 置顶
  1. 1. 原型 | 原型链
    1. 1.1. 原型 prototype
    2. 1.2. 显示原型 | 隐式原型
    3. 1.3. 原型链(隐式原型链)
  2. 2. 执行上下文与执行上下文栈
    1. 2.1. 变量提升与函数提升
    2. 2.2. 理解
    3. 2.3. 全局执行上下文
    4. 2.4. 函数执行上下文
    5. 2.5. 执行上下文栈
    6. 2.6. 执行上下文创建和初始化的过程
    7. 2.7. 例题
  3. 3. 作用域与作用域链
    1. 3.1. 理解:
    2. 3.2. 区别作用域与执行上下文
    3. 3.3. 作用域链查找一个变量的规则
  4. 4. 闭包
    1. 4.1. 理解
    2. 4.2. 产生闭包的条件?
    3. 4.3. 常见的闭包
    4. 4.4. 作用:
    5. 4.5. 生命周期
    6. 4.6. 闭包应用
    7. 4.7. 缺点
  5. 5. 内存溢出与内存泄露
    1. 5.1. 内存溢出
    2. 5.2. 内存泄露
请输入关键词进行搜索