JS函数

作者: Jason_Shu | 来源:发表于2018-09-24 22:35 被阅读0次

一. 5种函数声明方式

(1)function f(x, y) { return x+y; }

(2)var f1 = function(x, y) { return x+y; }

(3)var f1 = function f2(x, y) { return x+y; }

(4)var f = new Function("x", "y", "return x+y")

(5)var f = (x, y) => x+y;

二. 函数的name属性

函数其实自带一个name属性, 但是有时候我们分不清这个name到底指代的是谁。

function f(x, y) { return x+y; }
f.name;  //  "f"

var f1 = function(x, y) { return x+y; }
f1.name;  //   "f1"

上述代码,直接声明一个函数f,f.name就是"f", 然后声明一个变量f1,然后将一个匿名函数赋值给这个f1变量。

我们再看看第(3)(4)(5)中函数声明方式。

var f1 = function f2(x, y) { return x+y; }
f1.name;  //  "f2"
f2.name;  //  报错

var f = new Function("x", "y", "return x+y")
f.name;  //  "anonymous"

var f2 = (x, y) => x+y;
f2.name;  //  "f2"

三. 函数的本质

平常我们声明一个这样的函数:

function f(x, y) { return x+y; }

直接这样执行:

f(1, 2)  //  3

但其实应该这么执行:

f.call(undefined, 1, 2)

如果我们直接输出f,这个f只是一个变量指代这个函数,内存图分析如下:


image.png

call方法为后面的this,arguments知识打下铺垫。

四. this和arguments

我们看上述用call方法调用函数的代码:

function f(x, y) { 
  console.log(this)  //  window对象
  console.log(arguments)  // arguments[1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ] (伪数组)
  return x+y;
 }

f.call(undefined, 1, 2)

简单来说,call方法的第一个参数就是this,除了第一个参数外的所有参数就是arguments,其中这个arguments是一个“伪数组”.

注意在“严格模式下”,this是undefined。

function f(x, y) { 
  'use strict'
  console.log(this)  //  undefined
  console.log(arguments)  // arguments[1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ] (伪数组)
  return x+y;
 }

f.call(undefined, 1, 2)

五. call back调用栈

当一个函数执行的时候,会进入一个“调用栈”,我们知道“栈”的特性:先进后出。我们分析下面一段代码:

function a() {
  console.log("a1");
  b.call();  //  (1)  
  console.log("a2");
  return "a";
}

function b() {
  console.log("b1");
  c.call();   //  (2) 
  console.log("b2");
  return "b";
}

function c() {
  console.log("c")
  return "c";
}

a.call();  //  (3)
console.log("end");

在全局范围,我们有a, b, c三个变量,然后看到a.call();执行a函数,输出“a1”,然后看到b.call(),所以在输出“a2”前,进入b函数,输出“b1”,看到c.call(),就进入c函数,输出“c”,c函数执行完后,回到刚刚c.call()的位置,即代码中(2)位置,并执行下一步,输出“b2”,这样b函数就执行完了,回到b.call()的位置,即代码中的(1)位置,并执行下一步,输出“a2”,执行完a函数,即回到a.call()的位置,即代码中的(3)位置,然后输出“end”。

六. 作用域面试题(略略提下“闭包”)

var a = 1;
function f1() {
  var a =2;
  f2.call();
  console.log(a);  //  (1)
  function f2() {
    var a = 3;
    console.log(a)  //  (2)
  }
}

f1.call();
console.log(a)  //  (3)

全局作用域有两个变量: a和f1,f1作用域中有两个变量:a和f2,f2作用域中有一个变量:a,
var a的赋值只会影响当前作用域,故(1)(2)(3)处输出分别为2,3,1。

我们现在把代码变形下:

var a = 1;
function f1() {
  a =2;
  f2.call();
  console.log(a);   //  (1)
  function f2() {
    var a = 3;
    console.log(a);     //  (2)
  }
}

f1.call();
console.log(a);     //  (3)

f1作用域中的a=2;首先会被看成一个赋值语句,在f1作用域中找变量a并且赋值,如果没找到就往父作用域找,对于上述代码,父作用域(全局作用域)中有一个var a = 1,则会改变全局作用域的变量a为2,同时(1)处的log会从当前作用域(f1作用域)开始寻找变量a,如果没有,则往父作用域寻找,故(1)和(3)处的log输出都是全局作用域的a,即输出2。此处就有“闭包”,即:如果一个函数可以使用该函数作用域外的变量,那么该(这个函数+这个变量)就叫“闭包”。

我们再次变形:

var a = 1;
function f1() {
  f2.call();
  console.log(a);   //  (1)
  var a =2;
  function f2() {
    var a = 3;
    console.log(a);     //  (2)
  }
}

f1.call();
console.log(a);     //  (3)

在我们声明变量的时候我们要注意“变量提升”,乍一看(1)处的log可能是1或者2,但是其实都不是,上述代码应该实际上是这么写。

var a;
a = 1;
function f1() {
  var a;
  f2.call();
  console.log(a);   //  (1)
  a =2;
  function f2() {
    var a;
    a = 3;
    console.log(a);     //  (2)
  }
}

f1.call();
console.log(a);     //  (3)

进行变量提升的改写后,我们就可以很清楚地看出log输出什么了,(1)处会输出“undefined”。

相关文章

网友评论

      本文标题:JS函数

      本文链接:https://www.haomeiwen.com/subject/ddwhoftx.html