二、函数高级
1、原型与原型链
1.1、prototype原型对象(显示原型对象)
- 每个函数都有一个prototype属性, 它默认指向一个拥有constructor属性对象,该对象被称为原型对象,也被称作为显式原型对象。
1.2、constructor(构造器)
原型对象中有一个属性constructor, 它指向函数对象
给原型对象添加属性(一般都是方法),函数的所有实例对象自动拥有原型中的属性(方法)
1.2、__proto__
(隐式原型对象)
每个实例对象都有一个
__proto__
,可称为隐式原型
- 对象的隐式原型的值为其对应构造函数的显式原型的值
1.3、原型链
- 所有的实例对象都有
__proto__
属性, 它指向的就是原型对象- 这样通过
__proto__
属性就形成了一个链的结构---->原型链- 当查找对象内部的属性/方法时,
js
引擎自动沿着这个原型链查找- 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作
1.4、构造函数/原型/实例对象的关系
所有函数对象的
__proto__
属性值都相等
- 所有函数都是Function的实例, 包含它自己
1.5、原型链属性问题
读取对象的属性值时: 会自动到原型链中查找。
设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值。
方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上。
1.6、instanceof
介绍及手写实现
instanceof
是如何判断的?
- 表达式: A
instanceof
B- 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
Function是通过new自己产生的实例
- 手写
instanceof
1.7、面试题
测试题1
var A = function() { } A.prototype.n = 1 var b = new A() A.prototype = { n: 2, m: 3 } var c = new A() console.log(b.n, b.m, c.n, c.m)
测试题2
var F = function(){}; Object.prototype.a = function(){ console.log('a()') }; Function.prototype.b = function(){ console.log('b()') }; var f = new F(); f.a() f.b() F.a() F.b()
2、执行上下文和执行上下文栈
2.1、变量提升与函数提升
变量声明提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值: undefined
函数声明提升
- 通过function声明的函数, 在之前就可以直接调用
- 值: 函数定义(对象)
问题: 变量提升和函数提升是如何产生的?
/* 面试题: 输出什么? */ var a = 4 function fn () { console.log(a) var a = 5 } fn()
2.2、执行上下文
JS
引擎并不是一行行的解析和执行代码,而是一段段的去分析和执行,当执行一段代码时,先开始预处理,比如声明提升和函数提升在执行某段
JS
代码的时候,会进行一个准备工作,这个准备工作用专业的说法 叫“执行上下文”,其实执行上下文也是在内存中开辟的一个空间js可执行的代码分为3种类型, 全局代码 、 函数代码 、eval代码(忽略)
每执行一段代码,都会创建相对应的执行上下文,在脚本中可能存在多个执行上下文
因为有太多的执行上下文, JS创建了一个执行上下文栈(stack) 用来管理执行上下文
当js开始解析程序的时候,最先遇到的全局代码,此时向执行上下文栈中 压入一个全局执行上下文,全局的一定是在整体运行结束以后才被清空
当执行一个函数的时候 会创建一个函数的执行上下文,并压入到执行上下文栈中,只要函数执行完成,会将函数从栈里弹出
每个执行上下文 都有三个重要属性:
- 变量对象(
VO
)- 作用域链
- this
2.3、变量对象
- 变量对象是
ECMAScript
规范术语。在一个执行上下文中,变量对象才被激活,只有激活的变量对象,其各种属性才能被访问- 变量对象是与执行上下文相关的数据作用域,储存了在上下文中定义的变量和函数声明
2.3.1、全局上下文的变量对象
window
是预定义对象,作为JS
全局函数和全局属性的占位符(全局的变量和函数就是window对象属性和方法)- 全局执行上下文的变量对象其实就是全局对象window
2.3.2、函数上下文的变量对象
进入执行上下文 不会立马执行代码,只进行分析。此时首先第一步,变量对象包括了函数所有的形参和实参
检查所有声明的函数,由名称和对应值 组成一个变量对象的属性 被创建。如果变量对象已经有相同名字的属性,则完全替换
检查所有的声明的变量,创建键值对儿
变成变量对象的属性,如果变量名和已经声明的形参或函数相同,则变量声明不会干扰已经存在的这类属性
2.4、作用域
2.4.1、作用域
理解
- 就是一块"地盘", 一个代码段所在的区域
- 它是静态的(相对于上下文对象), 在编写代码时就确定了
分类
- 全局作用域
- 函数作用域
- 没有块作用域(
ES6
有了)作用
- 隔离变量,不同作用域下同名变量不会有冲突
2.4.2、作用域的执行上下文
区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
- 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
区别2
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放
联系
- 上下文环境(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
2.4.3、作用域链
- 在函数创建的时候创建一个包含全局变量对象的作用域链( scope chain),储存在内部[[Scope]]属性中。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
- 函数执行的时候会创建一个执行环境,通过复制[[Scope]]属性中的对象,构建执行环境的作用域链,并把自己的活动对象推入该作用域链的前端以此形成完整的作用域链。
- 作用域链的前端,始终都是当前执行的代码所在环境的变量对象。
- 全局执行环境的变量对象始终都是作用域链中的最后一个对象。
- 标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)
2.4.4、面试题
var x = 10; function fn() { console.log(x); } function show(f) { var x = 20; f(); } show(fn); var fn = function () { console.log(fn) } fn() var obj = { fn2: function () { console.log(fn2) } } obj.fn2() var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f(); } console.log(checkscope()); var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } console.log(checkscope()());
3、闭包
3.1、引入
点击某个按钮, 提示"点击的是第n个按钮"
3.2、理解闭包
如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
闭包到底是什么?
- 使用chrome调试查看
- 理解一: 闭包是嵌套的内部函数(绝大部分人)
- 理解二: 包含被引用变量(函数)的对象(极少数人)
- 注意: 闭包存在于嵌套的内部函数中
产生闭包的条件?
函数嵌套
内部函数引用了外部函数的数据(变量/函数)
执行外部函数
常见闭包
将函数作为另一个函数的返回值
将函数作为实参传递给另一个函数调用
闭包的作用
使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
让函数外部可以操作(读写)到函数内部的数据(变量/函数)
闭包的生命周期
产生: 在嵌套内部函数定义执行完(创建函数对象)时就产生了(不是在调用)
死亡: 在嵌套的内部函数成为垃圾对象时
3.3、闭包的应用
定义
JS
模块
- 具有特定功能的
js
文件- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个方法的对象或函数
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
3.4、 闭包的缺点及解决
- 缺点
- 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
- 容易造成内存泄露
- 解决
- 能不用闭包就不用
- 及时释放
3.5、 面试题
面试题一
var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { return function () { return this.name; }; } }; console.log(object.getNameFunc()()); //?
面试题二
var name2 = "The Window"; var object2 = { name2: "My Object", getNameFunc: function () { var that = this; return function () { return that.name2; }; } }; console.log(object2.getNameFunc()()); //?
面试题三
function fun(n, o) { console.log(o) return { fun: function (m) { return fun(m, n) } } } var a = fun(0) a.fun(1) a.fun(2) a.fun(3) //undefined,?,?,? var b = fun(0).fun(1).fun(2).fun(3) //undefined,?,?,? var c = fun(0).fun(1) c.fun(2) c.fun(3) //undefined,?,?,?