函数高级
原型(prototype)
- 函数的prototype属性(图)
- 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
- 原型对象中有一个属性constructor, 它指向函数对象
- 给原型对象添加属性(一般都是方法)
- 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
显式原型与隐式原型
- 每个函数function都有一个prototype,即显式原型(属性)
- 每个实例对象都有一个proto,可称为隐式原型(属性)
- 对象的隐式原型的值为其对应构造函数的显式原型的值
- 内存结构(图)
- 总结:
- 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
- 对象的proto属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
-
程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
显式原型与隐式原型.png
原型链
- 原型链(图解)
原型链的本质是隐式原型链,因为原型链的形成是真正是靠proto 而非prototype
对象可以分为普通对象Object和函数对象Function()
- 访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着proto这条链向上查找, 找到返回
- 如果最终没找到, 返回undefined
- 别名: 隐式原型链
- 作用: 查找对象的属性(方法)
- 构造函数/原型/实体对象的关系(图解)
三个版本的原型链图
测试题1
*/
function A () {
}
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) //1,undefined,2,3
/*
测试题2
*/
function F (){}
Object.prototype.a = function(){
console.log('a()')
}
Function.prototype.b = function(){
console.log('b()')
}
var f = new F()
f.a() // a()
f.b() //报错,f.b is not a function
F.a() //a()
F.b() //b()
原型链的属性问题
- 读取对象的属性值时: 会自动到原型链中查找
- 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
- 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
constructor属性
var p = new Person();
//方法才有prototype,普通对象无prototype
console.log(Person.prototype); // Object{}
console.log(p.prototype); // undifined
//任何对象都是有构造函数constructor,由构造函数创建的对象也可以获得构造函数的引用
//此处只是打印下列对象的构造函数是什么。
console.log(p.constructor); //function Person(){}
console.log(Person.constructor); //function Function(){}
console.log({}.constructor); // function Object(){}
console.log(Object.constructor); // function Function() {}
console.log([].constructor); //function Array(){}
instanceof
- instanceof是如何判断的?
- 表达式: A instanceof B
- 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
- Function是通过new自己产生的实例,所以有Function__proto__ === Function.prototype
案例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
参考链接:最详尽的 JS 原型与原型链终极详解,没有「可能是」。(一) - 简书 (jianshu.com)
执行上下文和执行上下文栈
变量声明和函数提升
- 变量声明提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值: undefined
- 函数声明提升
- 通过function声明的函数, 在之前就可以直接调用
- 值: 函数定义(对象)
先执行变量提升, 再执行函数提升
若使用var f = function(){}则不会产生函数提升,这种情况相当于是变量f的提前声明
执行上下文
- 代码分类(位置)
- 全局代码
- 函数(局部)代码
- 全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
- var定义的全局变量==>undefined, 添加为window的属性
- function声明的全局函数==>赋值(fun), 添加为window的方法
- this==>赋值(window)
- 开始执行全局代码
- 函数执行上下文
- 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
- 对局部数据进行预处理
- 形参变量==>赋值(实参)==>添加为执行上下文的属性
- arguments==>赋值(实参列表), 添加为执行上下文的属性
- var定义的局部变量==>undefined, 添加为执行上下文的属性
- function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
- this==>赋值(调用函数的对象)
- 开始执行函数体代码
执行上下文栈
- 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
- 在函数执行上下文创建后, 将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后, 栈中只剩下window
练习
测试题1:
*/
function a() {}
var a
console.log(typeof a) // 'function'
(说明先执行变量提升, 再执行函数提升)
/*
测试题2:
/
if (!(b in window)) {
var b = 1
}
console.log(b) // undefined
(b是全局变量,会提前声明作为window的属性,由于if语句没有执行,因此b的值为undefined)
/
测试题3:
*/
var c = 1
function c(c) {
console.log(c)
var c = 3
}
//c(2) // 报错
作用域与作用域链
作用域
- 理解
- 就是一块"地盘", 一个代码段所在的区域
- 它是静态的(相对于上下文对象), 在编写代码时就确定了
- 分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了)
- 作用
- 隔离变量,不同作用域下同名变量不会有冲突
作用域与执行上下文
- 区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
- 函数执行上下文是在调用函数时, 函数体代码执行之前创建
- 区别2
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
- 联系
- 执行上下文(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
作用域链
- 理解
- 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的
- 查找一个变量的查找规则
- 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入上一级作用域
- 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则继续进入上一级作用域查找
*直到全局作用域, 如果还找不到就抛出找不到的异常
测试题
题1.
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
f();
}
show(fn); //10
题2
var fn = function () {
console.log(fn)
}
fn() //输出fn函数
var a = 2
var obj = {
a:1, // 注意:对象的各属性间用逗号隔开,使用=会报错
fn2:function () {
//console.log(fn2) //报错,全局作用域中不存在fn2变量
console.log(this) // 输出obj
console.log(this.fn2) // 输出fn2函数
console.log(a) // 2
console.log(this.a) // 1(若不显示指定,还是在全局作用域中寻找)
}
}
obj.fn2()
参考链接:JS中对象的作用域?或者是对象的属性的一些疑问? - SegmentFault 思否
闭包
什么是闭包
- 如何产生闭包?
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
- 闭包到底是什么?
- 使用chrome调试查看(新版chrome浏览器需要手动return 子函数才能看到)
- 理解一: 闭包是嵌套的内部函数(绝大部分人)
- 理解二: 包含被引用变量(函数)的对象(极少数人)
- 注意: 闭包存在于嵌套的内部函数中
- 产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
- 常见使用闭包情况
将函数作为另一个函数的返回值
-
将函数作为实参传递给另一个函数调用
// 1. 将函数作为另一个函数的返回值 function fn1() { var a = 2 function fn2() { a++ console.log(a) } return fn2 } var f = fn1() f() // 3 f() // 4 // 2. 将函数作为实参传递给另一个函数调用 function showDelay(msg, time) { setTimeout(function () { alert(msg) }, time) } showDelay('atguigu', 2000)
闭包的作用
- 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
问题:
- 函数执行完后, 函数内部声明的局部变量是否还存在? 一般是不存在, 存在于闭中的变量才可能存在
- 在函数外部能直接访问函数内部的局部变量吗? 不能, 但我们可以通过闭包让外部操作它
闭包的生命周期
产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
-
死亡: 在嵌套的内部函数成为垃圾对象时
function fn1() { //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了) var a = 2 function fn2 () { a++ console.log(a) } return fn2 } var f = fn1() f() // 3 f() // 4 f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
闭包的应用
闭包的应用2 : 定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包信n个方法的对象或函数
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
闭包的缺点
- 缺点
- 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
- 容易造成内存泄露
- 解决
- 能不用闭包就不用
- 及时释放 (设置为Null)
练习题
题1
//代码片段一
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); // the window
//代码片段二
var name2 = "The Window";
var object2 = {
name2 : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name2;
};
}
};
alert(object2.getNameFunc()()); // my object
题2
<script type="text/javascript">
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 0 0 0
var b = fun(0).fun(1).fun(2).fun(3);// undefined 0 1 2
var c = fun(0).fun(1);c.fun(2);c.fun(3);// undefined 0 1 1
</script>