撬开Function的墙角

146 阅读4分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

函数对任何语言来说都是一个核心的概念。通过函数可以封装任意多条语句,而且可以在任何地方、 任何时候调用执行。

如何定义一个函数?

1. 函数声明式

function eat (food) {
    console.log("今天想吃:"+food)
}

2. 函数表达式

// 定义一个变量eat,并初始化为一个函数;
let eat = function(food) {
    console.log("今天想吃:"+food)
};

3. 箭头函数

// 与使用函数表达式定义一个函数对象很相似,在写法更简洁
let eat = (food) => {
    console.log("今天想吃:"+food)
}

4. 使用Function 构造函数

// Function 构造函数接收任意多个字符串参数,最后一个参数为函数体
let eat = new Function("food1","food2", "return console.log('今天想吃' + food1 + '和' + food2)")
eat('迷糊桃','烤地瓜'); //今天想吃迷糊桃和烤地瓜

// 不推荐,影响性能
// 因为这段代码会被解释两次:第一次是将它当作常规 ECMAScript 代码,第二次是解释传给构造函数的字符串。

小结

函数实际上是对象,每个函数都是 Function 类型的实例,而 Function 也有属性和方法,跟其他引用类型一样。
因为函数是对象,所以函数名就是 指向函数对象的指针,而且不一定与函数本身紧密绑定。

函数声明与函数表达式的区别

解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问); 至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

先来看两段代码:

console.log(calcu(66, 88));  //154
function calcu(num1, num2){
    return num1 + num2;
}
console.log(calcu2(66, 88));  //Uncaught TypeError: calcu2 is not a function
var calcu2 = function (num1, num2){
    return num1 + num2;
}

上面两段代码都是在定义函数之前调用它。使用函数声明方式创建的函数在他之前可以正常运行,而使用函数表达式却报错了。
这里设计到一个函数声明提升的概念,因为js引擎在加载数据时,他优先读取函数声明(找function),然后将他添加到执行上下文,所以函数如果在调用之后定义,会被提升到顶部。
但是函数表达式,看做初始化一个变量,只不过这个变量存储的是函数。在其之前调用的话,执行上下文中没有这函数,代码就会出错。

关于函数表达式

任何时候, 只要函数被当作值来使用,它就是一个函数表达式。但这并不是使用函数表达式的唯一 方式。

函数表达式看起来像是变量赋值的语句,创建的函数然后赋值给一个变量,这种方式创建的函数叫匿名函数,可以看到在function关键字后面函数名字

var calcu2 = function (num1, num2){
    return num1 + num2;
}
console.dir(calcu2)

image.png

// 打印一个未赋值的匿名函数看看
console.dir(function(a){return a})

image.png ps: 看上面👆🏻

  • 赋值给一个变量的匿名函数,函数名(name属性)就是这个被赋值的变量名
  • 未赋值的匿名函数,它的name是空字符串

关于函数名

函数实际就是对象,函数名就是指向函数的指针;同其他包含对象指针的变量一样。 所以说,一个函数可以有多个名称;

function sum(num1, num2){
    return num1 + num2;
}
console.log(sum(2,4)); // 6, 函数名加括号 => 执行函数;
let name1 = sum; // 函数名是指向函数的指针
console.log(name1(3,3)) // 6, 这时sum与name1 都指向同一个函数
sum = null; // 断开sum与函数间的关联
console.log(name1(3,3)); // 6, 不会影响name1

ECMAScript 6 的所有函数对象都会暴露一个只读的 name 属性,其中包含关于函数的信息。多数情 况下,这个属性中保存的就是一个函数标识符,或者说是一个字符串化的变量名。即使函数没有名称, 也会如实显示成空字符串。如果它是使用 Function 构造函数创建的,则会标识成"anonymous"

一起来看下不同方式创建的函数,他们的函数名都是什么样的:

function fn1(){};
let fn2 = function(){};
let fn3 = ()=> {};
let fn4 = new Function();

console.log(fn1.name); // fn1
console.log(fn2.name); // fn2
console.log(fn3.name); // fn3
console.log(fn4.name); // anonymous

// 未赋值的匿名函数
console.log((function(){}).name); // ''

// 获取函数,设置函数
let people = {
    years: 18,
    get age() {
        return this.years;
    },
    set age(newAge) {
        this.years = newAge;
    } 
}
// Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。
let propertyDescriptor = Object.getOwnPropertyDescriptor(people, 'age'); 
console.log(propertyDescriptor.get.name); // get age 
console.log(propertyDescriptor.set.name); // set age

// 使用 bind()实例化
function fn1(){};
console.log(fn1.bind(null).name); //bound fn1