一篇文章告诉你JavaScript的作用域和函数该这样理解

目录
  • 一、作用域
    • 1.1 局部作用域
      • 1、函数作用域
      • 2、块作用域
    • 1.2 全局作用域
    • 1.3 作用域链
    • 1.4、闭包
    • 1.5 变量提升
  • 二、函数
    • 2.1、函数提升
    • 2.2、函数参数
      • 1、默认参数
      • 2、动态参数
      • 3、剩余参数
    • 2.3、箭头函数
  • 总结

一、作用域

【解释】: 规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问。

【分类】:

局部作用域全局作用域

1.1 局部作用域

【分类】:

数作用域块作用域

1、函数作用域

【解释】: 在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。

【示例】:

<script>
  // 声明 counter 函数
  function counter(x, y) {
    // 函数内部声明的变量
    let s = x + y;
    console.log(s); // 18
  }
  // 调用 counter 函数
  counter(10, 8);
  // 访问变量 s
  console.log(s); // 报错 外部无法访问函数内部的变量
</script>

【总结】:

  • 函数内部声明的变量,在函数外部无法被访问
  • 函数的参数也是函数内部的局部变量
  • 不同函数内部声明的变量无法互相访问
  • 函数执行完毕后,函数内部的变量实际被清空了

2、块作用域

【解释】: 在 JavaScript 中使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。

【示例】:

<script>
  {
    // age 只能在该代码块中被访问
    let age = 18;
    console.log(age); // 正常
  }
  // 超出了 age 的作用域
  console.log(age); // 报错
  let flag = true;
  if(flag) {
    // str 只能在该代码块中被访问
    let str = 'hello world!';
    console.log(str); // 正常
  }
  // 超出了 age 的作用域
  console.log(str); // 报错
  for(let t = 1; t <= 6; t++) {
    // t 只能在该代码块中被访问
    console.log(t); // 正常
  }
  // 超出了 t 的作用域
  console.log(t); // 报错
</script>

【常量值】: JavaScript 中除了变量外还有常量,常量与变量本质的区别是 【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。

【示例】:

<script>
  // 必须要有值
  const version = '1.0';
  // 不能重新赋值
  // version = '1.1';
  // 常量值为对象类型
  const user = {
    name: '小明',
    age: 18
  }
  // 不能重新赋值
  user = {};
  // 属性和方法允许被修改
  user.name = '小小明';
  user.gender = '男';
</script>

【总结之let、var、const】:

let 声明的变量会产生块作用域,var 不会产生块作用域

const声明的常量也会产生块作用域

不同代码块之间的变量无法互相访问

推荐使用 let 或 const

开发中 let 和 const 经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const 声明成常量。

关键字 块级作用域 变量提升 初始值 更改值 通过window调用
let × √ - ×
const × √ × ×
var × -

1.2 全局作用域

【解释】: <script> 标签和 js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。

【示例】:

<script>
  // 此处是全局
  function sayHi() {
    // 此处为局部
  }
  // 此处为全局
</script>
<script>
  // 此处是全局
  function sayHi() {
    // 此处为局部
  }
  // 此处为全局
</script>

全局作用域中声明的变量,任何其它作用域都可以被访问

<script>
    // 全局变量 name
    let name = '小明';
  	// 函数作用域中访问全局
    function sayHi() {
      // 此处为局部
      console.log('你好' + name);
    }
    // 全局变量 flag 和 x
    let flag = true;
    let x = 10;
  	// 块作用域中访问全局
    if(flag) {
      let y = 5;
      console.log(x + y); // x 是全局的
    }
</script>

【总结】:

  • 为 window 对象动态添加的属性默认也是全局的,不推荐!
  • 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
  • 尽可能少的声明全局变量,防止全局变量被污染

1.3 作用域链

【解释】: 函数内部允许创建新的函数,f 函数内部创建的新函数 g,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域

【示例】:

<script>
  // 全局作用域
  let a = 1;
  let b = 2;
  // 局部作用域
  function f() {
    let c;
    // 局部作用域
    function g() {
      let d = 'yo';
    }
  }
</script>
<script>
  // 全局作用域
  let a = 1;
  let b = 2;
  // 局部作用域
  function f() {
    let c;
    // let a = 10;
    console.log(a); // 1 或 10
    console.log(d); // 报错
    // 局部作用域
    function g() {
      let d = 'yo';
      // let b = 20;
      console.log(b); // 2 或 20
    }
    // 调用 g 函数
    g()
  }
  console.log(c); // 报错
  console.log(d); // 报错
  f();
