重温JavaScript(lesson5):函数(1)

今天我们一起重温JS中的函数,这是第一部分,内容比较基础,让我们轻松的开始吧~

我们为什么要学习函数,要把它搞懂了,搞透彻?因为能把JS作为函数式语言(functional language)来理解会提高我们的代码水平。JS当中有一个重要的概念那就是:函数是一等公民(first-class citizens)。这是指函数可以像其他普通的JS数据类型一样,可以变量引用,能以字面量的形式声明,能够作为参数传递,还能够作为返回值返回。其实说白点儿,函数就是值

1.作为一等公民的函数

我们知道在JS中对象有以下几种常用的功能:(1)可以通过字面量来创建 (2)可以赋值给变量、数组项,或其他对象的属性(3)可以作为参数传递给函数(4)可以作为函数的返回值(5)对象能够动态的创建和分配属性 。看下面的代码:

//1.对象可以通过字面量来创建
var person = {};
//2.对象可以赋值给变量、数组项,或是其他对象的属性
var arr = [];
arr.push({});
//3.对象可以作为参数传递给函数
function young(person) {
  person.age = 18;
}
young(person)
//4.对象可以作为函数的返回值
function handsomeBou() {
  return {
    name: 'New_Name'
  }
}
//5.对象能够动态地创建和分配属性
var person = {}
person.name = 'New_Name';

函数既然是一等公民,函数同样拥有以上功能:

//1.通过字面量创建函数
function testFun(){}
//2.将函数赋值给变量,数组项或其他对象属性
var functionArr = [];
functionArr.push(testFun);
//3.函数作为函数的参数传递
function callTest(callbackFun) {
  callbackFun();
}
callTest(testFun);
//4.函数作为函数的返回值
function returnFun() {
  return function () {};
}
//5.可以为函数动态创建和分配属性
var dynamicFun = function () {}
dynamicFun.owner = 'New_Name';

函数说了:“对象能做的事情,俺也都能做”。看一个实战开发中可能用到的例子:

//函数作为参数传入
var numbers = [1,5,8,4,7,10,2,6];
numbers.sort(function(first,second){
  return first - second;
});
console.log(numbers);
// [1, 2, 4, 5, 6, 7, 8, 10]

sort()方法接受一个比较函数做为参数,这个比较函数是一个没有名字的函数表达式,仅仅作为引用被传递给另外一个函数。每当数组中的两个值进行比较时就会调用比较函数。如果第一个值小于第二个值,比较函数返回一个负数;如果第一个值大于第二个值,比较函数返回一个正数;如果两个值相等,比较函数返回0。换言之,如果想升序排序就用第一个值减去第二个值,想降序排序就用第二个值减去第一个值

函数的特殊之处就在于它是可以调用的(invokable),也就是说函数会被调用以便执行某项动作。下面我们具体学习函数定义方式。

2.函数的定义

JS函数通常由函数字面量(function literal)来创建函数值,就像数字字面量创建一个值一样。JS提供了以下几种定义函数的方式:(1)函数声明(function declarations)和函数表达式(function expressions) (2)箭头函数 (3)函数构造函数 (4)生成器函数。我们一个一个来学习,其中方式(4)我们在本次分享中只做简单介绍,以后单独详细介绍。

2.1函数声明和函数表达式

函数声明是以function关键字开头后面紧跟着函数的名字,以及括号和括号内一列以逗号分隔的可选参数名。例如:

//函数声明的例子
function add(num1, num2) {
 return num1+num2;
}

而在函数表达式中,function关键字后面不需要加上函数的名字。这种函数被称为匿名函数,因为函数对象本身没有名字。所以函数表达式通常会被一个变量或者属性引用,这也是为何叫做“函数表达式”的原因,因为这种函数总是其他表达式的一部分。例如下面的代码:

//函数表达式的例子
var add =  function(num1, num2) {
  return num1+num2;
};

这段代码将函数作为值赋值给变量add。除了没有函数名并在最后多了一个分号以外,函数表达式几乎和函数声明完全一样。这是在定义形式上的异同点,我们接着看在使用上二者有什么异同?那就是有关函数的提升。函数声明可以被提升,但是函数表达式无法提升

