JavaScript高级程序设计-函数学习
函数
函数实际上是对象,每个函数都是 Function 类型的实例。因为函数是对象,所以函数名就是指向函数对象的指针。
1.箭头函数
let sum = (a, b) => a + b 复制代码
ES6 新增使用胖箭头(=>)语法定义函数表达能力。
如果只有一个参数可以不用括号
省略大括号可隐式返回这行代码的值
箭头函数不能使用
arguments
、super
、new.Target
,也不能用作构造函数,此外也没有 prototype 属性
2.函数名
函数名就是指向函数的指针,这意味着一个函数可以有多个名称。 ECMAScript6 的所有函数对象都会暴露一个只读的 name 属性,其中包含关于函数的信息。即便函数没有名称,也会如实显示成空字符串。如果他是使用 Function 构造函数创建的,则会标识成"anonymous"
3.理解参数
函数及不关心传入的参数个数,也不关心参数的数据类型。 函数的参数在内部表现为一个数组,被调用时总会接收一个数组。在非箭头函数中可以在内部访问arguments
对象,从中取每个参数值。 arguments
对象是个类数组对象(不是 Array 实例),可以使用中括号语法访问元素(arguments[0]),要确定参数的个数可以使用arguments.length
注:
严格模式下不能再给 arguments 赋值,重写 arguments 对象会导致语法错误
箭头函数中不能使用 arguments 属性,只能通过定义的命名参数访问
4.没有重载
ECMAScript 的函数不能像传统编程那样重载。如果定义了两个同名函数,则后定义的会覆盖先定义的
5.默认参数值
function getCat(name = '66') { return `${name}是只猫` } 复制代码
在使用默认参数是,arguments 对象的值不反映参数的默认值,只反映传给函数的参数
function getCat(name = '66') { return `${arguments[0]}是只猫` } console.log(getCat()) // undefined是只猫 console.log(getCat('51')) // 51是只猫 复制代码
默认参数值也可以调用函数返回值
let romanNumerals = ['I', 'II', 'III'] let ordinality = 0 function getNumerals() { // 每次调用后递增 return romanNumerals[ordinality++] } function getCat(name = '66', numerals = getNumerals()) { return `${name}是只猫${numerals}` } console.log(getCat()) // 66是只猫I console.log(getCat('51')) // 51是只猫II console.log(getCat('建军')) // 建军是只猫IIII 复制代码
6.函数声明与函数表达式
函数声明:JavaScript 引擎在任何代码执行之前,都会先读取函数声明,并在执行上下文中生成函数定义。 函数声明会在任何代码执行之前仙贝读取并添加到执行上下文,这个过程叫做
函数声明提升(function declaration hoisting)
。在执行代码前,JavaScript 引擎会先执行一遍扫描,把发现的函数声明提升到源代码树的顶部。函数表达式:必须等到代码执行到他那一行,才会在执行上下文中生成函数定义。
除了函数什么时候真正有定期这个区别外,这两种语法是等价的。
7.函数内部
函数内部存在三个特殊的对象:arguments
、this
和new.target
。
7.1 arguments
类数组对象,包含调用函数时传入的所有参数,只有使用 function 关键字定义的函数才有。 arguments 还有一个callee
属性,是一个纸箱arguments
对象所在函数的指针。 例:下方的阶乘函数一遍是递归调用,但要正确执行必须保证函数名是 factorial,从而导致了紧密耦合,使用 arguments.callee 就可以让函数逻辑与函数名解耦。
function factorial(num) { if (num <= 1) { return 1 } else { return num * factorial(num - 1) } } function factorialArg(num) { if (num <= 1) { return 1 } else { return num * arguments.callee(num - 1) } } 复制代码
7.2 this
在标准函数中,this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为 this 值。 在箭头函数中,this 引用的是定义箭头函数的上下文。
7.3 caller
caller 引用的是调用当前函数的函数,在全局作用于中盗用则为 null
function outer() { inner() } function inner() { console.log(inner.caller) console.log(arguments.callee.caller) } outer() 复制代码
7.4 new.target
ECMAScript6 新增了检测函数是否使用 new 关键字调用的 new.target 属性,如果正常调用,值是 undefined,如果使用 new 关键字调用,则 new.target 将引用被调用的构造函数
function Cat() { if (!new.target) { throw 'dog' } console.log('cat') } new Cat() // cat Cat() // dog 复制代码
8.函数属性与方法
每个函数都有两个属性:length 和 prototype。
length 保存函数定义的命名参数的个数。
prototype 属性是保存引用类型所有实例方法的地方,这意味着toString()
、valueOf()
等方法实际上都保存在 prototype 上,进而所有实例共享。
函数还有两个方法:apply()
和call()
。这两个方法都会以指定的 this 值来调用函数,即会设置调用函数时函数体内 this 对象的值。
apply()方法接收两个参数:函数内 this 的值和一个参数数组。第二个参数可以是 Array 的实例,但也可以是 arguments 对象。 call()方法和 apply()的作用一样,只是传参形式不同,第一个参数是 this 值,剩下的参数要逐个传递
function sum(num1, num2) { return num1 + num2 } function applySum(num1, num2) { return sum.apply(this, [num1, num2]) } function callSum(num1, num2) { return sum.call(this, num1, num2) } 复制代码
使用 call()和 apply()的好处是可以将任意对象设置为任意函数的作用于,这样对象可以不用关心方法。
9.递归
递归函数通常的形式是一个函数通过名称调用自己。
const factorial = function f(num) { if (num <= 1) { return 1 } else { return num * f(num - 1) } } 复制代码
创建一个命名函数表达式 f(),然后将它赋值给变量 factorial。
10.闭包
闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
在调用一个函数时,,会为这个函数调用创建一个执行上下文,并创建一个作用域链。然后用 arguments 和其他命名参数来初始化这个函数的活动对象。外部函数的活动对象是内部函数作用域链上的第二个对象。这个作用域链一直向外串起了所有包含函数的活动对象,直到全局执行上下文才终止。
11.this 对象
如果内部函数没有使用箭头函数定义,this 对象会在运行时绑定执行函数的上下文。
如果在全局函数中调用,this 在非严格模式下等于 window,严格模式下等于 undefined。
如果作为某个对象的方法调用,则 this 等于这个对象。
匿名函数不会绑定到某个对象,所以 this 会指向 window。但在闭包的写法下,有时会产生误解:
window.identity = 'window' let object = { identity: 'object', getIdentityFunc() { return function () { return this.identity } } } console.log(object.getIdentityFunc()()) // 'window' 复制代码
每个函数在被调用时都会自动创建两个特殊变量:this 和 arguments。内部函数永远不可能直接访问外部函数的这两个变量。但是,如果把 this 保存到闭包可以访问的另一个变量中是可以的,或者是用bind
绑定外部函数的 this
let object = { identity: 'object', getIdentityFunc() { let that = this return function () { return that.identity } } } // 或 let object = { identity: 'object', getIdentityFunc() { return function () { return this.identity }.bind(this) } } 复制代码
12.小结
函数表达式与函数声明是不一样的。函数声明要求写出函数名称,而函数表达式并不需要。没有名称的函数表达式也被称为匿名函数。
ES6 新增了箭头函数
JavaScript 中函数定义与调用时的参数极其灵活。arguments 对象,以及 ES6 新增的扩展操作符,可以实现函数定义和调用的完全动态化。
函数内部暴露了很多对象和引用,涵盖了函数被谁调用,使用什么调用,以及调用时传入了什么参数等信息。
闭包的作用域链中包含自己的一个变量对象,然后是包含函数的变量对象,知道全局上下文的变量对象。
函数作用于及其中的左右变量在函数执行完毕后都会比销毁
闭包在被函数返回值后,起作用于会一直保存在内存中,直到闭包被销毁。
函数可以在创建后立即调用,执行其中代码之后却不留下对函数的引用。
立即调用的函数表达式如果不在包含作用域中将返回值赋给一个变量,则其包含的所有变量都会被销毁。
作者:澶渊
链接:https://juejin.cn/post/7031073539532783653