</script>

【总结】:

  • 嵌套关系的作用域串联起来形成了作用域链
  • 相同作用域链中按着从小到大的规则查找变量
  • 子作用域能够访问父作用域,父级作用域无法访问子级作用域(就近原则)

1.4、闭包

【解释】: 闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。

【好处】: 可以把一个变量使用范围延伸

【示例】:

<script>
  function foo() {
    let i = 0;
    // 函数内部分函数
    function bar() {
			console.log(++i);
    }
    // 将函数做为返回值
    return bar;
  }
  // fn 即为闭包函数
  let fn = foo();
  fn(); // 1
</script>

【总结】:

  • 闭包本质仍是函数,只不是从函数内部返回的
  • 闭包能够创建外部可访问的隔离作用域,避免全局变量污染
  • 过度使用闭包可能造成内存泄漏‘
  • 回调函数也能访问函数内部的局部变量。

1.5 变量提升

【解释】: 允许在变量声明之前即被访问

【示例】:

<script>
  // 访问变量 str
  console.log(str + 'world!');
  // 声明变量 str
  var str = 'hello ';
</script>
let和var都有提升,但是let定义的变量没有赋值之前是不可以使用、var可以使用是undefined

【总结】:

  • 变量在未声明即被访问时会报语法错误
  • 变量在声明之前即被访问,变量的值为 `undefinedlet
  • 声明的变量不存在变量提升,推荐使用let`【也有人认为具有提升但是不赋值不能使用】
  • 变量提升出现在相同作用域当中
  • 实际开发中推荐先声明再访问变量

二、函数

2.1、函数提升

【解释】: 函数在声明之前即可被调用

【示例】:

<script>
  // 调用函数
  foo();
  // 声明函数
  function foo() {
    console.log('声明之前即被调用...');
  }
  // 不存在提升现象
  bar();
  var bar = function () {
    console.log('函数表达式不存在提升现象...');
  }
</script>

【总结】:

  • 函数提升能够使函数的声明调用更灵活
  • 函数表达式不存在提升的现象
  • 函数提升出现在相同作用域当中

2.2、函数参数

1、默认参数

【示例】:

<script>
  // 设置参数默认值
  function sayHi(name="小明", age=18) {
    document.write(`<p>我叫${name},我今年${age}岁了。</p>`);
  }
  // 调用函数
  sayHi();
  sayHi('小红');
  sayHi('小刚', 21);
</script>

【总结】:

  • 声明函数时为形参赋值即为参数的默认值
  • 如果参数未自定义默认值时,参数的默认值为 undefined
  • 调用函数时没有传入对应实参时,参数的默认值被当做实参传入

2、动态参数

