JavaScript中的函数

@(javascript)[js函数]

[toc]

JavaScript中的函数

函数的分类与定义函数的方式

JavaScript中的函数可以分为两类:有名函数匿名函数。而定义函数的方式有两种:函数声明函数表达式

目标:定义一个函数 fn ==> 有名函数

// 使用函数声明
function fn(){
    // 函数执行体
}
// 使用函数表达式
var fn = function(){
    // 函数执行体
}

使用函数声明的重要特征就是函数声明提升,即在读取代码前会先读取函数声明。函数名()表示执行函数
看看下面的代码没有任何问题。

// 定义一个有名函数 fn1 使用函数声明
function fn(){
    console.log("fn1")
}
// 调用函数 fn1
fn1();  // fn1

// 定义一个有名函数 fn2 使用函数表达式
var fn2 = function(){
    console.log("fn2")
}
// 调用函数 fn2
fn2(); // fn2

但是如果是把调用放在定义函数前面,使用函数表达式的就会报错(Uncaught ReferenceError: fn1 is not defined)

// 调用函数 fn1
fn1(); // fn1
// 定义一个有名函数 fn1 使用函数声明
function fn(){
    console.log("fn1")
}

// 调用函数 fn2
fn2(); // Uncaught ReferenceError: fn1 is not defined
// 定义一个有名函数 fn2 使用函数表达式
var fn2 = function(){
    console.log("fn2")
}

这就是使用两种的区别。

函数的返回值

每一个函数在调用的时候都会默认返回一个undefined

function fn(){
    console.log(1)
}
fn(); // 1
console.log(fn); // console出一个函数 即 fn
console.log(fn()); // undefined

这里需要注意的地方就是关于函数执行过程函数执行结果

fn()表示调用函数。那就会执行函数体。并默认返回一个undefined。只不过这个值undefined没有变量接收或者说是我们没有用这个值。

console.log(fn)就只是console出一个变量fn的值。只不过这个值是一个函数。

console.log(fn())与第一个的区别就是函数执行了并返回了一个结果。这个结果呢与上面不同的就是现在这个结果我们用上了(放在了console)里面。再由于值是undefined,所以console了一个undefined

既然函数是可以有返回值的,并且这个值默认是一个undefined。那我们可以可以修改呢?答案是可以的。
我们使用return语句可以让函数返回一个值

function fn(){
    console.log(1)
    return "哈哈"
}
fn(); // 1
console.log(fn); // 函数 fn
console.log(fn()); // 哈哈

可以看一下第一个与第二个的结果与之前的是相同的。但是第三个的结果就不同了,他是字符串哈哈。因为我们修改了函数的默认返回值。

所以,可以把默认函数理解成这样的

function fn(){
    return undefined;
}

而我们修改返回值就是吧这个undefined给变成其他的值了。
注意:函数的返回值可以是任意的数据类型。

函数参数

函数是可以接收参数的,在定义函数的时候放的参数叫形式参数,简称形参。在调用函数的时候传递的参数叫实际参数,简称实参。一个函数可以拥有任意个参数

function fn(a,b){
    console.log(a)
    console.log(b)
    console.log(a+b)
}
// 调用函数并传递参数
fn(3,5);  // 3,5,8

fn(3); // 3,undefined,NaN

fn(3,5,10) // 3,5,8

可以看看上面的例子。定义函数的时候有两个形参。调用的时候分为了三种情况。

第一种,传递两个参数,在console时候a=3,b=5,a+b=8。老铁,没问题。

第二种,传递一个参数,在console的时候a=3,b=undefined,a+b=NaN。哈哈,你不行。

第三种,传递3个。在console的时候a=3,b=5,a+b=8。握草,你牛逼。对第三个参数视而不见了。

以上就是三种情况。一句话:参数一一对应,实参少了,那么没有对应的就是undefined,实参多了,多出来的就是没有用的

arguments

在不确定参数(或者定义函数的时候没有形参)的时候,调用函数你传递参数了,但是你没有使用新参去接收,就无法使用。把此时就有一个arguments对象可以获取到实参的个数以及具体的值。

function fn(){
    console.log(arguments)
}
fn(1,2,3,4,5,6,7) // Arguments(7) [1, 2, 3, 4, 5, 6, 7, callee: ƒ, Symbol(Symbol.iterator): ƒ]

arguments在严格模式下无法使用。

函数递归

递归:就是函数自己调用自己。比如下面经典的阶层递归函数

function stratum(n){
    if (n <= 1){
        return 1;
    } else {
        return n * stratum(n - 1);
    }
}
stratum(5) // 120 = 5 * (4 * (3 * (2 * 1) ) )

可以看出实现的阶层的功能。
不过需要注意一下每一个的执行顺序。不是5 * 4 * 3 * 2 * 1。而是5 * (4 * (3 * (2 * 1) ) )的顺序。为了证明这一点。可以将*换为-

function fn(n){
    if (n <= 1){
        return 1;
    } else {
        return n - fn(n - 1);
    }
}
fn(5) // 3

