JS 总结之函数、作用域链

栏目: JavaScript · 发布时间: 5年前

内容简介:JavaScript 用 function 关键字来声明一个函数:变体:函数表达式:这种没有函数名的函数被称为匿名函数表达式。

JavaScript 用 function 关键字来声明一个函数:

function fn () {

}
复制代码

变体:函数表达式:

var fn = function () {

}
复制代码

这种没有函数名的函数被称为匿名函数表达式。

‍ return

函数可以有返回值

function fn () {
  return true
}
复制代码

位于 return 之后的任何代码都不会执行:

function fn () {
  return true
  console.log(false) // 永远不会执行
}
fn() // true
复制代码

没有 return 或者只写 return,函数将返回 undefined:

function fn () {
}
fn() // undefined
// 或者
function fn () {
  return
}
fn() // undefined
复制代码

⛹ 参数

函数可以带有限个数或者不限个数的参数

// 参数有限
function fn (a, b) {
  console.log(a, b)
}
// 参数不限
function fn (a, b, ..., argN) {
  console.log(a, b, ..., argN)
}
复制代码

没有传值的命名参数,会被自动设置为 undefined

// 参数有限
function fn (a, b) {
  console.log(b) // undefined
}
fn(1)
复制代码

:rowboat: arguments

函数可以通过内部属性 arguments 这个 类数组 的对象来访问参数, 即便没有命名参数

// 有命名参数
function fn (a, b) {
  console.log(arguments.length) // 2
}
fn(1, 2)

// 无命名参数
function fn () {
  console.log(arguments[0], arguments[1], arguments[2]) // 1, 2, 3
}
fn(1, 2, 3)
复制代码

:golf:️ 长度

arguments 的长度由传入的参数决定,并不是定义函数时决定的。

function fn () {
  console.log(arguments.length) // 3
}
fn(1, 2, 3)
复制代码

如果按定义函数是决定个的,那么此时的 arguments.length 应该为 0 而不为 3。

同步

arguments 对象中的值会自动反应到对应的命名参数,可以理解为同步,不过并不是因为它们读取了相同的内存空间,而 只是保持值同步而已

function fn (a) {
  console.log(arguments[0]) // 1
  a = 2
  console.log(arguments[0]) // 2
  arguments[0] = 3
  console.log(a) // 3
}
fn(1)
复制代码

严格模式下,重写 arguments 的值会导致错误。

callee

通过 callee 这个指针访问拥有这个 arguments 对象的函数

function fn () {
  console.log(arguments.callee) // fn
}
fn()
复制代码

类数组

长的跟数组一样,可以通过下标访问,如 arguments[0],却 无法使用数组的内置方法 ,如 forEach 等:

function fn () {
  console.log(arguments[0], arguments[1]) // 1, 2
  console.log(arguments.forEach) // undefined
}
fn(1, 2)
复制代码

通过对象那章知道, 可以用 call 或者 apply 借用函数 ,所以 arguments 可以借用数组的内置方法:

function fn () {
  Array.prototype.forEach.call(arguments, function (item) {
    console.log(item)
  })
}
fn(1, 2)
// 1
// 2
复制代码

对于如此诡异的 arguments,我觉得还是少用为好。

this、 prototype

具体查看总结:

按值传递

引用《JavaScript 高级程序设计》4.1.3 的一句话:

ECMAScript 中所有函数的参数都是按值传递的,也就是说,把函数外部的值复制给函数内部的参数,就和把一个变量复制到另一个变量一样。

:trumpet: 基本类型的参数传递

基本类型的传递很好理解,就是把变量复制给函数的参数,变量和参数是完全独立的两个个体:

var name = 'jon'
function fn (a) {
  a = 'karon'
  console.log('a: ', a) // a: karon
}
fn(name)
console.log('name: ', name) // name: jon
复制代码

用表格模拟过程:

栈内存 堆内存
name, a jon

将 a 复制为其他值后:

栈内存 堆内存
name jon
a karon

:violin: 引用类型的参数传递

var obj = {
  name: 'jon'
}
function fn (a) {
  a.name = 'karon'
  console.log('a: ', a) // a: { name: 'karon' }
}
fn(obj)
console.log(obj) // name: { name: 'karon' }
复制代码

嗯?说好的按值传递呢?我们尝试把 a 赋值为其他值,看看会不会改变了 obj 的值:

var obj = {
  name: 'jon'
}
function fn (a) {
  a = 'karon'
  console.log('a: ', a) // a: karon
}
fn(obj)
console.log(obj) // name: { name: 'jon' }
复制代码