【解释】: arguments` 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。

【示例】:

<script>
  // 求生函数,计算所有参数的和
  function sum() {
    // console.log(arguments);
    let s = 0;
    for(let i = 0; i < arguments.length; i++) {
      s += arguments[i];
    }
    console.log(s);
  }
  // 调用求和函数
  sum(5, 10); // 两个参数
  sum(1, 2, 4); // 两个参数
</script>

【注意】:

  • arguments是一个伪数组
  • arguments的作用是动态获取函数的实参

3、剩余参数

【语法及示例】:

<script>
  function config(baseURL, ...other) {
    console.log(baseURL);
    // other 是真数组,动态获取实参
    console.log(other);
  }
  // 调用函数
  config('http://baidu.com', 'get', 'json');
</script>

2.3、箭头函数

【解释】: 箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。

【示例】:

<script>
  // 箭头函数
  let foo = () => {
    console.log('^_^ 长相奇怪的函数...');
  }
  // 调用函数
  foo();
  // 更简洁的语法
  let form = document.querySelector('form');
  form.addEventListener('click', ev => ev.preventDefault());
</script>

【总结】:

  • 箭头函数属于表达式函数,因此不存在函数提升
  • 箭头函数只有一个参数时可以省略圆括号 `()
  • 箭头函数函数体只有一行代码时可以省略花括号 {},并自动做为返回值被返回
  • 箭头函数中没有 arguments,只能使用 ... 动态获取实参
  • 涉及到this的使用,不建议用箭头函数

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • JavaScript函数作用域链分析

    本文实例分析了JavaScript函数作用域链.分享给大家供大家参考.具体分析如下: 作用域链: JavaScript的每个函数function都有自己的作用域,使用Active Object(简称AO)活动对象来保存,在相互嵌套的函数中形成了作用域链,如下图所示: 作用域链就是从里到外的AO链 变量的寻找: 函数fn3中使用的变量,如在fn3作用域内寻找不到,则往外层fn2作用域寻找,以此类推,直到全局对象window 代码演示如下: var c = 5; function t1(){ var

  • js函数内变量的作用域分析

    本文实例分析了js函数内变量的作用域.分享给大家供大家参考.具体分析如下: 先看一个函数实例: 复制代码 代码如下: <html> <head> </head> <body> <script type="text/javascript"> var a = 5; var c = 3; function t(){  var a = 6;  var b = 10;  document.write(a+'-----'+b);  doc

  • 深入理解js函数的作用域与this指向

    函数的作用域与this指向是js中很重要的一部分,理清这点东西需要个逻辑,看看我的逻辑怎么样... 下面是个提纲,可以直接挑你感兴趣的条目阅读. • 函数的定义方式:直接定义(window下,内部定义),对象的方法,对象原型的方法: • 函数的调用方式:直接调用,call/apply,with • 对于直接定义的函数和对象的方法,作用域默认状态下是它的定义处的作用域链. • 对于直接定义的函数,this指向window. • 对于对象的方法,this指向实例化对象(对应于实例化对象默认返回thi

  • 浅谈JavaScript的函数及作用域

    函数和作用域是JavaScript的重要组成部分,我们在使用JavaScript编写程序的过程中经常要用到这两部分内容,作为初学者,我经常有困惑,借助写此博文来巩固下之前学习的内容. (一)JavaScript函数 JavaScript函数是指一个特定代码块,可能包含多条语句,可以通过名字来供其他语句调用以执行函数包含的代码语句. 1.JavaScript创建函数的方法有两种: 函数声明: function funcDeclaration(){ return 'A is a function';

  • JavaScript匿名函数之模仿块级作用域

    匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途. 匿名函数:就是没有函数名的函数. 函数的定义,首先简单介绍一下函数的定义,大致可分为三种方式 第一种:这也是最常规的一种 function double(x){ return 2 * x; } 第二种:这种方法使用了Function构造函数,把参数列表和函数体都作为字符串,很不方便,不建议使用. var double = new Function('x', 'return 2 * x;'); 第三种: var

  • JavaScript 基础函数_深入剖析变量和作用域

    函数定义和调用 定义函数,在JavaScript中,定义函数的方式如下: function abs(x){ if(x >=0){ return x; }else{ return -x; } } 上述abs() 函数的定义如下: function 指出这是一个函数定义; abs 是函数的名称: (x) 括号内列出函数的参数,多个参数以,分隔: {...}之间的代码是函数体,可以包含若干语句,甚至可以没有任何语句. 注意:函数体内部的语句在执行时,一旦执行到return 时,函数就执行完毕,并将结果

  • 一篇文章告诉你JavaScript的作用域和函数该这样理解

    目录 一.作用域 1.1 局部作用域 1.函数作用域 2.块作用域 1.2 全局作用域 1.3 作用域链 1.4.闭包 1.5 变量提升 二.函数 2.1.函数提升 2.2.函数参数 1.默认参数 2.动态参数 3.剩余参数 2.3.箭头函数 总结 一.作用域 [解释]: 规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问. [分类]: 局部作用域全局作用域 1.1 局部作用域 [分类]: 数作用域块作用域 1.函数作用域 [解释]: 在函数内部声明的变量只能在函数内部被访问,外

  • 一篇文章搞定JavaScript类型转换(面试常见)

    为啥要说这个东西?一道面试题就给我去说它的动机. 题如下: var bool = new Boolean(false); if (bool) { alert('true'); } else { alert('false'); } 运行结果是true!!! 其实啥类型转换啊,操作符优先级啊,这些东西都是最最基本的.犀牛书上有详细的介绍.但我很少去翻犀牛书的前5章... 比如说优先级那块儿,很多书都教育我们,"不用去背诵优先级顺序,不确定的话,加括号就行了."平常我们写代码时也确实这么做的

  • 一篇文章搞懂JavaScript正则表达式之方法

    咱们来看看JavaScript中都有哪些操作正则的方法. RegExp RegExp 是正则表达式的构造函数. 使用构造函数创建正则表达式有多种写法: new RegExp('abc'); // /abc/ new RegExp('abc', 'gi'); // /abc/gi new RegExp(/abc/gi); // /abc/gi new RegExp(/abc/m, 'gi'); // /abc/gi 它接受两个参数:第一个参数是匹配模式,可以是字符串也可以是正则表达式:第二个参数是

  • 一篇文章搞懂:词法作用域、动态作用域、回调函数及闭包

    前言 把以前一直只限于知道,却不清晰理解的这几个概念完完整整地梳理了一番.内容参考自wiki页面,然后加上自己一些理解. 词法作用域和动态作用域 不管什么语言,我们总要学习作用域(或生命周期)的概念,比如常见的称呼:全局变量.包变量.模块变量.本地变量.局部变量等等.不管如何称呼这些作用域的范围,实现它们的目的都一样: (1)为了避免名称冲突; (2)为了限定变量的生命周期(本文以变量名说事,其它的名称在规则上是一样的). 但是不同语言的作用域规则不一样,虽然学个简单的基础就足够应用,因为我们有

  • JavaScript词法作用域与调用对象深入理解

    关于 Javascript 的函数作用域.调用对象和闭包之间的关系很微妙,关于它们的文章已经有很多,但不知道为什么很多新手都难以理解.我就尝试用比较通俗的语言来表达我自己的理解吧. 作用域 Scope Javascript 中的函数属于词法作用域,也就是说函数在它被定义时的作用域中运行而不是在被执行时的作用域内运行.这是犀牛书上的说法.但"定义时"和"执行(被调用)时"这两个东西有些人搞不清楚.简单来说,一个函数A在"定义时"就是 functio

  • 一篇文章告诉你如何用事件委托实现JavaScript留言板功能

    用事件委托实现留言板功能. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width

  • 一篇文章弄懂javascript中的执行栈与执行上下文

    前言 作为一个前端开发人员,弄清楚JavaScript的执行上下文有助于我们理解js中一些晦涩的概念,比如闭包,作用域,变量提升等等. 执行栈 执行栈用于存储代码执行期间创建的所有执行上下文.具有FILO接口,也被称为调用栈. 当JavaScript代码被运行的时候,会创建一个全局上下文,并push到当前执行栈.之后当发生函数调用的时候,引擎会为函数创建一个函数执行上下文并push到栈顶.引擎会先执行调用栈顶部的函数,当函数执行完成后,当前函数的执行上下文会被移除当前执行栈.并移动到下一个上下文

  • 一篇文章弄懂javascript内存泄漏

    1.什么是内存泄漏 在了解什么是内存泄漏之前, 我们应该要对内存是什么有个概念, 随机存取存储器(英语:Random Access Memory,缩写:RAM)是与 CPU 直接交换数据的内部存储器.它可以随时读写, 而且速度很快,通常作为操作系统或其他正在运行中的程序的临时资料存储介质. 什么是内存泄漏? : 程序不再需要使用的内存, 但是又没有及时释放, 就叫做内存泄漏! 然后在理解泄漏之前, 我们的了解下内存的管理, 在一些底层语言中, 如C语言, 内存是需要开发者自己分配和释放的, 通过

  • 一篇文章看懂JavaScript中的回调

    前言 回调函数是每个前端程序员都应该知道的概念之一.回调可用于数组.计时器函数.promise.事件处理中. 本文将会解释回调函数的概念,同时帮你区分两种回调:同步和异步. 回调函数 首先写一个向人打招呼的函数. 只需要创建一个接受 name 参数的函数 greet(name).这个函数应返回打招呼的消息: function greet(name) { return `Hello, ${name}!`; } greet('Cristina'); // => 'Hello, Cristina!'

  • 一篇文章告诉你JAVA Mybatis框架的核心原理到底有多重要

    目录 持久层的那些事 什么是 JDBC JDBC 原理 什么是 Mybatis Mybatis 与 JDBC 的关系 SqlSession SqlSessionFactory SqlSessionFactoryBuilder Configuration MappedStatement Executor ParameterHandler StatementHandler ResultSetHandler Interceptor Mybatis 关键词说明 Mybatis 架构设计 基础支持层 反射

随机推荐