Javascript四:函数

函数

函数实际上是对象,每个函数都是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

闭包

有权访问另一个函数作用域中变量的函数就是闭包,闭包只能取得包含函数在任何变量的最后一个值。

闭包也能建立模块,让外界访问不到变量。

无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用。

闭包的作用

  1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
闭包的应用: 定义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来触发垃圾回收

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容