# 函数
函数本质上就是一个对象,可以拥有各种属性
# 函数声明
# 通过字面量声明
// 函数的参数不需要加 var
function 函数名t(参数1,参数2,...) {
// 可以把函数的参数想象成局部变
return // 函数停止执行,返回 undefined,如果函数中不包含 return,也会返回 undefined
}
# 函数(匿名)表达式
var test = function () {
// 函数体
};
函数提升:在代码执行之前,解析器就已经把函数声明提升到了代码最顶部;而函数表达式则必须等到解析器执行到它所在的代码行时,才会真正被解释执行。
通过字面量声明的函数,会成为全局对象的属性
通过typeof 函数名
,得到的结果是function
函数内部声明的变量:
- 如果不使用
var
声明,和全局变量一致,表示给全局对象添加属性 - 如果使用
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 无法赋值
- 在全局作用域中,this 关键字固定指向全局对象
- 在函数作用域中,取决于函数是如何被调用的
- 函数直接调用,this 指向全局对象
- 通过一个对象的属性调用,格式为
对象.属性()
或对象["属性"]
,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
:保存实例方法的真正所在
# 预编译
- 暗示全局变量:任何变量如果变量未经声明就赋值,此变量就为全局对象(window)所有。
a = 10; 相当于 window.a = 10;
- 一切声明的全局变量全是 window 的属性
预编译发生在函数值执行的前一刻
- 创建 AO(Activation Object)对象(就是执行期上下文,相当于作用域)
- 找形参和变量声明,将变量和形参名作为 AO 的属性名,值为
undefined
- 将实参和形参相统一
- 在函数体里找函数声明,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: undefined,
d: function d() {}
}
5. 函数执行(从上到下,解释一行执行一行)
AO {
a: function a() {},
b: undefined,
d: function d() {}
}
console ----> a: function a() {}
AO {
a: 123,
b: undefined,
d: function d() {}
}
console ----> a: 123
AO {
a: 123,
b: function () {},
d: function d() {}
}
console ----> b: function () {}
← 对象