关注前端小讴,浏览更多原创技术文章
- 函数是对象,每个函数都是
Function
类型的实例,都与其余援用类型一样具备属性和办法 - 函数名是指向函数对象的指针,不会与某个函数绑定(一个函数可能会有多个名字)
相干代码 →
4 种定义形式
// 1.函数申明定义function sum(num1, num2) { return num1 + num2}// 2.函数表达式定义let sum = function (num1, num2) { return num1 + num2}// 4.箭头函数定义let sum = (num1, num2) => { return num1 + num2}// 4.Function构造函数定义/* 不举荐应用构造函数定义,因为会造成解析2次代码(1次解析惯例js代码,1次解析传入构造函数中的字符串) */var sum = new Function('num1', 'num2', 'return num1+num2')
10.1 箭头函数
- 任何能够应用函数表达式的中央都能够应用箭头函数
let arrowSum = (a, b) => { return a + b}let functionExpressionSum = function (a, b) { return a + b}console.log(arrowSum(5, 8)) // 13console.log(functionExpressionSum(5, 8)) // 13
- 如果只有1 个参数,能够不必括号(多个参数或无参数必须用括号)
let double = (x) => { return x * 3}console.log(double(3)) // 9
- 能够不必大括号,如果这样则箭头前面只能有 1 行代码(赋值操作或表达式),且隐式返回这行代码的值(不能有
return
)
let person = {}let setName = (obj) => (obj.name = 'Matt') // 相当于 { return obj.name = 'Matt' }// let setName = (obj) => { return (obj.name = 'Matt') } // 用大括号的写法setName(person)console.log(person.name) // 'Matt'
箭头函数不实用的场合:
- 不能应用
arguments
、super
和new.target
- 不能用作构造函数
- 没有
prototype
属性()
- 不能应用
10.2 函数名
- 函数名是指向函数的指针,一个函数能够有多个名称
function sum(num1, num2) { return num1 + num2}console.log(sum(10, 10)) // 20var anotherSum = sum // 应用不带括号的函数名是拜访函数指针,而非调用函数console.log(anotherSum(10, 10)) // 20sum = null // 切断sum与函数的关系console.log(anotherSum(10, 10)) // 20,anotherSum()仍可失常调用// console.log(sum(10, 10)) // 会报错,sum is not a function
- ES6 所有函数对象都裸露一个只读的
name
属性,默认保留函数标识符(字符串化的函数名) - 无函数名则标识成空字符串,应用
Function
构造函数创立则标识成anonymous
function foo() {} // 函数申明let bar = function () {} // 函数表达式let baz = () => {} // 箭头函数console.log(foo.name) // 'foo'console.log(bar.name) // 'bar'console.log(baz.name) // 'baz'console.log((() => {}).name) // 空字符串console.log(new Function().name) // 'anonymous'
- 如果函数是一个获取函数、设置函数或应用
bind()
实例化,标识符前会加上一个前缀
console.log(foo.bind(null).name) // 'bound foo' ,标识符前加前缀let dog = { years: 1, get age() { return this.years }, set age(newAge) { this.years = newAge },}let propertyDescriptor = Object.getOwnPropertyDescriptor(dog, 'age') // 获取属性描述符console.log(propertyDescriptor) // { get: [Function: get age], set: [Function: set age], enumerable: true, configurable: true }console.log(propertyDescriptor.get.name) // 'get age',标识符前加前缀console.log(propertyDescriptor.set.name) // 'set age',标识符前加前缀
10.3 了解参数
- JS 函数不关怀传入的参数个数和数据类型
- 可在非箭头函数外部拜访
arguments
类数组对象,获得每个参数(如arguments[0]
) - JS 函数的命名参数在调用时不必必须匹配函数签名,不存在验证命名参数的机制
function sayHi(name, message) { console.log(`Hello ${name}, ${message}`)}function sayHi2(name, message) { console.log(`Hello ${arguments[0]}, ${arguments[1]}`)}function sayHi3() { // 没有命名参数 console.log(`Hello ${arguments[0]}, ${arguments[1]}`)}sayHi('Jake', 'How are you') // 'Hello Jake, How are you'sayHi2('Jake', 'How are you') // 'Hello Jake, How are you'sayHi3('Jake', 'How are you') // 'Hello Jake, How are you'
- 能够通过
arguments.length
查看传入参数的个数
function howManyArgs() { console.log(arguments.length) // 查看参数个数}howManyArgs('string', 45) // 2howManyArgs() // 0howManyArgs(12) // 1
arguments
对象能够跟命名参数一起应用
function doAdd(num1, num2) { if (arguments.length === 1) { console.log(num1 + 10) } else if (arguments.length === 2) { console.log(arguments[0] + num2) }}doAdd(10) // 20,10+10doAdd(30, 20) // 50,30+20
arguments
对象的值始终会与命名参数同步- 在内存中中离开(非拜访同一个地址),仅值同步
- 调用函数时未传的参数,不会因为
arguments
扭转而扭转(始终是 undefined)
function args(num1, num2) { arguments[1] = 10 console.log(num1, num2)}args(2, 3) // 2 10function args2(num1, num2) { arguments[1] = 10 console.log(num1, num2)}args2(2) // 2 undefined,调用函数时只传入一个参数,批改arguments不会扭转第二个命名参数,仍旧是undefinedfunction args3(num1, num2) { num1 = 10 console.log(arguments[0], arguments[1])}args3(2, 3) // 10 3
严格模式下,
arguments
会有一些变动:arguments
对象的值与命名参数不再同步,批改arguments
不再对命名参数产生影响- 在函数中重写
arguments
对象会报错,代码也不会执行
function strictArgs(num1, num2) { 'use strict' arguments[1] = 10 console.log(num1, num2) // arguments = [] // SyntaxError: Unexpected eval or arguments in strict mode,不能重写arguments}strictArgs(2, 3) // 2 3,arguments与命名参数不再同步
- 箭头函数不能拜访
arguments
,只能拜访命名参数
let bar2 = (num1, num2) => { // console.log(arguments[0], arguments[1]) // Uncaught ReferenceError: arguments is not defined console.log(num1, num2)}bar2(2, 3) // 2 3
- JS 中的所有参数都是按值传递的,如果把对象作为参数传递,传递的值是这个对象的援用(仍是按值传递)
10.4 没有重载
- JS 的函数没有签名,因而没有重载,后定义的同名函数会笼罩先定义的
function addSomeNumber(num) { return num + 100}function addSomeNumber(num) { return num + 200}let result = addSomeNumber(100)console.log(result) // 300// 把函数名当成指针let addSomeNumber2 = function (num) { return num + 100}addSomeNumber2 = function (num) { return num + 200}let result2 = addSomeNumber2(100)console.log(result2) // 300
10.5 默认参数值
- ES5 及以前,实现默认参数的罕用办法是检测某个参数是否等于
undefined
function makeKing(name) { name = typeof name !== 'undefined' ? name : 'Henry' // 检测参数name是否为undefined,如果是则赋值 return `King ${name} VIII`}console.log(makeKing()) // 'King Henry VIII'console.log(makeKing('James')) // 'King James VIII'
- ES6 及当前反对显式定义默认参数,在函数定义中的参数后赋值即可
function makeKing2(name = 'Henry') { return `King ${name} VIII`}console.log(makeKing2()) // 'King Henry VIII'console.log(makeKing2('James')) // 'King James VIII'
- 应用默认参数时,
arguments
对象不反映参数默认值,只反映传给函数的参数,批改命名参数不会影响arguments
对象
function makeKing3(name = 'Henry') { name = 'Louis' // 批改命名参数 return `King ${arguments[0]}`}console.log(makeKing3()) // 'King undefined',传给函数的参数为undefinedconsole.log(makeKing3('James')) // 'King James',传给函数的参数为'James'
- 能够应用调用函数的返回值作为默认参数值,计算默认值的函数在未传相应参数时被调用
let romanNumerals = ['Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ', 'Ⅴ', 'Ⅵ']let ordinality = 0function getNumerals() { return romanNumerals[ordinality++] // 每次调用后递增}function makeKing4(name = 'Henry', numerals = getNumerals()) { return `King ${name} ${numerals}`}console.log(makeKing4()) // 'King Henry Ⅰ',未传numerals参数,调用函数getNumerals()console.log(makeKing4('James', 'Ⅸ')) // 'King James Ⅸ',已传numerals参数,不调用函数console.log(makeKing4()) // 'King Henry Ⅱ',未传numerals参数,调用函数getNumerals()console.log(makeKing4()) // 'King Henry Ⅲ',未传numerals参数,调用函数getNumerals()
- 箭头函数同样能够应用默认参数,只有一个参数时不能省略括号
let makeKing5 = (name = 'Henry') => `King ${name}`console.log(makeKing5()) // 'King Henry'
默认参数作用域与暂时性死区
- 给参数定义默认值,实际上相当于应用
let
关键字按程序申明变量一样
function makeKing6(name = 'Henry', numerals = 'Ⅷ') { return `King ${name} ${numerals}`}// 应用let按程序申明变量function makeKing7() { let name = 'Henry' let numerals = 'Ⅷ' return `King ${name} ${numerals}`}
- 后定义默认值的参数能够援用先定义的参数;反之会因为暂时性死区报错(
let
申明的变量不会在作用域中被晋升)
function makeKing8(name = 'Henry', numerals = name) { return `King ${name} ${numerals}`}console.log(makeKing8()) // 'King Henry Henry'function makeKing9(name = numerals, numerals = 'Ⅷ') { return `King ${name} ${numerals}`}// console.log(makeKing9()) // ReferenceError: Cannot access 'numerals' before initialization
- 参数存在本人的作用域,不能援用函数体的作用域
function makeKing10(name = 'Henry', numerals = defaultNumeral) { let defaultNumeral = 'Ⅷ' return `King ${name} ${numerals}`}// console.log(makeKing10()) // ReferenceError: defaultNumeral is not defined
10.6 参数扩大与收集
- ES6 新增扩大操作符
...
,能够用于调用函数时传参和定义函数参数
10.6.1 扩大参数
- 可对可迭代对象利用扩大操作符并将其作为一个参数传入,会将可迭代对象拆分并传入迭代返回的每个值
let values = [1, 2, 3, 4]function getSum() { let sum = 0 for (let i = 0; i < arguments.length; i++) { sum += arguments[i] } return sum}console.log(getSum(...values)) // 10,1+2+3+4
- 可在扩大操作符后面、候面再传其余的值
console.log(getSum(-1, ...values)) // 9,-1+1+2+3+4console.log(getSum(...values, 5)) // 15,1+2+3+4+5console.log(getSum(-1, ...values, 5)) // 14,-1+1+2+3+4+5console.log(getSum(...values, ...[5, 6, 7])) // 28,1+2+3+4+5+6+7
arguments
对象仍依照调用函数时传入的参数接管每一个值
function countArgs() { console.log(arguments.length)}countArgs(-1, ...values) // 5countArgs(...values, 5) // 5countArgs(-1, ...values, 5) // 6countArgs(...values, ...[5, 6, 7]) // 7
- 应用扩大操作符的同时,也能够应用默认参数
function getProduct(a, b, c = 1) { return a * b * c}console.log(getProduct(...[1, 2])) // 2,1*2*1console.log(getProduct(...[1, 2, 3])) // 6,1*2*3console.log(getProduct(...[1, 2, 3, 4])) // 6,1*2*3let getSum2 = (a, b, c = 0) => { return a + b + c}console.log(getSum2(...[0, 1])) // 1,0+1console.log(getSum2(...[0, 1, 2])) // 3,0+1+2console.log(getSum2(...[0, 1, 2, 3])) // 3,0+1+2
10.6.2 收集参数
- 能够应用扩大操作符把不同长度的独立参数组合为一个数组
function getSum3(...values) { return values.reduce((pre, cur) => pre + cur, 0)}console.log(getSum3(1, 2, 3)) // 6
- 因为收集参数的后果可变,因而只能把它作为最初一个参数
- 收集参数的后面如果还有命名参数,则只收集其余参数;若没收集到则取得空数组
function getProduct(...values, lastValue) {} // SyntaxError: Rest parameter must be last formal parameterfunction ignoreFirst(firstValue, ...values) { console.log(values)}ignoreFirst() // [],没收集到ignoreFirst(1) // [],没收集到ignoreFirst(1, 2) // [2],收集其余参数ignoreFirst(1, 2, 3) // [2, 3],收集其余参数
- 箭头函数反对收集参数的定义形式,可用其实现与应用
arguments
一样的逻辑
let getSum4 = (...values) => values.reduce((pre, cur) => pre + cur, 0)console.log(getSum4(1, 2, 3, 4)) // 10
- 收集参数不影响
arguments
对象,仍反映调用时传给函数的参数
function getSum5(...values) { console.log(arguments.length) // 4 console.log(arguments) // [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 } console.log(values) // [ 1, 2, 3, 4 ]}getSum5(1, 2, 3, 4)
10.7 函数申明与函数表达式
- js 引擎在代码开始执行之前,解析器通过函数申明晋升(function declaration hoisting)的过程,将申明函数放到源代码树的顶部,使其在执行任何代码之前可用(能够拜访);而函数表达式则必须等到解析器执行到所在代码行才被解释执行。
- 函数申明和函数表达式的惟一区别就是什么时候能够通过变量拜访函数
console.log(sumDeclare(10, 10)) // 函数申明会提前function sumDeclare(num1, num2) { return num1 + num2}console.log(sumExpression(10, 10)) // 函数表达式不会提前,会报错,sumExpression is not a functionlet sumExpression = function (num1, num2) { return num1 + num2}
10.8 函数作为值
- 函数名在 ECMAScript 中是变量,函数即可作为另一个函数的参数,也可在一个函数中返回另一个函数
- 如果是拜访函数指针而非调用函数,必须不带括号
function callSomeFunction(someFunction, someArgument) { return someFunction(someArgument)}function add10(num) { return num + 10}let result3 = callSomeFunction(add10, 10) // 拜访函数的指针而不是执行函数,add10不带括号console.log(result3) // 20function getGreeting(name) { return 'Hello,' + name // Hello,Nicholas}let result4 = callSomeFunction(getGreeting, 'Nicholas') // 拜访函数的指针而不是执行函数,getGreeting不带括号console.log(result4) // 'Hello,Nicholas'
- 依据数组对象的某个对象属性进行排序:定义一个依据属性名来创立比拟函数的函数
function arraySort(key, sort) { return function (a, b) { if (sort === 'asc' || sort === undefined || sort === '') { // 正序:a[key] > b[key] if (a[key] > b[key]) return 1 else if (a[key] < b[key]) return -1 else return 0 } else if (sort === 'desc') { // 倒序:a[key] < b[key] if (a[key] < b[key]) return 1 else if (a[key] > b[key]) return -1 else return 0 } }}var userList = [ { name: 'Tony', id: 3 }, { name: 'Tom', id: 2 }, { name: 'Jack', id: 5 },]console.log(userList.sort(arraySort('id'))) // [{ name: 'Tom', id: 2 },{ name: 'Tony', id: 3 },{ name: 'Jack', id: 5 }],按 id 正序排列console.log(userList.sort(arraySort('id', 'desc'))) // [{ name: 'Jack', id: 5 },{ name: 'Tony', id: 3 },{ name: 'Tom', id: 2 }],按 id 倒序排列console.log(userList.sort(arraySort('name'))) // [{ name: 'Jack', id: 5 },{ name: 'Tom', id: 2 },{ name: 'Tony', id: 3 }],按 name 正序排列
总结 & 问点
- 函数是什么?函数有哪几种定义形式?
- 箭头函数在什么状况下能够不必参数的括号?什么状况下能够不必函数体的大括号?
- 箭头函数为什么不能用作构造函数或定义原型办法?
- 函数名是什么?一个函数能够有多少函数名?如何获取函数标识符?
- 函数外部的 arguments 对象是什么?其应用有哪些特点和限度?
- 如何了解并证实 JS 的函数没有重载?
- ES5 及之前、ES6 及之后,别离如何定义函数的默认参数?
- arguments 对象与函数默认参数有什么关联?如何了解默认参数的作用域与暂时性死区?
- 扩大操作符有什么作用?定义参数时应用其对 arguments 有什么影响?
- 箭头函数如何实现与 arguments 一样的逻辑,获取每个参数?
- 函数申明与函数表达式有什么区别?
- 写一段代码,依据对象数组的某个对象属性进行排序,可依据参数决定排序属性及升/降序