函数的作用及用法

什么是函数?

  • 具有特定功能的 n 条语句的封装体
  • 只有函数是可执行的,其它类型的数据是不可执行的
  • 函数也是对象,instanceof Object===true

为什么要用函数?

  • 提高代码复用
  • 便于阅读和交流

如何定义函数?

  • 函数声明
  • 表达式

调用(执行)函数

  • 直接调用:test()
  • 构造函数:new test()
  • 对象方法:obj.test()
  • 间接调用其他对象方法:test.call/apply(obj)

回调函数

函数定义了但没有直接调用,但最终在特定条件下执行了

常见的回调函数:

  • DOM 事件函数
  • 定时器函数
  • ajax 回调函数
  • 生命周期回调函数

匿名函数

专业术语为: IIFE (Immediately Invoked Function Expression) 立即调用函数表达式

1
2
3
;(function (w, obj) {
//实现代码
})(window, obj)

作用:

  • 隐藏内部实现
  • 不污染外部命名空间

函数中的 this

  • 显式指定谁:obj.xxx()
  • 通过 call/apply 指定谁调用:xxx.call(obj)
  • 不指定谁调用:xxx() : window
  • 回调函数:看背后是通过谁来调用的,window/其它

原型与原型链

原型 prototype

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

给原型对象添加属性(一般都是方法),可以使函数的所有实例对象自动拥有原型中的属性(方法)。因此原型一般只在创建实例对象时起作用。

显式原型和隐式原型

每个函数 function 都有一个prototype属性,即显式原型;每个实例对象都有一个__proto__属性,可称为隐式原型。对象的隐式原型的值为其对应构造函数的显式原型的值。

显式原型和隐式原型

显式原型和隐式原型

函数的 prototype 属性在定义函数时自动添加,默认值是一个空 Object 对象;对象的 proto 属性在创建对象时自动添加,默认值为构造函数的 prototype 属性值。

原型对象即为当前实例对象的父对象。

原型链

所有的实例对象都有__proto__属性,指向原型对象。这样通过__proto__属性就形成了一个链的结构——原型链。

当查找对象内部的属性/方法时,js 引擎自动沿着这个原型链查找:先在自身属性中查找,找到返回;如果没有,再沿原型链向上查找,找到返回;如果最终没找到,返回 undefined。
当给对象属性赋值时不会使用原型链,而只是在当前对象中进行操作。

执行上下文

变量提升与函数提升

变量声明提升:通过 var 定义(声明)的变量,在定义语句之前就可以访问到。值为 undefined。
函数声明提升:通过 function 声明的函数,在之前就可以直接调用值为函数定义(对象)。
先有变量提升, 再有函数提升。

执行上下文

执行上下文是由 js 引擎自动创建的对象,包含对应作用域中的所有变量属性。

将代码分为全局代码和函数代码两种。

全局执行上下文:在执行全局代码前将 window 确定为全局执行上下文,对全局数据进行预处理。var 定义的全局变量==>undefined,添加为 window 的属性;function 声明的全局函数==>赋值(fun),添加为 window 的方法;this==>赋值(window)。然后开始执行全局代码。 当页面刷新/关闭时死亡。

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

执行上下文栈

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

作用域与作用域链

作用域

作用域就是一块代码区域。它是静态的(相对于上下文对象),在编写代码时就确定了。作用是隔离变量,不同作用域下同名变量不会有冲突。

分类:

  • 全局作用域
  • 函数作用域
  • 块作用域

作用域与执行上下文

  1. 区别 1

    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时;
    • 全局执行上下文环境是在全局作用域确定之后, js 代码马上执行之前创建;
    • 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建。
  2. 区别 2

    • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化;
    • 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放。
  3. 联系:上下文环境(对象)是从属于所在的作用域

    • 全局上下文环境==>全局作用域;
    • 函数上下文环境==>对应的函数使用域。

作用域链

多个上下级关系的作用域形成的链,它的方向是从内到外,查找变量时就是沿着作用域链来查找,直到全局作用域, 如果还找不到就抛出找不到的异常。

闭包

闭包的概念

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包。闭包存在于嵌套的内部函数中。

产生闭包的条件是函数嵌套以及内部函数引用了外部函数的数据(变量/函数)。

闭包可以理解为嵌套的内部函数,也可以理解为包含被引用变量(函数)。

闭包程序示例:

1
2
3
4
5
6
7
8
9
10
11
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f()
f()

闭包的意义

作用:

  1. 使函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期);
  2. 让函数外部可以操作到函数内部的数据(变量/函数)。

缺点:

  1. 变量占用内存的时间可能会过长;
  2. 可能导致内存泄露。
  • 解决:及时释放,让内部函数对象成为垃圾对象。f = null

应用:

闭包应用:

  • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为;
  • 循环遍历加监听。