如果是按照不带括号的5-4-3-2-1 = -5。但是结果却是3。那3是怎么来的呢?5 - (4 - (3 - (2 - 1) ) ) = 5 - (4 - (3 - 1)) = 5 - (4 - 2) = 5 - 2 = 3

还可以使用arguments.callee方法调用自身。这个方法就指向当前运行的函数

function stratum(n){
    if (n <= 1){
        return 1;
    } else {
        return n * arguments.callee(n - 1);
    }
}
stratum(5) // 120

递归虽然可以让代码更简洁,但是能不使用递归的时候就不要使用,递归会影响性能(因为过多的调用自己会一直保存每一次的返回值与变量,导致内存占用过多甚至内存泄露)

console.time(1);
function stratum(n){
    if (n <= 1){
        return 1;
    } else {
        return n * arguments.callee(n - 1);
    }
}
console.log(stratum(5))
console.timeEnd(1) // 1: 4.470947265625ms

console.time(2)
var a = 1;
for (var i = 1; i <= 5; i++) {
    a *= i;
}
console.log(a);
console.timeEnd(2)  // 2: 0.2373046875ms

两个阶层,一看。for循环快太多了。具体的性能问题可以看看<a href="//www.greatytc.com/p/6bdc8e3637f2" target=“_blank”>爱情小傻蛋</a>关于递归的算法改进。

函数闭包

闭包是指有权访问另一个函数作用域中的变量的函数。
两个条件:

  1. 函数嵌套函数
  2. 内部函数使用包含函数的变量或者是参数
function fn(){
    var a = 1;
    return function(){
        console.log(a);
        a++;
    }
}
fn()(); // 1
fn()(); // 1

var a = fn();
a(); // 1
a(); // 2

上面的例子中的函数就是一个闭包。注意上面的直接调用返回值与先保存返回值在调用的区别。

闭包只能取得包含函数中任何变量的最后一个值this是无法在闭包函数中调用的。因为每一个函数都有一个this

闭包函数中使用的变量是不会进行销毁的,像上面的var a = fn(),这个函数a中使用了函数fn中的变量,且a是一直存在的,所以函数fn里面的变量a是不会销毁的。如果是直接调用函数fn()()只是相当于调用一次fn函数的返回值。调用完函数返回值就销毁了。所以变量a不会一直保存。

因为闭包函数的变量会一直保存不会

call,apply与bind

三个方法都是改变this指向

call apply

function fn(a,b){
    console.log(a)
    console.log(b)
    console.log(this.name)
}

var name = "嘻嘻"
var obj = {
    "name": "哈哈"
}
// 执行函数fn
fn(1,2) // 1,2,嘻嘻

直接调用函数fn(1,2)this.name的值是嘻嘻

如果使用call:

fn.call(obj,1,2) // 1,2,哈哈

call方法的第一个参数是改变this指向的东西,可以是任何的数据类型。只不过如果是null或者是undefined就会指向window。<span style="color: red;font-weight: 900;">其他的参数</span>依次对应函数的每一个形参。

如果使用apply

fn.apply(obj,[1,2])

apply的使用与call的使用的唯一的区别就是它对应函数每一项形参<span style="color: red;font-weight: 900;">是一个数组</span>而不是单独的每一个。

call与applu都是在函数调用的时候去使用

bind则是在函数定义的时候使用

function fn(a,b){
    console.log(a)
    console.log(b)
    console.log(this.name)
}

var name = "嘻嘻"
var obj = {
    "name": "哈哈"
}
// 执行函数fn
fn(1,2) // 1,2,嘻嘻

如果使用bind可以是一下几种方式

// 使用函数表达式 + 匿名函数
var fn = function(a,b){
    console.log(a)
    console.log(b)
    console.log(this.name)
}.bind(obj)
fn(1,2)

// 使用有名函数
function fn(a,b){
    console.log(a)
    console.log(b)
    console.log(this.name)
}
fn.bind(obj)(1,2)

// 函数在自执行
(function fn(a,b){
    console.log(a)
    console.log(b)
    console.log(this.name)  
}.bind(obj)(1,2))

(function fn(){
    console.log(a)
    console.log(b)
    console.log(this.name)
}.bind(obj))(1,2);

(function fn(){
    console.log(a)
    console.log(b)
    console.log(this.name)
}).bind(obj)(1,2);

使用bind的时候也是可以传递参数的,但是不要这样使用,因为使用bind后你不调用函数那么参数还是没有作用。既然还是要调用函数,我们一般就把函数的实参传递到调用的括号里面。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,556评论 0 5
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,148评论 0 13
  • 前言:本文将详细的介绍JS中函数的相关概念(包括函数的call stack 、this 、作用域、闭包、柯里化、高...
    EnochQin阅读 645评论 2 2
  • --- 学习目标: - 掌握编程的基本思维 - 掌握编程的基本语法 typora-copy-images-to: ...
    YFBigHeart阅读 1,051评论 0 2
  • 和好朋友聊了一上午的天,觉得幸福感蹭蹭的往出冒。想起原来两人吃串串火锅,包里还要偷偷背上一斤半劲酒的日子,忍俊不禁...
    鱼在盘子里想家阅读 335评论 0 0