一、函数的定义

函数式一段可以重复使用的代码块

1.1 函数声明和函数表达式

定义函数常用的两种方式是函数声明和函数表达式,如下:

  1. function fn1 () { //函数声明
  2. console.log('fn1()')
  3. }
  4. var fn2 = function () { //表达式
  5. console.log('fn2()')
  6. }

关于函数声明与函数表达式的区别,请参考大神之作:https://www.cnblogs.com/chaoyuehedy/p/9110063.html

1.2 函数的调用

函数调用方式有以下几种:

  • 直接调用: test()
  • 通过对象调用: obj.test()
  • new调用: new test()
  • 临时让test成为obj的方法进行调用:test.call/apply(obj)

    1.3 回调函数

    简单理解什么函数才是回调函数?

  • 你定义的

  • 你没有调
  • 但最终它执行了(在某个时刻或某个条件下)

常见的回调函数:

  • dom事件回调函数 ==>发生事件的dom元素
  • 定时器回调函数 ===>window
  • ajax请求回调函数
  • 生命周期回调函数:

    1.4 IIFE(立即执行函数)

    全称: Immediately-Invoked Function Expression,即立即执行函数。常用的写法如下:

    1. (function () { //匿名函数自调用
    2. var a = 3
    3. console.log(a + 3)
    4. })()

    如上的代码理解为(匿名函数)(),即定义一个匿名函数后直接使用 () 来调用该匿名函数。更多关于立即执行函数的知识,参考:https://www.cnblogs.com/cnfxx/p/7337889.html

    立即执行函数的作用:

  • 隐藏实现,其内部的变量对外是不可见的(函数局部变量,作用域为函数体内),是变量更安全

  • 不会污染外部(全局)命名空间,内部变量时局部变量,不会造成全局变量的更改
  • 用它来编码js模块

    1.5 函数中的this

    1.5.1 this是什么?

  • 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window

  • 所有函数内部都有一个变量this
  • 它的值是调用函数的当前对象

    1.5.2 如何确定this的值? 记住以下几点

  • test(): window

  • p.test(): p
  • new test(): 新创建的对象
  • p.call(obj): obj

    了解更多关于JavaScript this 的知识请参考:https://www.cnblogs.com/yuanbo88/p/6290543.html

    1667240-20190922105942263-336834271.png

二、原型与原型链

2.1 原型(prototype)

2.1.1 函数的prototype属性

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

  1. function Fun () {}
  2. console.log(Fun.prototype) // 默认指向一个Object空对象(没有我们的属性)
  3. //控制台输出结果为:
  4. {constructor: ƒ}
  5. constructor: ƒ Fun()
  6. __proto__: Object

2.1.2 给原型对象添加属性(一般都是方法)

给对象原型添加方法直接使用:Fun.prototype.方法名 = function() {方法体}。作用:函数的所有实例对象自动拥有原型中的属性(方法)

  1. Fun.prototype.test = function () {
  2. console.log('test()')
  3. }
  4. var fun = new Fun()
  5. fun.test() //通过实例化对象调用原型对象中的方法。此处的方法类似于java中的实例方法

以上代码在控制台输出 : test()。即通过给函数的原型对象添加的方法,函数的实例对象会自动拥有该方法。若要给函数添加静态方法,代码如下:

  1. Fun.staticFun = function() { //给函数对象添加静态的方法
  2. console.log( 'this is static function')
  3. }
  4. Fun.staticFun(); //通过函数名直接调用静态方法,类似java中的静态方法的调用

2.2 显式原型与隐式原型

  • 每个函数function都有一个prototype,即显式原型(属性)
  • 每个实例对象都有一个proto,可称为隐式原型(属性)
  • 对象的隐式原型的值为其对应构造函数的显式原型的值 ```javascript //定义构造函数 function Fn() { // 内部语句: this.prototype = {}

} console.log(Fn.prototype) //定义函数时添加了显示原型即prototype属性

var fn = new Fn() // 内部语句: this.proto = Fn.prototype console.log(fn.proto) //创建对象时给对象添加了隐式原型属性proto

console.log(Fn.prototype===fn.proto) // true 对象的隐式原型与其构造函数的显示原型是同一个对象

  1. **总结:**<br />* 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象<br />* 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值<br />* 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
  2. <a name="Apsn7"></a>
  3. ### 2.3 原型链
  4. <a name="7vYqJ"></a>
  5. #### 2.3.1 原型链(图解)。
  6. 创建一个Fun函数对象,并new创建一个Fun的实例对象,访问对象属性是的分析图如下。访问一个对象的属性时:
  7. 1. 先在自身属性中查找,找到返回;
  8. 1. 如果没有, 再沿着__proto__这条链向上查找, 找到返回;
  9. 1. 如果最终没找到, 返回undefined
  10. ![1667240-20190928160823167-1006516401.png](https://cdn.nlark.com/yuque/0/2020/png/1121446/1585465681557-1b7f1653-7618-421a-bf21-972387de8954.png#align=left&display=inline&height=513&name=1667240-20190928160823167-1006516401.png&originHeight=513&originWidth=1141&size=53093&status=done&style=none&width=1141)
  11. <a name="PKfuh"></a>
  12. #### 2.3.2 原型链_属性问题
  13. 1. 读取对象的属性值时: 会自动到原型链中查找
  14. 1. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
  15. 1. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
  16. ```javascript
  17. function Fn() {
  18. }
  19. Fn.prototype.a = 'xxx'
  20. var fn1 = new Fn()
  21. console.log(fn1.a, fn1) //'xxx'
  22. var fn2 = new Fn()
  23. fn2.a = 'yyy'
  24. console.log(fn1.a, fn2.a, fn2) //'xxx' 'yyy'

