函数
函数实际上是对象,每个函数都是Function的实例。函数名仅仅存着指向函数的指针,所以可以有多个名字变量指向一个函数。所以javascript函数没有重载,后面的声明会覆盖前面的变量指针。
函数的返回值
如果return后面没跟内容,或者没有写return ,返回undefined;
arguments.callee
指向正在执行的函数
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
}
}
调用栈和调用位置
function baz() {
// 当前调用栈是:baz 当前调用位置是全局作用域
console.log( "baz" );
bar(); // <-- bar 的调用位置
}
function bar() {
// 当前调用栈是 baz -> bar 当前调用位置在 baz 中
console.log( "bar" );
foo(); // <-- foo 的调用位置
}
function foo() {
// 当前调用栈是 baz -> bar -> foo 当前调用位置在 bar 中
console.log( "foo" );
}
baz(); // <-- baz 的调用位置
作用域
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
静态作用域与动态作用域
因为 JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。
而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
let value = 1;
function foo() {
console.log(value);
}
function bar() {
let value = 2;
foo();
}
bar();//1
//JavaScript采用静态作用域:
//执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是全局变量value 等于1。
//假设JavaScript采用动态作用域:
//执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果为为2。
作用域链
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象。
let scope = "global scope";
function checkscope(){
let scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();//local scope
function checkscope2(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope2()();//local scope
//两段代码都会打印:`local scope`。
//因为JavaScript采用的是词法作用域,函数的作用域基于函数创建的位置。
//JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。
This
JavaScript 中的作用域就是词法作用域 ,但是 this 机制某种程度上很像动态作用域。
this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
本质上任何函数在执行时都是通过某个对象调用的,如果没有直接指定就是window。
this的绑定规则
默认绑定
function foo() {
console.log( this.a );
}
let a = 2;
foo(); // 2
//foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,this指向全局对象window。
// 用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定 到 undefined:
function foo() {
"use strict";
console.log(this.a);
}
let a = 2;
foo(); // TypeError: this is undefined
隐式绑定
function foo() {
console.log(this.a);
}
let obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
//对象属性引用链中只有最后一层会影响调用位置。
function foo() {
console.log(this.a);
}
let obj2 = {
a: 42,
foo: foo
};
let obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,此时它会应用默认绑定
function foo() {
console.log(this.a);
}
let obj = {
a: 2,
foo: foo
};
let bar = obj.foo; // 函数别名!
let a = "global"; // a 是全局对象的属性
bar(); // "global"
传入回调函数时也会丢失
function foo() {
console.log( this.a );
}
function doFoo(fn) {
fn();
}
let obj = {
a: 2,
foo: foo
};
let a = "global";
doFoo( obj.foo ); // "global"
//函数的参数传递是一种隐式赋值,所以也会丢失绑定对象。
setTimeout( obj.foo, 100 ); // "global"
//JavaScript 环境中内置的 setTimeout() 函数实现和下面的伪代码类似:
function setTimeout(fn,delay) {
// 等待 delay 毫秒
fn();
}
显式绑定
用call和apply对this绑定对象,它们区别是参数形式
function.apply(Obj,[argArray])
function.call(Obj, arg1, arg2, ...);
bind 和 call/apply 的区别,一个函数被 call/apply 的时候,会直接调用,但是 bind 会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,新函数的参数将会和原函数的参数合并成为原函数的参数。
function foo() {
console.log( this.a );
}
let obj = {
a:2
};
foo.call( obj ); // 2
如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:
如果对一个函数进行多次 bind
,fn中的
this永远由第一次
bind` 决定
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => widow
new绑定
function foo(a) {
this.a = a;
}
let bar = new foo(2);
console.log( bar.a ); // 2
//使用 new 来调用 foo(..) 时,会构造一个新对象,并把它绑定到 foo(..) 调用中的 this 上。
优先级
new绑定> bind等绑定>obj.foo() > foo(),
箭头函数的 this
一旦被绑定,就不会再被任何方式所改变。
//在 new 中使用硬绑定函数,可以预置函数的一些参数,这样在使用 new 进行初始化时就可以只传入其余的参数。
function foo(p1,p2) {
this.val = p1 + p2;
}
let bar = foo.bind( null, "p1" );
let baz = new bar( "p2" );
baz.val; // p1p2
箭头函数
箭头函数与普通函数的区别:
箭头函数没有自己的this,this指向包裹箭头函数的第一个普通函数的this,类似词法作用域的行为 (call/apply等方法也无法改变this指向)
没有arguments,所以只能用...arg来获取所有参数
不能用new,因为没有this,没有prototype
使用:
箭头函数最常用于回调函数中,例如事件处理器或者定时器:
let a = 3;
let obj = { a: 2 };
function foo() {
setTimeout(() => {
console.log(this.a);
}, 100); // 这里的 this 在词法上继承自 foo()
}
foo.call(obj); // 2
function poo() {
setTimeout(function() {
console.log(this.a);
}, 100); // 这里丢失了绑定,默认绑定到window上
}
poo.call(obj); // 3
闭包
有权访问另一个函数作用域中变量的函数就是闭包,闭包只能取得包含函数在任何变量的最后一个值。
闭包也能建立模块,让外界访问不到变量。
无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用。
闭包的作用
- 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
闭包的应用: 定义JS模块
将所有的数据和功能都封装在一个函数内部(私有的)
只向外暴露一个包含n个方法的对象或函数
// 自定义模块1
function coolModule() {
//私有的数据
let msg = 'atguigu'
let names = ['I', 'Love', 'you']
//私有的操作数据的函数
function doSomething() {
console.log(msg.toUpperCase())
}
function doOtherthing() {
console.log(names.join(' '))
}
//向外暴露包含多个方法的对象
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
闭包的缺点
函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
容易造成内存泄露,需要及时设置null来触发垃圾回收