原型 | 原型链
原型 prototype
- 定义:每个函数都有一个 prototype 属性, 它默认指向一个 Object 空对象(即称为: 原型对象)
- 当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性执行该原型对象,指向该构造函数的原型对象,这个隐含的属性可以通过proto来访问。
- 原型对象中有一个属性 constructor, 它指向函数对象
- 理解:
- 原型对象就相当于一个公共区域,所有同一个类的实例都可以访问到这个原型对象
- 创建构造函数时,可以将对象共有的属性和方法,统一添加到构造函数的原型对象中,可以使每个对象都具有这些属性和方法,也不会影响到全局作用域
- 函数的所有实例对象自动拥有原型中的属性(方法)
- 示例
1 | function MyClass() {} // 普通函数 |
- 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 | // 1. 将函数作为另一个函数的返回值 |
作用:
- 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
- 注:
- 函数执行完后, 函数内部声明的局部变量一般是不存在, 存在于闭包中的变量才可能存在
- 在函数外部不能直接访问函数内部的局部变量, 但我们可以通过闭包让外部操作它
生命周期
- 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡: 在嵌套的内部函数成为垃圾对象时(让包含闭包的函数对象=null)
闭包应用
- 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为(对象或函数)
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
- 循环遍历加监听
- JS 框架(jQuery)大量使用了闭包
缺点
- 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
- 可能导致内存泄露
- 解决:
- 及时释放: f = null; //让内部函数对象成为垃圾对象
内存溢出与内存泄露
内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
- 示例:
1 | // 1. 内存溢出 |