var result = add(5,5);
function add(num1, num2) {
  return num1+num2;
}

这段代码我们咋一看感觉有错误,怎么能够在add函数声明之前就去使用呢?但是实际上这段代码不会报错,因为JS引擎将函数声明提升到了顶部优先执行,看起来就是如下的形式:

//JS引擎中代码的呈现方式
function add(num1, num2) {
  return num1+num2;
}
var result = add(5,5);

JS能对函数声明进行提升的原因是:引擎提前知道了函数的名字。而对于函数表达式,它们只能通过变量引用,因此无法提升。所以像下面的这段代码会报错:

var result = add(5,5);
var add = function(num1, num2) {
  return num1+num2;
}

学到这里,我们知道了变量声明和函数声明都存在提升,但是要注意函数在先,变量在后。(还不知道变量提升的同学快看看lesson1的内容吧)知道这一原理对于解决一些常见的面试题非常有用。例如我们来看看下面代码的运行结果:

var b = 6;
logB(b);
console.log(a);
var a = 5;
function logB(param) {
  console.log(param)
}

运行结果是 6 undefined ,你答对了吗?logB是函数声明所以会被提升。

接下来我们看看函数定义的第二种方式:箭头函数。

2.2箭头函数

箭头函数是ES6新增的成员,为什么要多一个箭头函数呢?由于JS中会大量使用函数,而在ES5标准下声明函数就要写function关键字,所以增加简化创建函数的语法十分有意义,能够减少function关键字的敲击次数,以及花括号的敲击次数。还是先看看箭头函数的芳容吧:

const arrowFun = () => console.log("hello");
arrowFun();
//hello

啥玩意?这就是箭头函数吗?没错,上例中() => console.log("hello");这句代码讲就是在声明一个箭头函数。要是函数有一个参数,箭头函数可以这么写:

const arrowFun = name => console.log('hello,',name);
arrowFun('New_Name');
//hello, New_Name

要是有两个参数呢?也很简单:

const arrowFun = (greet ,name) => console.log(greet,name);
arrowFun('hello','New_Name');
//hello, New_Name

如果函数体有多条语句呢?那就用花括号把函数体包起来:

const arrowFun =  partName => {
  let fullName = 'New_' + partName;
  console.log(fullName);
} 
arrowFun('Name');
//New_Name

是不是使用箭头函数非常方便啊?看完了箭头函数的几种形式,我们还是看看为什么箭头函数使用起来就方便?有3点:

第一,可以省略function关键字

第二,如果函数只有一个参数,可以省略小括号

第三,如果函数体是一个单独的表达式,可以省略花括号和返回语句

箭头函数在使用的时候大部分情况是匿名的,但是也可以赋值给变量。还记得我们刚刚在讲函数是一等公民的时候提到过给数组排序的例子吗?如果使用箭头函数将会更加简洁:

var numbers = [1,5,8,4,7,10,2,6];
numbers.sort((first,second) => first - second);
console.log(numbers);
//[1, 2, 4, 5, 6, 7, 8, 10]

关于箭头函数的使用要注意一个细节:如果想使用箭头函数直接返回一个对象字面量,则需要将对象字面量包裹在小括号里

let getTempItem = id => ({id:id,name:"Temp"})
const obj = getTempItem(5);
console.log(obj)
//{id: 5, name: "Temp"}

还有一个问题我们要思考:箭头函数除了在使用时的形式不同于普通函数之外,还有没有其他不同?还真有三点:

第一,箭头函数不能显示地命名,尽管运行环境会将箭头函数所赋予的变量名作为函数名。

第二,箭头函数会绑定到所在的词法作用域中,因此不会改变this的指向。说白点儿,箭头函数没有this绑定。如果箭头函数被非箭头函数包含,则this绑定的是最近一层的非箭头函数的this。

第三,箭头函数不能不能用作构造函数,也没有prototype属性,这意味着不能对其使用new关键字。