2.3.3 instanceof是如何判断的

表达式: A instanceof B, 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false

function Foo() {  }
var f1 = new Foo()
console.log(f1 instanceof Foo) // true
console.log(f1 instanceof Object) // true
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

三、执行上下文

3.1 引入

3.1.1 变量声明提升

通过var定义(声明)的变量, 在定义语句之前就可以访问到,其值为: undefined

var a = 3
function fn () {
    console.log(a)   //输出结果为undefined
    var a = 4  
}
fn()

3.1.2 函数声明提升

通过function声明的函数, 在之前就可以直接调用,其值为: 函数定义(对象)

console.log(b) //undefined  变量提升
fn2() //可调用  函数提升
// fn3() //不能  变量提升

var b = 3
function fn2() {
    console.log('fn2()')
}

var fn3 = function () {
    console.log('fn3()')
}

更多关于变量提升和函数提升的知识见https://www.cnblogs.com/echolun/p/7612142.html

3.2 执行上下文

执行上下文是一个比喻的词,用于描述运行Javascript代码的环境。JavaScript的代码按位置可简单分为全局代码和函数(局部)代码。在JavaScript中,每次运行一些Javascript代码时,引擎都会创造一个全局执行上下文,当调用函数时,在执行函数体之前,JavaScript引擎会创建一个局部的执行上下文。

3.2.1 全局执行上下文

执行全局代码的过程:

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

    3.2.2 函数执行上下文

    执行局部(函数)代码的过程

  4. 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)

  5. 对局部数据进行预处理
    • 申明形参变量,并将实参变量的值赋值为形参变量,添加为执行上下文的属性
    • arguments==>赋值(实参列表), 添加为执行上下文的属性
    • var定义的局部变量==>undefined, 添加为执行上下文的属性
    • function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
    • this==>赋值(调用函数的对象)
  6. 开始执行函数体代码

    3.3 执行上下文栈

    在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象(栈结构不了解的自行查询资料)。

  7. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)

  8. 在函数执行上下文创建后, 将其添加到栈中(压栈)
  9. 在当前函数执行完后,将栈顶的对象移除(出栈)
  10. 当所有的代码执行完后, 栈中只剩下window

如下代码执行的过程中会出现的上下文栈的变化图所示:

//创建全局上下文,并将该全局上下文储存到上下文栈中
var a = 10;  
var bar = function (x) {
    var b = 5;
      //创建foo的局部上下文栈,并将该上下文对象存储到栈中,
      //当函数foo执行完成后自动释放该执行上下文,跳到bar执行上下文中
    foo(x + b);  
} 

var foo = function (y) { 
  var c = 5;
      console.log(a + c + y); 
} 
bar(10) //创建bar的局部上下文对象,并将该上下文对象存储到栈中

试题:
查看如此代码,分析输出内容

console.log('gb: '+ i)
var i = 1
foo(1)
function foo(i) {
    if (i == 4) {
      return
    }
    console.log('fb:' + i)
    foo(i + 1) //递归调用: 在函数内部调用自己
    console.log('fe:' + i)
}
console.log('ge: ' + i)

答案:

gb: undefined
  fb: 1
  fb: 2
  fb: 3
  fe: 3
  fe: 2
  fe: 1
  ge: 1

四、作用域和作用域链

4.1 作用域

4.1.1 理解:

  • 就是一块”地盘”, 一个代码段所在的区域
  • 它是静态的(相对于上下文对象), 在编写代码时就确定了

    4.1.2 分类

  • 全局作用域

  • 函数作用域
  • 没有块作用域(ES6有了)

    4.1.3 作用

  • 隔离变量,不同作用域下同名变量不会有冲突

    4.1.4 作用域与执行上下文的区别

  1. 区别1:创建的时间不同
    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
    • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
    • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
  2. 区别2:执行上下文是动态的,作用域是静态的
    • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
    • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
  3. 联系

    • 执行上下文(对象)是从属于所在的作用域
    • 全局上下文环境==>全局作用域
    • 函数上下文环境==>对应的函数使用域

      4.2 作用域链

      4.2.1 理解

      多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)。查找变量时就是沿着作用域链来查找的

      4.2.2 查找一个变量的查找规则

  4. 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2

  5. 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
  6. 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常

内容来源于谷粒学院:http://www.gulixueyuan.com/my/course/194