### 函数与箭头函数 * 函数function * 箭头函数 =>{ } * call、apply、bind、:: * rest参数与扩展运算符 * * * * * #### **函数function** > 函数对于任何一门编程语言来说都是核心概念,通过函数可以封装多条语句,而且可以在任何地方任何时候去调用,es5中通过function关键字来声明函数,后面跟一组参数以及函数体。在javascript中函数在定义时不必指定是否有返回值,但实际上任何函数都可以通过return语句后面跟上药返回的值来实现定义返回值,同时函数中也可以嵌套函数,调用函数或者返回一个函数。案例如下: ~~~ function s(){ let b =1 return b } ~~~ > 上面代码我们可以通过 s() 来调用,返回 1,这个函数执行完return语句后会立即退出,因此return后面的语句永远不会被执行,例如: ~~~ function s(){ let b =1 return b console.log(2) //这里永远不会被执行 } ~~~ > 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。如下: ~~~ function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello ~~~ > 需要注意的是:参数变量是默认声明的,所以不能用let或const再次声明。而且函数默认值的作用域只在函数体中,外部不可使用。 > ES6 引入 rest 参数(形式为“...变量名”),用于获取函数的多余参数,rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中,同时也可以使用map和set这种类似数组的值。案例如下: ~~~ function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // 10 ~~~ > 从ES5开始,函数内部可以设定为严格模式。《ECMAScript 2016标准》做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。 #### **箭头函数 =>{ }** > ES6允许使用“箭头”(=>)定义函数。如下: ~~~ let f = v => v // let f 定义函数名,=v 指定了箭头函数的参数v , =>指定了箭头函数的返回值 v ~~~ > 上面的箭头函数等同于: ~~~ let f = function(v) { return v } ~~~ > 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。如下: ~~~ let f = () => 5 f() // 5 ~~~ > 如果箭头函数内部有语句需要执行,可以使用 { } 来将它们括起来,并指定return语句,如下: ~~~ let f = (a,b) => { a = 1 b = 2 return a+b } f() // 3 ~~~ > 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。如下: ~~~ let getTempItem = id => ({ id: id, name: "Temp" }); ~~~ > 下面是rest参数与箭头函数结合的例子: ~~~ const numbers = (...nums) => nums; numbers(1, 2, 3, 4, 5) // [1,2,3,4,5] ~~~ #### **箭头函数使用注意点:** * 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。 * 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 * 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。(arguments对象是es5中获取函数参数的对象,本文档不会详细解释,因为用的少,而且ES6已经提供了rest参数) * 不可以使用yield命令,因此箭头函数不能用作Generator函数。(javascript进阶中会讲到Generator函数和yield命令) > 上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。代码如下: ~~~ function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } let id = 21; foo.call({ id: 42 }); // id: 42 ~~~ > 上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到100毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。 > 另外,由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。(关于call,apply,bind这三个改变this指向的方法下面有讲到) > 同时,箭头函数内部也可以嵌套箭头函数,除this指向外与function中嵌套function无异 #### **call、apply、bind、::(注意⚠️:敲黑板)** * * * * * #### call > 在javascript编程中,this指向的是当前对象/调用对象,call方法可以指定函数内部this的指向(即函数执行时的作用域)然后在指定的作用域中调用该函数,如下: ~~~ function a(name,price){ this.name = name this.price = price } function b(name,price){ a.call(this,name,price) // 如果没有使用call,那么当运行到这句代码时this指向的是a this.category = "B" } let c = new b("andy",6000000000000000) console.log(c) //{b:{category:"B",name:"andy",price:6000000000000000,__proto__:Object}} ~~~ > call方法的第一个参数就是this所要指向的哪个对象,后面的参数是函数运行时所需要的参数。 > 上面代码中,方法b中调用了a方法,在调用a方法时又使用了call指定了执行作用域为this,此时this指向的是b方法,如果没有使用call,那么在运行这行代码时this指向的是方法a #### apply > apply方法的作用域call类似,也是改变this指向,然后再调用该函数,唯一的区别就是,它接收一个数组作为函数执行时的参数,如下: ~~~ function y (o,i){ console.log(o+i) } f.call(null , 1 ,1) //2 f.apply(null,[1,2]) //3 ~~~ #### bind > bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数,如下: ~~~ let n ={ name:"andy", age:22 } function c(){ let name = "tom" let age = "25" console.log(this.name) console.log(this.age) } let f = c.bind(n) f() // andy 22 ~~~ #### :: (注意这两个冒号) > 这两个冒号可不是我失误打上去的,而是ES7中新增的一种改变this指向的语法,用于替代上面的call、apply、bind,代码如下: ~~~ let foo = {} let bar = {} bar::foo //将foo的this指向绑定到bar对象上 ~~~ #### **rest参数与扩展运算符** #### rest参数 > ES6引入了rest参数(‘...a//自定义的变量’),用于获取函数的多余参数,这样就不需要使用arguments对象了,rest参数搭配的自定义变量是一个数组,该变量将多余的参数放入这个数组中。如: ~~~ function add(...a){ let n = 0 for(let i of a){ n+=i } return n } add(4,6,10) // 20 ~~~ > 上例add函数是一个求和函数,利用rest参数可以想该函数传入任意数目的参数。rest参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量,类似的数组操作如下: ~~~ function push(array,..items){ items.forEach(function(item){ array.push(item) console.log(item) }) } let a = [] push(a,1,2,3,4,5,6) console.log(a) // [1,2,3,4,5,6] ~~~ > 在使用rest参数时一定要注意,rest参数后不可以再有任何其他参数,即:“rest参数只能是函数的最后一个参数”,否则会报错。同时函数的length属性中也不包括rest参数。 #### 扩展运算符 > 扩展运算符是三个点(...),它好比是rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。如: ~~~ console.log(...[1,2,3]) // 1 2 3 console.log(1,...[2,3,4],5) // 1 2 3 4 5 ~~~ > 扩展运算符也可以用来合并数组,如: ~~~ //ES5合并数组写法 [1,2,3].concat([4,5]) //[1,2,3,4,5] //ES6扩展运算符写法 [1,2,3,...[4,5]] //[1,2,3,4,5] ~~~ > 也可以在解构运算中使用扩展运算符,如: ~~~ let [a,...b] = [1,2,3,4] console.log(a) // 1 console.log(b) // [2,3,4] ~~~ > 在使用扩展运算符对数组赋值时,扩展运算符只能放在最后一位,否则会报错。 > 除了数组以外,扩展运算符也可以将一个字符串/对象/Map/Set等数据结构转为数组。 > 在javascript中,函数只能返回一个值,如需返回多个值,只能返回数组或对象,扩展运算符提供了一种变通的方法,可以将函数返回的数组或对象通过扩展运算符直接传入其他函数。