zweizhao.github.io

项目,文章,随笔,博客,Markdown,个人网站,人生感悟。

View on GitHub

你确定掌握了JS的函数,真的确定吗?

警告:最后一站:秋名山,座下乃AE86,无法保证不撒豆腐,路途颠簸刺激,请务必拉好扶手!

JavaScript函数

函数过于恶心,我们由浅入深,一点一点来,相信全篇下来,不懂也能做到心里有数了。


经典函数的定义

函数定义有三种情况,具名函数、函数表达式与匿名函数。

函数的作用,简单理解:专门解决某个问题或业务的功能模块

  1. 具名函数

  2. 函数表达式

  3. 匿名函数

具名函数

具名函数,就是最常见的普通函数及其声明。

function func1(参数) {
    内容
    return x // return 可选
}
关键词 说明
function 函数声明前缀,就像变量的var, let, const一样。
func1 func1就是函数名,当然可以随便什么名字。
参数 可选的,所谓参数就是给这个函数运行的时候提供的资源,并且是可以直接在函数内部调用的变量。
内容 常规的js代码,可以使用参数。
return 函数的返回值,就是这个函数执行完之后给外部执行者一个结果,当然也可以不给。

参数注意一下,参数可以没有,可以一个,也可以多个。多个参数使用 , 隔开。这里不讲解arguments,需要了解的请点击:js arguments

比如你要做个平方处理,弄个函数如下:

function getSquare(num) {
    return num * num // 当然可以使用Math方法,这里不提。
}

let result = getSquare(987)
console.log(result) // 974169

又或者如下:

let initialNum = 987

function makeSquare() {
    initialNum *= initialNum 
}

makeSquare()
console.log(initialNum) // 974169

这两个函数使用相信大家没什么问题,主要说明一下里面的return区别。

第一个提供的是公共函数,就是你给我一个数,我帮你给平方并且把结果返回给你,所以需要return。

第二种是对已经有的数据处理,并不产生新的需要用到的变量,所以就不需要返回,也就不需要return。

不写return还是会默认返回一个值,其实JS操作都会返回一个值,即return一个值出来,如果是没有明确return什么东西,则返回的是undefined。

上述例子改写一下,如下:

let result = getSquare(987)

function getSquare(num) {
    return num * num // 当然可以使用Math方法,这里不提。
}

console.log(result) // 974169

这种情况下没有报错,按说getSquare在使用后声明,应该会报错才是,但实际上不是,这里就有一个小知识点:函数的声明提升。实际代码解释后如下:

// 注意实际上被解释器提升到这里了。
let getSquare
getSquare = function(num) {
    return num * num // 当然可以使用Math方法,这里不提。
}

let result
result = getSquare(987)

console.log(result) // 974169

上面看不懂的请回去看我之前的文章:JavaScript变量

这里要记住一点:具名函数是JS所有类型中的一等公民,所有提升函数都会到当前作用域的最上面!

this内容在后面的ES6箭头函数那里

匿名函数

function(参数(可选)) {
    内容
    return x // return 可选
}

匿名函数由于没有名字,定义后续无法使用,因为无名所以找不到。正因此,匿名函数多用来作为回调。如下:

// 函数func1有一个参数any,any由于会执行,所以是个函数,因为这里给这个函数名字了,所以传进来时候不需要名字,即匿名。
function func1(any) {
    console.log('func1调用了')
    any()
}

// 执行func1并传了一个匿名函数进去
func1(function() {
    console.log('匿名函数调用了')
})

// func1调用了
// 匿名函数调用了

函数表达式

let func1 = function(参数(可选)) {
    内容
    return x // return 可选
}

与具名函数几乎一样,唯一区别就是无法做到完全的函数声明提升,此时不是具名函数也就不是一等公民了,如下:

let result = getSquare(987)

let getSquare = function(num) {
    return num * num // 当然可以使用Math方法,这里不提。
}

console.log(result) // Uncaught ReferenceError: getSquare is not defined,未定义。

解释器是这么玩的:

let result
result = getSquare(987)
let getSquare
getSquare = function(num) {
    return num * num // 当然可以使用Math方法,这里不提。
}
console.log(result)

看明白为什么报没有定义的错误了吧。


ES6的箭头函数

let func1 = (参数) => {
    内容
    return x // 可选
}

