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

熵桥流沙
• 阅读 885

今天我们一起重温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),也就是定义好就能立即执行的函数。可用于创建块级作用域,解决循环中的异步回调问题和类库封装等。

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

如有错误,请不吝指正。温故而知新,欢迎和我一起重温旧知识,攀登新台阶~
重温JavaScript(lesson5):函数(1)

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
SQL利用函数或存储过程求男或女的总分平均分
!(https://oscimg.oschina.net/oscnet/633e11621f3e13e713cf063db00d72c8aa0.png)函数alterfunctionxb(@xingbievarchar(2))returnstableas
Wesley13 Wesley13
3年前
mysql中时间比较的实现
MySql中时间比较的实现unix\_timestamp()unix\_timestamp函数可以接受一个参数,也可以不使用参数。它的返回值是一个无符号的整数。不使用参数,它返回自1970年1月1日0时0分0秒到现在所经过的秒数,如果使用参数,参数的类型为时间类型或者时间类型的字符串表示,则是从1970010100:00:0
Wesley13 Wesley13
3年前
JS必知的6种继承方式
JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。那么如何在JS中实现继承呢?让我们拭目以待JS继承的实现方式既然要实现继承,那么首先我们得有一个父类,代码如下:// 父类function Person(name) { // 给构造函数添加了参数  this.name  name;
Stella981 Stella981
3年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Stella981 Stella981
3年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
3年前
初探 Objective
作者:Cyandev,iOS和MacOS开发者,目前就职于字节跳动0x00前言异常处理是许多高级语言都具有的特性,它可以直接中断当前函数并将控制权转交给能够处理异常的函数。不同语言在异常处理的实现上各不相同,本文主要来分析一下ObjectiveC和C这两个语言。为什么要把ObjectiveC和
Stella981 Stella981
3年前
JavaScript函数式编程,“香”吗?
总说函数是JavaScript的一等公民,很多人就问了,它凭什么?其实凭的就是对于JS这种没有明确归类的“多范式语言”,函数式编程拥有着天然的优势。在JS里,函数本身就被视作对象,可以有属性,能作为参数传给函数,也能作为函数的返回结果,十分便利。而这种特性对于代码日益庞大,业务逻辑逐渐复杂的前端来说称得上是至关重要。
熵桥流沙
熵桥流沙
Lv1
愿你有坚定立场穿过风沙和迷惘。
文章
12
粉丝
0
获赞
0