# 函数

函数本质上就是一个对象,可以拥有各种属性

# 函数声明

# 通过字面量声明

// 函数的参数不需要加 var
function 函数名t(参数1,参数2...) {
  // 可以把函数的参数想象成局部变
  return // 函数停止执行,返回 undefined,如果函数中不包含 return,也会返回 undefined
}

# 函数(匿名)表达式

var test = function () {
  // 函数体
};

函数提升:在代码执行之前,解析器就已经把函数声明提升到了代码最顶部;而函数表达式则必须等到解析器执行到它所在的代码行时,才会真正被解释执行。

通过字面量声明的函数,会成为全局对象的属性

通过typeof 函数名,得到的结果是function

函数内部声明的变量

  1. 如果不使用var声明,和全局变量一致,表示给全局对象添加属性
  2. 如果使用var,变量提升到所在函数的顶部,函数外部不可以使用该变量

&

只有表达式才能被执行符号执行

# 函数调用

函数调用有以下方式:

  • 函数调用fn()
  • 方法调用
    • 定义:o.m = fn
    • 调用:o.m()
    • 此时的 this 指向对象 o
    • 方法的链式调用:如果方法不需要返回值,最好直接返回 this,这样就能通过这个对象一直使用链式调用
  • 构造函数调用
    • 通过构造函数调用创建一个新的空对象,这个对象继承自构造函数的 prototype 属性
  • 间接调用
    • 通过call()apply()方法间接调用函数
    • 任何函数都可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法

# 实参列表

在函数声明了之后,系统会自动生成一个实参列表arguments[],专门用来存放实参,长度和实参相同

function test (a, b) {
    // arguments[1,2,3];
    // 这个实参数组里面的内容一一对应实参
}
test(1,2,3);

&

没有重载,因为 js 中的函数名仅仅是指向函数的指针,如果有两个重名的函数,后面的会覆盖前面的。

要想访问函数的指针,只需要写 test 即可,不需要括号

实参对象的两个属性:

  • callee:指代当前正在执行的函数本身,在匿名函数中通过它可以递归地调用自身
  • caller:指代调用当前正在执行的函数的函数,通过它可以访问调用栈
var factorial = function (x) {
  if (x <= 1) return 1 // 递归结束的条件
  return x * arguments.callee(x - 1) // 递归调用执行函数自身
}

# 回调函数

把函数当作参数传递给另一个函数

// callback:传递一个函数进来
function test(callback) {
  //函数体
  callback();
}
var func = function() {
  //函数体
}
test(func);// 直接写函数名,不要加括号
  • 如果需要获取一个函数中异步操作的结果,就必须通过回调函数来获取。
  • 回调函数的作用就是获取异步操作的结果
function fn(callback) {
  // var callback = function (data) { console.log(data) }
	setTimeout(function () {
		var data = '123'
		callback(data)
	}, 1000)
}

fn(function (data) {
	console.log(data)
})

# this 关键字

&

this 无法赋值

  1. 在全局作用域中,this 关键字固定指向全局对象
  2. 在函数作用域中,取决于函数是如何被调用的
    1. 函数直接调用,this 指向全局对象
    2. 通过一个对象的属性调用,格式为 对象.属性()对象["属性"],this指向对象
var obj = {
  a: function () {
    console.log(this);
  }
  b: {
  x: 123,
  func: function () {
    console.log(this);
  }
}
};
// 调用方式不同,this 指代的就不同
obj.a(); // 此时 this 指代的是 obj 对象
var b = obj.a;
b(); // => window.b() 此时 this 指代的是全局的 window 对象
obj.b.func(); // 此时 this 指代的是 b 对象

# apply() 和 call()

function test() {}
test() => 相当于 window.test.call()

function Person(name, age) {
  this.name = name;
  this.age = age;
}
var obj = {}
// 借用别人的方法实现自己的功能
Person.call(obj, "张三", 18);
Person.apply(obj, ["张三", 18]);
// 1. 通过 window.Person 找到 Person 函数
// 2. 调用 call 和 apply 方法把 Person 内部的 this 指向了 obj
// 3. 再把实参依次赋给了形参
// 4. 此时的 obj 就是 {name: '张三', age: 18}

call()apply()的根本作用就是改变 this 的指向,此时的 this 指向的就是call()的第一个参数。

  • call():需要把实参按照形参的个数传进去
  • apply():需要传一个 arguments 数组

# bind() 方法:

将函数绑定至某个对象上,返回一个新函数

function f (y) {
  return this.x + y
}
var o = {
  x: 1
}
var g = f.bind(o)
g(2) => o.f(2) => 3

实现:

Function.prototype.bind = function (o) {
  var _this = this,
      boundArgs = arguments
  return function () {
    // 创建一个实参列表,将 bind() 函数的第二个以及后面的参数都传入这个函数
    var args = [], i
    for (i = 1; i < boundArgs.length; i++) {
      args.push(boundArgs[i])
    }
    for (i = 0; i < arguments.length; i++) {
      args.push(arguments[i])
    }
    return _this.apply(o, args)
  }
}

# 函数的属性

  • length:函数命名参数的个数
  • prototype:保存实例方法的真正所在

# 预编译

  1. 暗示全局变量:任何变量如果变量未经声明就赋值,此变量就为全局对象(window)所有。
a = 10; 相当于 window.a = 10;
  1. 一切声明的全局变量全是 window 的属性

预编译发生在函数值执行的前一刻

  1. 创建 AO(Activation Object)对象(就是执行期上下文,相当于作用域)
  2. 形参和变量声明,将变量和形参名作为 AO 的属性名,值为undefined
  3. 将实参和形参相统一
  4. 在函数体里找函数声明,AO 对象的值改为函数体

活动对象 AO 与 变量对象 VO 的区别:

  • VO 是规范上或者 JS 引擎上实现的,并不能在 JS 环境中直接访问
  • 当进入一个执行上下文之后,这个 VO 才被激活,所以叫 AO,这时候 AO 身上的各种属性才能被访问
function fn(a) {
  console.log(a);
  var a = 123;
  console.log(a);
  function a() {}		// 执行到这句的时候已经预编译过,已经在最上面
  console.log(a);
  var b = function () {}
  console.log(b);
  function d() {}
}
fn(1);

上述代码的执行流程是:

1. 创建 AO 对象
2. 找形参和变量声明
AO {
	a: undefined,
	b: undefined
}
3. 把实参传给形参
AO {
	a: 1,
	b: undefined
}
4. 找函数声明
AO {
	a: function a() {},
	b: undefinedd: function d() {}
}
5. 函数执行(从上到下,解释一行执行一行)
AO {
  a: function a() {},
    b: undefinedd: function d() {}
}
console ----> a: function a() {}
AO {
  a: 123,
    b: undefinedd: function d() {}
}
console ----> a: 123
AO {
  a: 123,
    b: function () {}d: function d() {}
}
console ----> b: function () {}