关于第一点和第三点我们不说了,看看箭头函数不会改变this指向的例子:

const obj = {
 name: 'New_Name',
 greet: function () {
  console.log(this); 
  //{name: "New_Name", greet: ƒ}  
  setTimeout(function () {
    console.log(this);
    //Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …} 
  });
 }    
}
obj.greet();

由于是obj调用的greet方法,所以第一个打印出来的this指向了obj对象本身;而setTimeout是全局的window对象上的方法,所以二个打印出来的this指向了window对象。我们改写成如下形式:

const obj = {
 name: 'New_Name',
 greet: function () {
  console.log(this); 
  //{name: "New_Name", greet: ƒ}  
  setTimeout( () => {
    console.log(this);
    //{name: "New_Name", greet: ƒ}
  });
 }    
}
obj.greet();

改写后第二个this也指向了obj对象,这就验证了第二点。下面我们学习第三种定义函数的方式:函数构造函数(Function)。

2.3函数构造函数

事先说明啊,函数构造函数是一种不常使用的函数定义方式,现阶段来看大家只需要了解一下即可。

函数构造函数能让我们以字符串的形式动态构造一个函数,这样的函数是动态生成的。我们看一个例子:

var add = new Function('a','b','return a + b')
var res = add(1,1);
console.log(res)
//2

我们看到Function()构造函数可以传入任意数量的字符串实参,最后一个参数表示的文本就是函数体;函数体中可以包含意义的JS语句,每两条语句之间用分号分隔。

关于Function()构造函数有三点需要注意:

第一,Function()构造函数允许JS在运行时动态地创建并编译函数。

第二,每次调用Function()构造函数都会解析函数体,并创建新的函数对象。所以像new Function()这样的语句要是放在循环中或者多次被调用的另外一个函数中,执行效率会受到影响。

第三,new Function()创建出来的函数使用的是全局作用域。

Function()在实际编程中很少用到,我们就再做过多的解释了。

2.4生成器函数

生成器函数也是ES6的新功能,能让我们创建一个和普通函数不一样的函数。在程序的执行过程中,这种函数能够退出再重新进入,在这些再进入间保留函数内变量的值。一个基本的生成器函数的例子如下:

function*  generateFun() {
  yield 1;
  yield 2;
  yield 3;
}

注意到function关键字后面的星号了吗?关于生成器函数的知识,我们将在后续内容中详细介绍。下面看几个相关的面试题吧

3.函数定义相关面试题

1.什么是函数?

答:函数是由事件驱动或者当调用它时执行的可重复代码块。

2.函数的命名规则是什么?

答:函数名区分大小写;函数名允许包括字母、数字、下划线、美元符号,但是第一个字符不能是数字;不允许使用其他字符、关键字和保留字命名函数。

3.描述一下JS中的匿名函数。

答:被声明为没有任何命名标识符的函数称为匿名函数。

4.什么是回调函数?

答:回调函数就是一个通过函数指针调用的函数。如果把函数的指针作为参数传递给另一个函数,当这个指针用来调用所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的执行方直接调用的,而是在特定的事件或者条件发生时由另一方调用的,用于对该事件或者条件做出响应

5.Function构造器有哪些功能?

答:Function构造器通过动态编辑字符串代码来实现函数的创建,其实现方式和使用全局函数eval()相似;构造函数Function()可以接受任意多个实参,最后一个是新函数的函数体,其他都是新函数的形参。用Function()构造函数来创建新函数不但写法晦涩、性能较低,而且新函数使用的是全局作用域。

6.将一个匿名函数像下面这样用圆括号包裹,有什么作用?

(function(){})()

答:这是一种即时函数(immediate function),也就是定义好就能立即执行的函数。可用于创建块级作用域,解决循环中的异步回调问题和类库封装等。

好了,今天就到这里吧。本次分享主要介绍了函数作为一等公民和函数定义的方式,下次我们继续一起重温函数有关的其他知识,再见啦~

如有错误,请不吝指正。温故而知新,欢迎和我一起重温旧知识,攀登新台阶~

 

图片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

重温新知

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值