:guitar: 真相浮出水面

参数 a 只是复制了 obj 的引用,所以 a 能找到对象 obj,自然能对其进行操作。一旦 a 赋值为其他属性了,obj 也不会改变什么。

用表格模拟过程:

栈内存 堆内存
obj, a 引用值 { name: 'jon' }

参数 a 只是 复制了 obj 的引用,所以 a 能找到存在堆内存中的对象,所以 a 能对堆内存中的对象进行修改后:

栈内存 堆内存
obj, a 引用值 { name: 'karon' }

将 a 复制为其他值后:

栈内存 堆内存
obj 引用值 { name: 'karon' }
a 'karon'

因此,基本类型和引用类型的参数传递也是按值传递的

:bicyclist: 作用域链

理解作用域链之前,我们需要理解 执行环境变量对象

:poultry_leg: 执行环境

执行环境定义了变量或者函数有权访问的其它数据,可以把执行环境理解为一个大管家。

执行环境分为 全局执行环境函数执行环境 ,全局执行环境被认为是 window 对象。而函数的执行环境则是由函数创建的。

每当一个函数被执行,就会被推入一个环境栈中,执行完就会被推出,环境栈最底下一直是全局执行环境,只有当关闭网页或者推出浏览器,全局执行环境才会被摧毁。

:meat_on_bone: 变量对象

每个执行环境都有一个变量对象,存放着环境中定义的所有变量和函数,是作用域链形成的前置条件。但我们无法直接使用这个变量对象,该对象主要是给 JS 引擎使用的。具体可以查看 《JS 总结之变量对象》

:fried_shrimp: 作用域链的作用

作用域链 属于执行环境的一个变量,作用域链收集着所有有序的变量对象,函数执行环境中函数自身的变量对象(此时称为活动对象)放置在作用域链的最前端,如:

scope: [函数自身的变量对象,变量对象1,变量对象2,..., 全局执行环境的变量对象]
复制代码

作用域链保证了对执行环境有权访问的所有变量和函数的有序访问。

var a = 1
function fn1 () {
  var b = 2
  console.log(a,b) // 1, 2
  function fn2 () {
    var c = 3
    console.log(a, b, c) // 1, 2, 3
  }
  fn2()
}
fn1()
复制代码

对于 fn2 来说,作用域链为: fn2 执行环境fn1 执行环境全局执行环境 的变量对象(所有变量和函数)。

对于 fn1 来说,作用域链为: fn1 执行环境全局执行环境 的变量对象(所有变量和函数)。

总结为一句:函数内部能访问到函数外部的值,函数外部无法范围到函数内部的值。引出了闭包的概念,查看总结: 《JS 总结之闭包》

:horse_racing: 箭头函数

ES6 新语法,使用 => 定义一个函数:

let fn = () => {}
复制代码

当只有一个参数的时候,可以省略括号:

let fn = a => {}
复制代码

当只有一个返回值没有其他语句时,可以省略大括号:

let fn = a => a

// 等同于
let fn = function (a) {
  return a
}
复制代码

返回对象并且没有其他语句的时候,大括号需要括号包裹起来, 因为 js 引擎认为大括号是代码块

let fn = a => ({ name: a })

// 等同于
let fn = function (a) {
  return { name: a }
}
复制代码

箭头函数的特点:

  1. 没有 this ,函数体内的 this 是定义时外部的 this
  2. 不能被 new ,因为没有 this
  3. 不可以使用 arguments ,可以使用 rest 代替
  4. 不可以使用 yield 命令 ,因此箭头函数不能用作 Generator 函数。

:rocket: 参考


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大学程序设计课程与竞赛训练教材

大学程序设计课程与竞赛训练教材

吴永辉、王建德 / 机械工业出版社 / 2013-6 / 69.00

本书每章为一个主题,实验内容安排紧扣大学算法和数学的教学,用程序设计竞赛中的算法和数学试题作为实验试题,将算法和数学的教学与程序设计竞赛的解题训练结合在一起;在思维方式和解题策略的训练方面,以问题驱动和启发式引导为主要方式,培养读者通过编程解决问题的能力。 本书特点: 书中给出的234道试题全部精选自ACM国际大学生程序设计竞赛的世界总决赛以及各大洲赛区现场赛和网络预赛、大学程序设计竞......一起来看看 《大学程序设计课程与竞赛训练教材》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具