注意,只能使用类似函数表达式形式,不存在具名函数一说,匿名倒是常见。所以也没有提升了。

格式上与函数表达式差不太多,主要就是function换成了=>然后放到后面去了,光看格式还是很简单的。

箭头函数的意义:

  1. 写法与return

  2. this绑定处理

写法与return

不单单只是说把function去掉换成=>了,最主要是多了另一个强迫症福音,如下:

let af = num => num * num
let result = af(4)
console.log(result) // 16

首先,有没有看着很爽?大括号省了,这里注意,单句,还是单句才能省。单句说明在:JavaScript判断语句

再者,有没有发现没有return还是实现了return的功能?result打印居然不是undefined?这就是箭头函数的一个return功能点:单句的情况下,并且没有大括号包裹,默认返回此单句

let af = num => {
    let a = 1
    let b = 2
    a + b
    num * num
}

let result = af(4)
console.log(result) // undefined,如你所愿。

af = num => {
    num * num
}

result = af(4)
console.log(result) // undefined,未能如你所愿。。。

个人倒是很喜欢这个不要括号的风格,与本尊爱的CoffeeScript又进了一步。

this

this对于函数来说,就是调用者。比如:a.func1() 那么原来func1里面的this都是a。如果是func1()这种调用,则是当前作用域this,比如全局下调用,当前this是window,所以func1里面的this也是window。因为相当于window.func1()

具体this的相关如call,apply,bind以及new模式的this绑定。

但是箭头函数对此有特殊情况,具体区别见如下代码:

let obj1 = {
    func1: function() {
        console.log(this)
        let obj2 = {
            func2: function() {
                console.log(this)
            },
            af2: () => console.log(this)
        }
        obj2.func2()
        obj2.af2()
    },
    af1: () => console.log(this)
}

obj1.func1()
// obj1,谁调用this指向谁。
// obj2,谁调用this指向谁。
// obj1,自身不绑定this,this指向最近的被绑定this,若无,则Window。

obj1.af1() // Window,自身不绑定this,this指向最近的被绑定this,若无,则Window。

注意上述的代码与注释,说明了箭头函数与function函数this区别。


强制this绑定

与箭头函数无关

  1. call

  2. apply

  3. bind

call与apply

两者功能一致,都是强制绑定一个对象给函数调用,使得函数的this指向该对象,区别见表:

名称 区别
call x.call(obj, 参数1, 参数2, …)
apply x.apply(obj, [参数1, 参数2, …])
let obj = {
    func: function(num) {
        console.log(this)
        console.log(num)
    }
}

obj.func(1) // obj, 1

let objX = {
    x: 'x'
}

obj.func.call(objX, 2) // objX, 2
obj.func.apply(objX, [2]) // objX, 2

就是call可以正常传参,而apply把参数组成数组传进去,个人比较喜欢call。

bind

call与apply使用后会直接运行,而bind使用后会复制一个已绑定的对象函数,然后需要手动运行,传参与call一致。

let obj = {
    func: function(num) {
        console.log(this)
        console.log(num)
    }
}

obj.func(1) // obj, 1

let objX = {
    x: 'x'
}

let newFunc = obj.func.bind(objX, 2)
newFunc()

可以获取一个随时使用的被绑定指定对象的函数,优先考虑使用。

new

没什么太多可说的,秉承着谁调用this指向谁就对了。

function Obj() {
    this.a = 1
    this.func = function() {
        console.log(this.a)
    }
}

let obj = new Obj()
obj.a // 1
obj.func() // 1

new的this指向优先级高于其他,心里有个数就好。


闭包

获取与使用原本应该拿不到的局部作用域内的引用或变量。

一例胜千言:

// 常规
function add() {
    let num = 1
    num++
    return num
}

add() // 2,调用的时候会新生成一个num,然后用完销毁num。
add() // 2,一样
add() // 2,一样
// 闭包
function add() {
    let num = 1
    return function() {
        num++
        return num
    }
}

let result = add()
console.log(result)
// f() {
//     num++
// return num
// }
result() // 2,num
result() // 3,num

获取并操作本该获取不到的num(当前是全局作用域,而num是在add函数作用域),因为result持有的是一个函数,类似一个包裹一样,把其他作用域的变量包在里面并返回给外界,形象的称呼为闭包。


恩,关于函数的知识点就说到这里了,喜欢我的文章请记得关注一波啊,我会定期更新技术文章滴。