关注前端小讴,浏览更多原创技术文章

相干代码 →

10.9 函数外部

  • ES5 中函数外部有 2 个非凡对象 argumentsthis,1 个外部属性 caller
  • ES6 新增new.target属性

10.9.1 arguments

  • arguments是一个类数组对象,蕴含调用函数时传入的所有参数
  • 只有以funciton关键字定义函数时才会有arguments对象(箭头函数没有
  • 对象有一个callee属性,指向arguments所在函数的指针(留神:是指针即函数名,而非函数)

    • 严格模式下,拜访arguments.callee会报错
// 递归函数:计算阶乘function factorial(num) {  if (num <= 1) return 1  else return num * factorial(num - 1)}// 应用arguments.callee解耦函数逻辑与函数名function factorial(num) {  if (num <= 1) return 1  else return num * arguments.callee(num - 1) // callee指向arguments对象所在函数}let trueFactorial = factorial // 保留函数的指针// 重写factorial函数,trueFactorial指针不变factorial = function () {  return 0}console.log(trueFactorial(5)) // 120,已用arguments.callee解除函数体内代码与函数名的耦合,仍能失常计算console.log(factorial(5)) // 0,函数已被重写

10.9.2 this

  • 规范函数中,this指向调用函数的上下文对象,即函数执行的环境对象(全局作用域指向window
window.color = 'red' // vscode是node运行环境,无奈辨认全局对象window,测试时将window改为globallet o = { color: 'blue' }function sayColor() {  console.log(this.color)}sayColor() // 'red',this指向全局对象o.sayColor = sayColoro.sayColor() // 'blue',this指向对象o
  • 箭头函数中,this指向定义函数的上下文对象,即该函数内部的环境对象
let sayColor2 = () => {  console.log(this.color) // this指向定义sayColor2的上下文,即全局对象}sayColor2() // 'red',this指向全局对象o.sayColor2 = sayColor2o.sayColor2() // 'red',this指向全局对象
  • 事件回调定时回调中调用某个函数时,this指向的并非想要的对象,将回调函数写成箭头函数可解决问题
function King() {  this.royaltyName = 'Henry'  setTimeout(() => {    console.log(this.royaltyName) // 箭头函数,this指向定义函数的上下文,即King()的函数上下文  }, 1000)}function Queen() {  this.royaltyName = 'Elizabeth'  setTimeout(function () {    console.log(this.royaltyName) // 规范函数,this指向调用函数的上下文,即setTimeout()的函数上下文  }, 1000)}new King() // 'Henry',1秒后打印new Queen() // undefined,1秒后打印

10.9.3 caller

  • ES5 定义了 caller 属性,指向调用以后函数的函数(全局作用域中为 null)
function callerTest() {  console.log(callerTest.caller)}callerTest() // null,在全局作用域种调用function outer() {  inner()}function inner() {  console.log(inner.caller)}outer() // [Function: outer],在outer()调用// 解除耦合function inner() {  console.log(arguments.callee.caller) // arguments.callee指向arguments所在函数的指针,即inner}outer() // [Function: outer],在outer()调用
  • arguments.caller的值始终是undefined,这是为了辨别arguments.caller和函数的caller
  • 严格模式下,拜访arguments.caller为函数的caller属性赋值会报错
function inner2() {  console.log(arguments.caller) // undefined  console.log(arguments.callee) // [Function: inner2]}inner2()

10.9.4 new.target

  • ES6 在函数外部新增new.target属性,检测函数是否应用new关键字调用

    • 未应用new调用,new.target的值是undefined
    • 应用new调用,new.target的值是被调用的构造函数
function King2() {  if (!new.target) {    console.log(new.target, 'King2 must be instantiated using "new"')  } else {    console.log(new.target, 'King2 instantiated using "new"')  }}new King2() // [Function: King2] 'King2 instantiated using "new"'King2() // undefined 'King2 must be instantiated using "new"'

10.10 函数属性与办法

  • 函数蕴含 2 个属性:lengthprototype

    • length 保留函数心愿接管的命名参数的个数
    function nameLength(name) {  return name}function sumLength(sum1, sum2) {  return sum1 + sum2}function helloLength() {  return 'Hello'}console.log(nameLength.length, sumLength.length, helloLength.length) // 1 2 0
    • prototype 指向函数的原型对象,保留函数所有实例办法不可枚举(应用for-in无奈发现)
    console.log(Array.prototype) // 在浏览器中查看Array的原型对象,蕴含sort()等办法console.log(Object.keys(Array)) // [],Array构造函数本身所有可枚举的属性console.log(Object.getOwnPropertyNames(Array)) // [ 'length', 'name', 'prototype', 'isArray', 'from', 'of' ],Array构造函数本身的所有属性
  • 函数有 3 个办法:apply()call()bind()

    function sumPrototype(num1, num2) {  return num1 + num2}
    • apply()call()都会以指定的this值调用函数,即设置调用函数时函数体内this指向
    • apply()接管 2 个参数:① 运行函数的作用域(指定 this);② 参数数组(实例或 arguments 对象均可)
    function applySum1(num1, num2) {  return sum.apply(this, arguments) // 传入arguments对象}function applySum2(num1, num2) {  return sum.apply(this, [num1, num2]) // 传入数组实例}console.log(applySum1(10, 10)) // 20console.log(applySum2(10, 10)) // 20
    • call()接管若干参数:① 运行函数的作用域(指定 this);残余参数一一传入
    function callSum(num1, num2) {  return sum.call(this, num1, num2) // 一一传入每个参数}console.log(callSum(10, 10)) // 20
    • apply()call() 真正弱小的中央在于可能裁减函数运行的作用域,即管制函数体内this
    window.color = 'red' // vscode是node运行环境,无奈辨认全局对象window,测试时将window改为globallet o2 = { color: 'blue' }function sayColor3() {  console.log(this.color)}sayColor3() // 'red',this指向全局对象sayColor3.call(this) // 'red',this指向全局对象sayColor3.call(window) // 'red',this指向全局对象,测试时将window改为globalsayColor3.call(o2) // 'blue',this指向对象o2
    • Function.prototype.apply.call(),将函数原型的apply办法利用call()进行绑定(可通过Reflect.apply()简化代码)
    let f1 = function () {  console.log(arguments[0] + this.mark)}let o3 = {  mark: 95,}f1([15]) // '15undefined',this指向f1的函数上下文,this.mark为undefinedf1.apply(o3, [15]) // 110,将f1的this绑定到o3Function.prototype.apply.call(f1, o3, [15]) // 110,函数f1的原型对象的apply办法,利用call进行绑定Reflect.apply(f1, o3, [15]) // 110,通过指定的参数列表发动对指标函数的调用,三个参数(指标函数、绑定的this对象、实参列表)
    • bind()创立一个新的函数实例,其this绑定传给bind()的对象
    let o4 = { color: 'blue' }function sayColor4() {  console.log(this.color)}let bindSayColor = sayColor4.bind(o4) // 创立实例bindSayColor,其this被绑定给o4sayColor4() // 'red',this指向全局对象bindSayColor() // 'blue',this被绑定给对象o4

10.11 函数表达式

  • 函数申明的要害特点是函数申明晋升,即函数申明会在代码执行之前取得定义
sayHi() // 'Hi',先调用后申明function sayHi() {  console.log('Hi')}
  • 函数表达式必须先赋值再应用,其创立一个匿名函数(function后没有标识符)再把它赋值给一个变量

    • 匿名函数的name属性是空字符串
sayHi2() // ReferenceError: Cannot access 'sayHi2' before initialization,不能先调用后赋值let sayHi2 = function sayHi() {  console.log('Hi')}
  • 函数申明与函数表达式的区别在于晋升,在条件块防止应用函数申明能够应用函数表达式
let condition = falseif (condition) {  function sayHi3() {    console.log('true')  }} else {  function sayHi3() {    console.log('false')  }}sayHi3() // 不同浏览器的后果不同,防止在条件块中应用函数申明let sayHi4if (condition) {  sayHi4 = function () {    console.log('true')  }} else {  sayHi4 = function () {    console.log('false')  }}sayHi4() // false,能够在条件块中应用函数表达式
  • 创立函数并赋值给变量可用于在一个函数中把另一个函数当作值返回
/** * 依照对象数组的某个object key,进行数组排序 * @param {String} key 要排序的key * @param {String} sort 正序/倒序:asc/desc,默认为asc */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 是什么?arguments.callee 指向哪里?写一段代码,示意函数名与函数逻辑解耦的阶乘函数
  • this 在规范函数和箭头函数的指向有什么不同?在事件回调或定时回调中,为什么更适宜应用箭头函数?
  • 函数的 caller 属性指向哪里?arguments.caller 的值是什么?严格模式下 caller 有哪些限度?
  • new.target 的作用和值别离是什么?
  • 函数有哪些属性?其指向和用法别离是什么?
  • 请用代码证实 apply()、call()、bind()是如何裁减函数作用域的,并解释 Function.prototype.apply.call()的含意
  • 函数申明和函数表达式最大的区别是什么?如何了解申明晋升?
  • 写一段代码,依据对象数组的某个对象属性进行排序,可依据参数决定排序属性及升/降序