ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] ## 函数参数默认值 ES6之前不能直接给函数的参数设置默认值。 ``` function log(x, y) { y = y || 'world' console.log(x, y) } log('hello') // hello world log('hello', '') // hello world ``` 这种写法的缺点在于如果参数y赋值了,但对应的值为false,则该赋值不起作用。 为了避免这个问题,通常要先判断参数y是否被赋值,如果没有,再等于默认值,这有两种写法。 ``` if (typeof y === 'undefinded') { y = 'world' } if (arguments.length === 1) { y = 'world' } ``` ES6允许为函数的参数设置默认值,即直接在参数定义的后面。 ``` function log(x, y = 'world') { congole.log(x, y) } ``` ### 与解构赋值默认值结合 ``` function log({x, y = 5}) { congole.log(x, y) } foo({}) // undefined, 5 foo({x:1}) // 1, 5 foo({x:1, y:2}) // 1, 2 ``` ``` function fetch(url, {body = '', method = 'GET', header = {} }) { console.log(method) } fetch('http://example.com', {}) // 'GET' fetch('http://example.com') // 报错 ``` 上面的代码,如果fetch第二个参数是对象,就会给3个属性设置默认值,但不能省略第二个参数。 如果结合函数参数的默认值,则可以省略第二个参数。这时,就出现了双重默认值。 ``` function fetch(url, {method = 'GET'} = {}) { console.log(method) } fetch('http://example.com') // 'GET' ``` ``` function m1({x=0, y=0} = {}) { return [x, y] } function m2({x, y} = {x: 0, y: 0}) { return [x, y] } m1({}) // [0,0] m2({}) // [undefined, undefined] m1({x:1}) // [3, 0] m1({x:1}) // [3, undefined] ``` ### 函数的length属性 指定默认值后,函数的length属性将返回没有指定默认值的参数个数。 ``` (function(a){}).length // 1 (function(a = 5){}).length // 0 (function(a, b, c = 1){}).length // 2 ``` 这是因为length属性的含义是,该函数预期传入的参数个数。某个函数指定默认值后,预期传入的参数个数就不包括这个参数了。同理,rest函数也不会计入length属性。 ``` (function(...args){}).length // 0 ``` ### 作用域 如果函数默认值是一个变量,则该变量所处的作用域与其他变量的作用域规则一样,即先是当前函数的作用域,然后才是全局作用域。 ``` var x = 1 function f(x, y = x) { console.log(y) } f(2) // 2 ``` 如果调用时函数作用域内部的变量x还没生成,结果会不一样。 ``` let x = 1 function f(y = x) { let x = 2 console.log(y) } f() // 1 ``` ## rest参数 Rest参数接收函数的多余参数,组成一个数组,放在形参的最后,形式如下: ``` function func(a, b, ...theArgs) { // ... } ``` Rest参数和arguments对象的区别 * rest参数只包括那些没有给出名称的参数,arguments包含所有参数; * arguments对象不是真正的array,而rest参数是Array的实例,可以直接应用sort, map, forEach, pop等方法; * arguments对象拥有一些自己额外的功能。 函数的length属性,不包括rest参数: ``` (function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, b, ...c)).length // 2 ``` ## 扩展运算符 扩展运算符是三个点(...),将一个数组转为用逗号分隔的参数序列。 ``` console.log(...[1,2,3]) // 1 2 3 console.log(1,...[2,3,3], 5) // 1 2 3 4 5 ``` 该运算符主要用于函数调用 ``` function push(array, ...items) { array.push(...items) } function add(x, y) { return x + y } var number = [4, 38] add(... number) ``` ### 替代数组的apply方法 ``` // ES5 function f(x, y, z) {} var args = [0,1,2] f.apply(null, args) // ES6 function f(x, y, z){} var args = [0,1,2] f(...args) ``` ### 应用 #### 合并数组 ``` // ES5 [1,2].conacat(more) // ES6 [1,2,...more] ``` #### 与解构赋值结合 生成新数组 ``` // ES5 a = list[0], rest = list.slice(1) // ES6 [A, ...rest] = list ``` #### 函数的返回值 JavaScript的函数只能返回一个值,多个值只能通过数组或对象。扩展运算符为解决这个问题提供了变通的方法。 ``` var dataFileds = readDateFields(database) var d = new Date(...dateFileds) ``` 从数据库读取一行数据,通过扩展运算符将其传入构造函数Date。 #### 字符串 扩展运算符还可以将字符串转为真正的数组。 ``` [...'hello'] // ['h', 'e', 'l', 'l', 'o'] ``` #### 类似数组的对象 任何类似数组的对象都能用扩展运算符转为真正的数组。 ``` var nodeList = document.querySelectorAll('div') var array = [...nodeList] ``` #### Map和Set结构,Generator函数 只要有Interator接口的对象,都可以使用扩展运算符。 ``` let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'] ]) let arr = [...map.keys()] // 1,2,3 ``` Genertor函数允许后返回一个遍历器对象,也可以使用扩展运算符。 ``` var go = function *() { yield 1 yield 2 yield 3 } [...go()] [1,2,3] ``` ## name属性 返回该函数的函数名。 ``` function foo(){} foo.name // 'foo' ``` ## 箭头函数 ``` var f = v => v //等同于 var f = function(v) { return v } ``` 如果函数不需要参数或需要多个参数,就使用圆括号代表参数部分。 ``` var f= () => 6 等同于 var f = function() { return 6 } var f= (num1, num2) => num1 + num2 等同于 var f = function(num1, num2) { return num1 + num2 } ``` 由于大括号被解析为代码块,箭头函数返回直接返回对象,外面要加上括号。 ``` var getTempItem = id => ({id: id, name: 'temp'}) ``` 可以与变量解构结合使用。 ``` const full = ({first, last}) => first + ' ' + last ``` ### 使用注意点 1. **函数体内的this对象就是定义时所在的对象,而不是使用时所在的对象。** 2. 不可以当做构造函数,即不能使用new命令,否则会报错。 3. 不可以使用arguments对象,该对象在函数体内不存在 。如果要用,用rest参数替代。 4. 不可以使用yield命令,因此箭头函数不能用作Generator函数。 this对象的指向是可变的,但在箭头函数中它是固定的。 ``` function foo() { setTimeout(() => { console.log('id:', this.id) }, 100) } foo.call({id: 42}) //42 ``` 如果是一般函数,指向的this指向全局对象,但是箭头函数导致this总是指向函数所在的对象。 箭头函数没有this,内部的this就是外层代码块的this。正因为它没有this,不能用作构造函数。也不能用call\(\)、apply\(\)、bind\(\)这些方法。 ## 函数绑定 箭头函数绑定this对象,大大减少了显示绑定this对象的写法,但箭头函数并非适合所有场合,所以ES7提出了函数绑定运算符,用来取代call、apply和bind 的调用。 函数绑定运算符是并排的双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境(即this对象)绑定到右边的函数。 ``` foo:bar // 等同于 bar.bind(foo) const hansOwnProperty = Object.prototype.hasOwnProperty function hasOwn(obj, key) { return obj::hasOwnProperty(key) } ``` 如果双冒号左边为空,右边是一个对象的方法,则将该方法绑定在该对象上。 ``` var method = obj::obj.foo // 等同于 var method = ::obj.foo var log = ::console.log // 等同于 var log = cosole.log.bind(console) ``` 由于双冒号运算符返回的还是原对象,因此可以使用链式写法。 ``` let {find, html} = jake document.querySelectorAll('div.myClass') ::find('p') ::html('sss') ``` ## 尾调用优化