JS高级相关

JSCore01

第三阶段

  • JS高级 --- 面试时重点考察的 JS 核心技巧 都在这里 (难度极高)

    • 学习技巧: 重在理解, 不要边看边写

  • DOM -- 用JS灵活操作 HTML+CSS 的技术

    • 可以让网页变化起来

    • 学习技巧: 多写多练

  • jQuery

  • Vue

声明提升

亮亮老师阶段: var 声明的变量 带有声明提升

学习 JS语言 必须知道的一个 语言特征: 其他编程语言都没有的特征

为什么其他语言都没有这个特征?? 因为 此特征特别 垃圾

JS语言在运行时, 实际代码分两次运行.

  • 第一次: 先找到所有的声明操作(var/function/let/const/class), 提取到顶部

  • 第二次: 再执行提升后的代码

面试题中经常考: 考察你的JS基础如何!

JS中:眼见不一定为实, 底层会隐式 先提升执行提升后的代码

 

var 和 function 都是声明, 谁先谁后?

  • JS的作者从来没有明确说明过到底谁先谁后

  • 但是先后顺序不重要

    • 函数提升时 会携带函数体

    • var提升时, 只提升变量声明没有值

    • 不管谁先写谁后写, 提升后的值 都是函数

      function a(){}
      var a
      ​
      var b 
      function b(){}

作用域

作用域 是 一类拥有特殊操作的 对象类型的 称呼

  • 全局

  • 局部/函数

  • 块级

  • 脚本

宿主环境: JS运行在哪个平台上, 这个平台就称为 JS的宿主环境

  • node.js: 操作数据库/网络请求/操作文件系统...

  • 浏览器: 提供了操作浏览器相关的 技能包

    • 存储在 window 对象中, 此对象也称为 全局作用域

    • 因为 利用 var/function 声明的变量 都会自动存储在window里

var 具有全局变量污染特征:

  • 全局: window

  • var声明的变量会自动存储在 window 里,导致window原有的结构被污染了!

函数作用域

函数在运行时, 会自动创建一个对象, 用来保存函数中 声明的变量

这个特殊的对象就称为: 函数作用域 也叫 局部作用域

局部作用域 和 全局作用域 是独立关系, 互相不影响

因为函数运行时创建 作用域对象, 在 函数运行结束后 会自动销毁

所以, 必须利用 断点调试 功能才能查看到 这个特殊的作用域对象

脚本作用域

块级作用域

作用域链

闭包

闭包 Closure, 就是函数作用域的别名

当一个函数出生时, 会自动保存其所在的 词法作用域 (所在的作用域们)

存储在函数的 scopes 属性里

为什么要存??? 以后函数不管在哪里使用, 永远都是曾经的少年--携带固有属性

这些固有的作用域中, 函数作用域 称为 闭包

当你听到闭包 这个称号: 就应该知道 这是个函数作用域 被保存在另一个函数的 scopes 里了

  • 例如案例中, c函数在 b 函数里声明的, b函数作用域保存在 cscopes

    此时 b函数作用域 就是 c函数的闭包

今日内容回顾

JS高级: 了解JS的本质,底层设计 -- 面试必考

  • 声明提升

    • JS语言独有的特征, 其他语言没有 -- 此设计特别

    • JS代码在真正运行时, 会先调整代码的结构, 然后才会执行

    • 把 声明操作(var/function/let/const) 提升到作用域的顶部, 然后再执行

    • 注意: 千万不要按照正常人从上到下的顺序来阅读JS代码

  • 作用域

    • 作用域的本质就是个对象类型的变量, 只是因为这个对象类型特殊

    • window: 浏览器提供的对象类型, 存储了所有浏览器的功能代码. 称为全局对象

      • 使用 var 和 function 声明的变量, 自动存储在window里

    • 局部作用域: 函数在调用时, 会自动产生一个对象类型, 用于存储函数中声明的变量, 其会在函数执行完毕时, 自动销毁

    • 脚本作用域: ES6 2015年开始, 使用 let /const / class 在 script 中声明的, 存储在脚本区

    • 块级: ES6 2015年开始, 在 {} 中 使用 let/const/class 关键词声明的变量, 存储在 块级作用域 对象里

  • 作用域链

    • 根据代码的书写, 函数中使用一个变量时, 优先从自身作用域中查找, 自身没有 则到上层作用域中查找 -- 直到找到位置(就近原则)

  • 闭包

    • 函数在声明时,会保存其所在的词法环境(所有父作用域) 到 scopes 属性里

    • 如果是函数作用域, 就称为闭包

    • 简单粗暴理解: 外层函数 是内层函数的闭包 -- 必须是函数套函数的方式

    • 缺点: 浪费内存. 外层函数的作用域不会自动销毁, 会被内层函数保存

    • 作用: 在ES6 2015年之前, 用于避免全局变量污染.

      • 利用闭包, 为函数提供私有的变量, 避免全局变量污染

今日代码

声明提升

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>声明提升</title>
</head>
<body>
<!-- HTML中, 有两个特殊的标签 -->
<!-- style: 其中用于书写 css 代码 -->
<!-- script: 其中用于书写 JavaScript 代码 -->
<script>
// function: 用于声明函数
// 函数就是把 一段代码保存起来, 起了个名字
// 注意: 函数必须调用之后, 才能执行{}中的代码
// 调用函数的格式: 函数名(). 通过()来触发
// JS的反人类设计: 不能按照正常人的阅读顺序来理解代码
// 你所见到的 并非 JS 实际运行时的模样
// 因为JS拥有: 声明提升的机制
// 声明: 制作出来一个变量 就叫声明 -- 变量出生的时候
// 用于声明变量的关键词: var/function + let/const/class
a()
function a(){
console.log(111)
 }
var a //声明了变量a
var b //声明了变量b
function c(){} //声明了函数c
</script>
<script>
// 在阅读JS代码时, 一定不要当正常人
// 正常人: 从上向下阅读
// JS具有声明提升特征: 先隐式调整代码的顺序, 然后再执行
function b(){
console.log(111)
 }
b()
function b(){
console.log(222)
 }
b()
</script>
</body>
</html>

 小练习

​
<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
​
<body>
  <script>
    a()
​
    function a() {
      console.log(111);
    }
​
    function a() {
      console.log(222);
    }
    a()
​
    a = function () { console.log(333) }
    a()
  </script>
</body>
​
</html>
<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
​
<body>
  <script>
    // = :称为赋值符号. 把一个值保存到一个变量里
    // 如果变量中 已经有值了 会怎样,  旧的会清理掉, 换成新的
    var a = 5
    var a = 8
    console.log(a);
​
    function b() { }
    console.log(b);
​
    b = 666
    console.log(b);
​
​
​
​
    // 函数作为 表达式的一部分, 称为函数表达式
    // 表达式中的函数: 不会提升
    b = function () { console.log(222); }
    console.log(b);
​
    // 整个表达式 就是一个函数, 可以提升
    function c() { }
  </script>
​
  <script>
    console.log('-------------------------');
​
    ff = function () { console.log(222); }
    ff()
​
    function ff() {
      console.log(1111);
    }
    ff()
​
​
​
  </script>
</body>
​
</html>
<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
​
<body>
  <script>
    // 此处是合写格式
    // var a;  a = 5;
    var a = 5
​
    function a() {
      console.log(111);
    }
​
    console.log(a);
  </script>
</body>
​
</html>
​
<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
​
<body>
  <script>
    // 为一个变量重新赋值, 只能用 = 进行
    function a() { }
    // 声明a, 如果a中没有值, 就是undefined
    // 而不是: 声明a的同时 赋值成 undefined
​
    // 但是 a 中存储了函数, 所以a的值就是函数
    var a  //这是赋值吗?? 不是!!!  声明操作
​
​
    // 没有 = 就不会给a赋值
​
​
    console.log(a);
  </script>
</body>
​
</html>

作用域

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>作用域</title>
</head>
​
<body>
  <script>
    // JS有4种作用域: 全局 函数 脚本 块级
    // 专业名词: 宿主环境 -- 被寄生的就叫宿主
    // JS语言有两种常见的宿主: node.js 和 浏览器
    // JS寄生在宿主上之后, 就可以使用宿主的各种功能
    // JS寄生在浏览器上时, 就拥有了操作浏览器的各种技能
    // 这些技能存储在 浏览器提供的 window 对象属性中
    console.log(window);
    console.log(typeof window); // object:对象类型
​
    // 作用域 就是 一个特殊的对象类型的值
    // window对象,就是浏览器提供的全局作用域
    // 使用 var 在脚本中声明的变量, 就会存储在全局作用域 window对象里
    var aa = 'AAA'
​
    function aab() { }
​
    // 用法1: 读取 全局作用域 window对象中的值
    console.log(window.aa);
    // 用法2: 直接用变量, 默认会从全局作用域获取
    console.log(aa);
  </script>
</body>
​
</html>

局部作用域

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>局部作用域</title>
</head>
​
<body>
  <script>
    // 2015年之前, 即ES6 -- JS的第六次升级版本 前
    // 只有两种作用域: 全局 , 局部(函数)
    function a() {
      var x = 11
      var y = 22
      var z = 33
      console.log(x, y, z);
    }
    // 注意: 函数需要调用才能触发
    // 调用时, 会触发 {} 中的代码, 并制作一个 局部作用域--对象类型
    // 需要浏览器提供的 断掉调试 软件, 来查看函数运行时的作用域
    a()
  </script>
</body>
​
</html>

脚本作用域

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>脚本作用域</title>
</head>

<body>
  <!-- 
    由于 var 在 script 中声明的变量, 自动出现在 window 对象里,
    而 window 对象是浏览器提供的 用于存储 系统方法 的对象

    把自定义的变量存储在 window 中, 会造成全局变量污染
    所在 从 2015年开始 的 第六个JS版本, 称为 ES6 
    提供了新的脚本作用域, 专门存储 自定义的 变量
  -->
  <script>
    // var在 script 声明的变量, 存储在 window 对象里
    // 和系统提供的内容 混合在一起: 全局变量污染
    var aa = 'AAA'

    // let/const : ES6中提供的 声明变量的 关键词
    // 在 script 中声明的变量, 存储在 script 对象里 而非 window
    // 避免的全局变量污染
    let bb = 'BBB'

    // 取舍:
    // 理论上: 推荐用 let/const 代替 var  -- var就特性,被淘汰!
    // 实际上: 为了兼容性的考虑, 对于2015年之前的浏览器--IE8
    //        -- 有些政府项目中, 要考虑适配 旧版本浏览器, 只能用var

    console.log('-----');
  </script>
</body>

</html>

块级作用域

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>块级作用域</title>
</head>

<body>
  <script>
    // 块级: 由 ES6 中提供的作用域, 类似 局部作用域
    // {} 配合 let/const 使用, 就会生成独立的块级作用域对象
    // 但是 var 对块级无效, 依然在全局中

    // 代码中: if  for while switch 都有 {}, 会产生块级
    {
      let a = 5
      const b = 6
      var c = 7
      console.log('....');
    }

    function d() {
      // 函数的{} 不会产生块
      // 依然是 函数作用域
      let a

      {
        // 依然形成块级作用域
        let b
        console.log(b);
      }

      console.log(a);
    }

    d()
  </script>
</body>

</html>

作用域链

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>作用域链</title>
</head>

<body>
  <script>
    // JS中, 代码结尾的分号 非必备, 是可选的
    // 由于大家越来越懒.. 现在很多人习惯不写 分号

    // undefined: 声明了但是没赋值
    // undeclare: a is not defined 代表变量不存在, 即没声明

    // 当使用一个变量时, 会按照就近原则, 到作用域链中查找并使用
    // 细节: 查找时 向上层作用有中查找
    // 找祖宗 不找 儿子
    var a = 11

    function b() {
      var a = 22;

      // 函数的自调用  (function(){})()
      // 细节: 自调用写法 和 上一行代码之间 必须有 分号 间隔
      (function () {
        var a = 33

        function c() {
          var a = 44
        }
        c()

        // 如果 a = 33 不写, 会到上层作用域中查找
        // 不会到 c 中查找
        console.log(a) //??
      })()
    }
    b()
  </script>
</body>

</html>

综合练习

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>综合练习</title>
</head>

<body>
  <!-- 声明提升: JS独有的特征, 先隐式调整代码的结构, 把 声明操作 var/function 提升到作用域的顶部, 然后再运行 -->

  <!-- 作用域: 一些特殊的对象.  window全局  函数触发后-局部 -->

  <!-- 作用域链: 使用一个变量时, 就近原则 从自身向上层作用域查找 -->

  <script>
    var a = 10

    function b() {
      a = 20
      console.log(a);
    }
    b()

    console.log(a);
  </script>

  <script>
    // 代码提升后的样子如下:
    function b() {
      // 变量a在哪??   函数自身没有用 var 声明变量a
      // 所以 到上层作用域中查找: 找到了window中的a
      a = 20 // 把 a 的值替换成 20
      console.log(a) // 打印出20
    }
    // var a = 10
    var a


    //
    a = 10
    b() //触发函数体中的代码
    console.log(a);
  </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>综合练习</title>
</head>

<body>
  <script>
    var a = 10

    function b() {
      // var: 用于在当前作用域中声明变量
      // 当前在函数作用域中, 所以声明了变量a
      // var a = 20
      var a
      // 因为当前作用域中有 a , 所以就给当前作用域的a赋值
      a = 20
      console.log(a) //20
    }

    b()
    console.log(a);
  </script>
</body>

</html>

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>综合练习</title>
</head>

<body>
  <script>
    var a = 10

    function b() {
      // 考点: 声明提升;  var a 实际运行时会先提升到作用域的顶部
      // var a
      a = 20
      console.log(a);
      var a
    }

    b()
    console.log(a);
  </script>
</body>

</html>

闭包特性

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>闭包特征</title>
</head>

<body>
  <script>
    // 类似: 亮亮出生时 在 成家, 所以姓成
    // 如果亮亮以后 入赘到 涛哥家里.. 是否依然姓成?
    // 答案: 依然是,  出生时就锁死. 携带在自身的固有属性

    // 函数 在声明时, 即 function 书写时, 就已经把自身要用到的各种变量 都携带在身上, 不管以后在哪里使用, 都是固有的属性

    // scope: 作用域  scopes:作用域们
    // 函数的 scopes 属性中, 存储来函数 出生时, 所在的作用域们
    // closure: 闭包 -- 函数作用域的别名
    // global: 全局

    var a = 10

    function b() {
      var a = 20

      // c出生时: 在b作用域中, b出生时在全局里
      function c() {
        // 这里的a ,根据作用域链的元素, 使用 b函数作用域中的a
        // 但是 把 c 作为返回值, 传递到外部, 为什么依然能打印出a?
        console.log(a) //打印出什么?
      }

      return c // 返回c函数
    }

    var c = b() // b的返回值, 存储在 变量c 中
    console.log(c);

    c() // 触发全局中的c函数 打印出什么?

    // 打印有很多种方式, 最常见:log --具有美化功能
    // console.log(c);

    // dir: direct直接, 输入函数原本的模样
    console.dir(c);
  </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>闭包特征</title>
</head>

<body>
  <!-- 
    JS的固有设定: 函数在出生时 会保存 其所在的所有作用域 到 函数的scopes 属性里

    例如: 亮亮老师在出生的时候, 就会保存: 爸爸是谁,妈妈是谁, 爷爷奶奶, 姥爷姥姥 , 北京的5套房子.  永远都携带在身上
   -->

  <script>
    {
      // let 配合 {} 会形成块级作用域
      let a = 10
      // alt+上/下 : 移动当前行

      // 把函数c 放在另一个函数中声明
      function b() {
        var x = 999

        // b函数作用域 是 c函数的闭包, 存储在 c的 scopes 属性里
        function c() {
          // 细节: 函数只存对自身有用的, 
          // 如果没有使用变量a, 则不会存储a 所在的作用域
          console.log(a, x)
        }

        console.dir(c) //利用dir 直接查看函数本体, 展开查看 scopes 属性
      }
      b()
    }
  </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>闭包特性</title>
</head>

<body>
  <!-- 
    闭包: 函数在声明时, 会保存其所在的所有作用域 到 scopes 属性里, 如果这个作用域 是个函数作用域, 称为 闭包Closure

    粗暴理解: 函数保存所有的父元素到自身的scopes属性里

    闭包的形成: 必须是 函数中声明另一个函数

    外层的函数  是 内层函数的 闭包
   -->
  <script>
    // Closure:闭包
    (function () {
      var a = 10

      // 声明函数, 
      function b() { console.log(a) }
      // b的scopes属性中存储了 b函数声明时所在的作用域们 (祖先们)
      // 匿名函数作用域 被保存在 b 的scopes 中,  称为 b的闭包
      // 本质上 闭包就是函数作用域, 只是因为被b存储了, 所以称为闭包

      // 例如: 迪丽热巴.   假如 嫁给了家乐,  称为 家乐的妻子
      // 迪丽热巴 别名是 妻子.  说明是别人的妻子
      console.dir(b)
    })()
  </script>
</body>

</html>

关于函数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>关于函数</title>
</head>

<body>
  <script>
    // 关于函数: 分两种状态 -- 静态  动态

    // 静态的: 制作了一个函数
    function b() {
      // return 返回结果
      // 不写return 则默认是 undefined
      // return 111
    }

    // 打印静态的函数
    console.log(b);

    // 动态: 利用()触发; 打印函数运行后的
    console.log(b());
  </script>
</body>

</html>

闭包的应用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>闭包的应用</title>
</head>

<body>
  <!-- 
    闭包的设计的缺点:
    - 正常 函数执行时 会形成 局部作用域对象, 当函数执行结束时, 这个对象会销毁, 来节省内存
    - 但是: 闭包机制会导致 函数作用域 被保存到另一个函数中, 无法释放. 导致内存的浪费

    所以: 在ES6中, 提供了 块级作用域的写法, 来代替闭包

    由于兼容性 和 习惯: 闭包依然非常常见
   -->

  <script>
    // 闭包作用: 为函数提供私有变量, 避免全局污染 -- ES6之前

    // 例如: 记录一个函数的调用次数
    // var声明的变量, 存储在 window 全局中: 污染全局变量
    // 如何避免全局污染??  let/const -- ES6  2015年出现
    // 2015年之前 靠什么?  需要闭包 -- 提供一个函数作用域来存储变量
    console.log(window);

    // 习惯使用匿名函数自调用, 来提供一个函数作用域
    // 声明变量, 保存函数的返回值; 此时就可以在外部使用b
    var b = (function () {
      // 在匿名函数作用域中, 声明变量: 不在全局区 就没有全局污染
      var count = 0

      function b() {
        // var count = 0
        count++
        console.log('调用了第' + count + '次');
      }
      // 通过return 把b暴露到外部  -- return 大腰子
      return b
    })()

    // 目的是:让count变成私有的, 避免全局污染
    console.dir(b) // 查看 count 是不是存在 scopes 里

    b()
    b()
    b()
    b()
  </script>
</body>

</html>

JSCORE02

JS高级部分: 重在理解

复习

JS高级部分主要讲解: JS底层的设计理念 -- 面试必考

  • 声明提升

    • JavaScript语言 在实际运行时的方式 -- 不要看表面

    • JS代码在实际运行时, 会先隐式调整代码结构, 然后再执行调整过后的代码: 如果按照代码从上向下阅读, 并不准确

    • 声明操作的代码, 会提升到其作用域的顶部, 然后再执行

      • 声明操作: var/function/let/const/class

    • 考点: JS代码运行的 和 你所见的 并非同一个, 隐式调整结构

  • 作用域

    • 作用域本质是个对象类型的变量, 只是因为这个对象类型拥有特殊的功能

    • 4种作用域对象

      • 全局作用域对象: 浏览器作为宿主环境时, 就是 window 属性

        • 使用 var 在 script 中声明的变量 存储在 window 里. 所以有全局变量污染

      • 局部作用域对象: 函数在 被触发时, 会临时生成一个对象, 存储函数中声明的变量. 在函数执行结束时, 自动销毁

      • 脚本作用域对象: ES6 2015 JS的第6个版本 使用 let/const 关键词在 script 里声明的变量

      • 块级作用域: {} 配合 let/const 使用时, 会形成块级作用域

        • if/for/switch/while 都带有{} 会形成块级作用域

  • 作用域链

    • 按照代码的书写位置, 函数中使用一个变量时, 会先在自身作用域中查找, 如果自身没有 则向上级作用域查找 -- 就近原则

  • 闭包

    • 函数在声明时, 会保存其所在的词法作用域到自身的 scopes 属性里. 其中如果词法作用域是 函数作用域 的话, 就称为闭包

      • 词法作用域有4类: 全局, 脚本, 块级, 闭包(局部作用域/函数作用域)

      • 闭包本质就是 局部作用域, 只是在当前场景中, 称为闭包

      • 闭包的形成: 必须是 外部函数 中声明了 内部函数, 此时 外部函数就是内部函数的闭包

      • 闭包的缺点: 本来函数作用域会自动销毁, 由于闭包的保存操作, 不再自动销毁, 浪费内存!

      • 函数分两种状态: 静态 动态

        • 静态: 声明函数 -- 闭包存储在静态的函数中

        • 动态: 调用函数 -- 执行的时候会使用到闭包里的值

    • 闭包的作用:

      • ES6 之前, 只有两种作用域: 全局 / 局部(函数)

      • 通过var 声明的变量, 存储在全局里, 造成全局变量污染

      • 闭包: 可以把变量声明在函数作用域中, 避免全局变量污染

        • 制作私有的变量, 规避全局变量污染

arguments

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>函数的arguments</title>
</head>
​
<body>
  <script>
    // 计算任意数量 参数的和
    function add() {
      // 函数隐式带有一个 arguments 属性, 其中存储了函数收到的所有参数
      // 通过打印可以发现: arguments 是个对象类型
      // 只是属性名是 0 1 2 3 ...  跟数组相似而已
      // 我们称这种样子的对象: 伪数组/ 类(似)数组
      // 像数组 但是没有数组的各种方法  -- 太监 - 像男人..但是不行
      console.log('arguments:', arguments);
​
      var sum = 0
​
      for (var i = 0; i < arguments.length; i++) {
        var value = arguments[i] //下标取值
        sum += value //累加到sum上  sum = sum + value
      }
      console.log(sum);
    }
​
    add(10, 20)
    add(100, 200, 10, 20)
    add(100, 200, 10, 20, 40, 50)
  </script>
</body>
​
</html>

函数的重载

函数的一种制作的技巧: 可以让一个函数 拥有更多的功能, 更加强大

通过判断参数的 个数 或 类型, 来执行不同的逻辑代码

具体实现方式:

  • 依赖函数自带的 arguments 属性: 存储函数接收的所有参数

  • 利用 arguments 来判断 函数接受的参数数量

  • 或者 从arguments 中读取参数, 判断其类型

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>函数的重载 10:07</title>
</head>
​
<body>
  <!-- 函数的重载: 通过判断参数的 数量 或 类型 来执行不同的逻辑代码 -->
  <!-- 优点: 让一个函数具有多种作用, 功能更加强大 -->
​
  <!-- 具体实现方式: 利用 arguments 属性, 来读取参数的个数 或者 某个参数的类型, 通过if判断来执行不同的逻辑代码 -->
​
  <script>
    // 参数是数字类型, 当做时间戳(距离1970年1月1日的秒数)来识别
    // 系统提供的 Date, 其会根据参数的类型不同, 用不同的方式进行转换, 最终得到时间
    var t = new Date(1453243343453)
    console.log(t);
​
    var t = new Date('2020/10/20')
    console.log(t);
​
    // 制作一个求和的函数, 两种作用: 求出多个参数的和 或 数组的和
    function add() {
      // 参数的个数如果是1个 且 是数组类型
      // console.log(arguments);
      // Array.isArray(): 判断值是否为数组类型
      if (arguments.length == 1 && Array.isArray(arguments[0])) {
        // arguments = { 0: [11, 22, 33, 44, 66] }
        var nums = arguments[0]
        // nums = [11, 22, 33, 44, 66]
​
        var sum = 0
        // 数组.length: 获取数组的长度
        for (var i = 0; i < nums.length; i++) {
          sum += nums[i]
        }
        console.log(sum);
      } else {
        // 否则: 一堆数字的场景
        // 例如 arguments: {0: 10, 1: 20, 2: 30,  3: 40, length: 4}
        var sum = 0
        for (var i = 0; i < arguments.length; i++) {
          sum += arguments[i]
        }
        console.log(sum);
      }
    }
​
    // add: 可以传递一堆数字 或 传递 一个数组
    add(10)
    add(10, 20, 30, 40)
    add(10, 20, 30, 40, 6, 7, 8)
​
    add([11, 22, 33, 44, 66])
  </script>
​
  <script>
    // 练习: 制作一个函数, 计算出数字的累加之和
    function getSum() {
      console.log(arguments);
      // arguments: 接受到的参数们
      if (arguments.length == 1) {
        var sum = 0
        // 看下成亮老师讲解的 对象的 下标取值 -- 对象访问器语法
        for (var i = 1; i <= arguments[0]; i++) {
          sum += i
        }
        console.log(sum);
      }
​
      if (arguments.length == 2) {
        var sum = 0
        for (var i = arguments[0]; i <= arguments[1]; i++) {
          sum += i
        }
        console.log(sum);
      }
​
      if (arguments.length == 3) {
        var sum = 0
        for (var i = arguments[0]; i <= arguments[1]; i += arguments[2]) {
          sum += i
        }
        console.log(sum);
      }
    }
​
    // 升级: 如果3个参数, 第三参数代表步长 50 52 54 56... 间隔2
    getSum(50, 200, 2)
    getSum(50, 200, 3)
​
    // 例如:
    getSum(100) //累加出 1 - 100 之间数字的总和
​
    getSum(50, 150) // 累加出 50 - 150 之间的数字总和
​
    getSum(150, 10)
  </script>
</body>
​
</html>

函数部分--高级操作

  • 函数作用域: 函数触发时自动创建的对象, 用于存储函数中声明的变量

  • 作用域链: 函数中使用的变量, 优先使用自身作用域的, 如果自己没有则到上层查找

  • 闭包: 函数在声明时, 把 其所在的作用域们, 保存到scopes属性里

    • 作用域们中的 : 函数作用域 叫闭包

  • 函数隐式具有arguments属性: 存储了函数接受到的所有实参, 类(似)数组类型. 像数组但是没有数组的各种方法 -- 太监: 像男人,但...

  • 技巧-函数重载: 通过判断函数的参数 个数 或 类型, 来执行不同的逻辑代码

    • 具体实现:依赖 arguments 属性来进行

属性访问器

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>对象的访问器语法</title>
</head>
​
<body>
  <script>
    // 对象的访问器语法
    // JS官方文档: MDN  - https://developer.mozilla.org/zh-CN/
​
    // JS提供了两种 属性访问器 语法,  分别是 点号 和 方括号
    var emp = { ename: 'mike', age: 19, 1: 'one' }
    console.log(emp);
​
    // 读取ename属性的值
    console.log(emp.ename) //点语法读取
    console.log(emp['ename']) //方括号语法
​
    // 如何读取 属性1 的值
    // console.log(emp.1); // .1 是小数的表达方式  11.2
    console.log(emp[1]); // 数字只能用 方括号语法
​
    // 错误理解: []下标取值 是数组特有的语法!
    // 正确理解: 由于数组类型的对象, 其下标都是 数字类型, 无法使用 .语法 只能用[]
    console.log(['mike', 'lucy', 'lily']);
​
    // 数组 和 对象的关系是?   数组是对象的一种
    // 就好像  人 和 男人的关系.  男人 是 人的一种
​
    var stu = {}
​
    var a = 'gege'
    // []: 里面是JS代码, a是变量, 其中的值是 'gege'
    stu[a] = '格格'
    // 问 :  格格这个值的 属性名是什么?
    // 选项1: a   选项2: gege.
​
    stu['a'] = '子轩'
    // 问: 子轩的属性名是?
    // 选项1: a     选项2: 'gege'
​
    var a = 'gg'
    stu.a = '家乐'
    // 问: 家乐的属性名是?  1
    // 选项1: a   选项2: gg
​
    // 语法
    // 对象.属性名  : 在点语法中, 就是个属性名, 不会变化
    // 对象[]:  方括号中是 JS 代码
    console.log(stu);
  </script>
</body>
​
</html>

对象是引用类型

内存分两种:

把内存想想成是一本字典/书: 分两个部分 目录 和 详情页

为什么?: 目录记录简单的标题, 其中存储 详情的页数, 通过页数找到存放详细内容的页

  • 目录的内容少, 查询速度快

栈内存: 相当于目录, 存储少量数据, 查询速度快

堆内存: 相当于详情页, 存储大量数据, 拥有一个固定的页数

在JS中, 基础数据类型属于少量数据的范畴

  • number: 123, 12.3, 456

  • boolean: true false

  • null

  • undefined

  • string: 字符串 是由多个单个字符组成的

基本数据类型, 赋值时, 值传递

对象类型: 属于存储很多很多数据的类型, {}中可以存储任意数量的数据

在内存中存储在 堆内存 里, 相当于 书本中的 详情页, 带有一个页号

var obj ={
 xx: xxx,
 xx: xxx,
 xxx:xxx
}

对象是引用(内存地址)类型

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>对象是引用类型</title>
</head>
​
<body>
  <!-- JS的数据类型有哪些? -->
  <!-- 基本数据类型: string boolean number null undefined + symbol bigInt -->
  <!-- 在 ES6 2015年 新增了 symbol(唯一,做属性名用)  bigInt(大整型) -->
  <!-- 引用/对象类型: object -->
​
  <script>
    // 猜猜 b 的值
    // 数字属于 number类型,  属于基础数据类型 -- 赋值时 属于 值传递
    var a = 5
    var b = a
    a = 10
    console.log(b) //5
​
    // 猜猜打印的结果
    var x = { num: 10 }
    var y = x
    x.num = 20
    console.log(y.num);
  </script>
</body>
​
</html>

拷贝/克隆

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>对象的克隆</title>
</head>
​
<body>
  <!-- 克隆: clone  --复制,拷贝 -->
  <script>
    var x = { num: 10, ename: 'mike', age: 18 }
    // 克隆 x 对象: 分两步
    // 1. 创建1个空对象
    var y = {}
    // 2. 遍历x对象, 把其中的属性挨个存入空对象里
    // for..in.. 用于遍历对象
    for (var key in x) {
      // key: 是 x 对象中的属性
      console.log('key:', key);
      var value = x[key] // 对象 方括号取值
      // 把值存储在 y 这个空对象里
      y[key] = value
    }
​
    console.log('x:', x);
    console.log('y:', y);
​
    // 对比是否是同一个
    console.log('x == y:', x == y) //false: 不是同一个
​
    // 因为 x 和y 非同一个, 所以x的修改 不会影响 y
    x.age = 99
    console.log(y.age) // 18
  </script>
​
  <script>
    // 扩展小课堂:
    var a = { num: 10, count: 20 }
​
    var num = 'count'
​
    // 纯语法问题:  -- 成亮老师的JS基础有讲解!
    // 对象.属性名 : 点后面的就是属性名, 不是js代码
    // 对象[JS代码]
​
    console.log(a.num) //10
    // a[num] -> a['count']
    console.log(a[num]) //20
​
  </script>
</body>
​
</html>

函数的this

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>this关键词</title>
</head>
​
<body>
  <!-- this: 代表函数运行时所在的对象 -->
  <!-- 
    关键词 `我` : 代表说话时 的 人
    亮亮: 我今天要给 2204班上课
    娅楠: 我今天休息
    铭铭: 我今天被隔离, 在家直播
​
    这些 `我` 字 都代表什么?
   -->
​
  <script>
    // 全局中声明, 存储在window里
    function show() {
      // this: 这个
      console.log('我是:', this);
    }
​
    show() // 猜猜this
​
    var emp1 = { ename: '亮亮' }
    emp1.show = show // show函数存储到 emp1 里
    emp1.show()
​
    var emp2 = { ename: '娅楠', show: show }
    emp2.show()
​
    var a = 10
    var b = 20
    function c() { console.log(this) }
    // 属性名: 值(JS代码)
    // 简化语法: 属性名和值一样, 可以简化成一个 {b:b} -> {b}
    var obj = { num: 10, count: a, b, c: c }
    // 相当于
    obj.b = b
​
    obj.c() // 打印的this是什么
​
    console.log(obj)
​
    // 粗暴理解: 
    // 对象.方法()  : 方法中的this 就是前面的对象
    // 我.打亮亮() : 主语是   我
    // 涛哥.打亮亮() : 主语是 涛哥
    // 楠姐.打亮亮() : 主语是 楠姐
    // emp2.打亮亮() : this 就是 emp2
    // emp3.打亮亮() : this 就是 emp3
​
​
    var e1 = {
      name: '楠姐',
      打亮亮: function () {
        console.log(this, "在打亮亮");
      }
    }
    e1.打亮亮()
    // 函数中的this: 代表谁在使用
    var e2 = { name: '涛哥', 打亮亮: e1.打亮亮 }
    e2.打亮亮()
​
    // 面试的考点: 存储在window中的属性, 在使用时可以省略前缀
    var 打亮亮 = e1.打亮亮
    // window.打亮亮()
    打亮亮() //没有前缀 触发的方法, 都是window里的
  </script>
</body>
​
</html>

对象的创建方式

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>对象的创建方式</title>
</head>
​
<body>
  <!-- 语法糖: 作者提供的简化语法, 使用起来非常方便, 让人感觉幸福, 所以像吃糖一样甜蜜! -->
​
  <!-- 创建一个对象, 通常分两种方式 -->
  <!-- 1. 字面量: 一种语法糖,简化创建  -->
  <!-- 2. 构造方式 -->
  <script>
    // 数组的语法糖: 字面量
    var names = ['mike', 'lucy', 'lily']
    console.log(names);
​
    // 构造方式
    var names = new Array('tom', 'jerry', 'shirley')
    console.log(names);
​
    // 对象类型
    var emp = { name: '亚楠', age: 18, phone: '18332434344' }
    console.log(emp);
​
    // 构造方式
    var emp = new Object()
    emp.name = '凯凯'
    emp.age = 32
    emp['phone'] = '15534343444'
    console.log(emp);
​
    // 函数
    function aa(a, b) { console.log(a + b) }
​
    // 函数的构造写法:  前几个参数 是形参, 最后一个是函数体
    var bb = new Function('a', 'b', 'console.log(a + b)')
    console.log(bb);
    bb(20, 40)
​
    // 下节课主题: 我们自己做构造函数试一试
  </script>
</body>
​
</html>

this的作用

 

 

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>this的作用 16:10</title>
  <!-- 在对象中复用函数,省内存 -->
</head>

<body>
  <script>
    // 矩形
    // 1个矩形, 长10  宽5   拥有方法area 来计算面积
    var r1 = {
      length: 10,
      width: 5,
      area: function () {
        // 长 x 宽
        return this.width * this.length
      }
    }

    console.log(r1);
    console.log(r1.area());

    // 创建r2对象, 长 20, 宽 30   area方法 算面积
    var r2 = {
      length: 20,
      width: 30,
      area: function () {
        return this.width * this.length
      }
    }
    console.log(r2.area());

    // 创建r3对象, 长 120, 宽 35   area方法 算面积
    var r3 = {
      length: 120,
      width: 35,
      area: function () {
        return this.width * this.length
      }
    }
    console.log(r3.area());

    // 思考1: r1 r2 r3 中都有area函数, 函数体一模一样
    // 他们是同一个函数吗??
    console.log(r1.area == r2.area) //不是同一个
    // area在不同的对象中声明的, 所以并非同一个函数

    // 思考2: 有必要吗?  同样功能的函数 声明3个 在不同对象里
    // 没必要, 浪费内存, 一个足够了

    // 怎么办?  提取area函数放在外面 共享即可
    function area() { return this.width * this.length }

    var r4 = {
      width: 100,
      length: 200,
      // area: area  语法糖: 属性名和变量名相同, 可以合写
      area
    }
    console.log(r4.area());

    // r5:  宽300, 长100  area方法
    var r5 = { width: 300, length: 100, area }
    console.log(r5.area()); // 函数()  ()是调用函数, 让函数运行

    // 思考1: r4的area 和 r5的area 是否相同?   是
    // 因为都引用的同一个函数
    console.log(r4.area == r5.area) // true

    // 思考2: 为什么函数书写在外部, 引入到不同的对象里, 就能为所在的对象服务??
    // 因为 灵活的 this 关键词: 函数在哪个对象里执行, 就代表哪个对象
    // this是特别好用的特性: 实现节省内存, 在不同对象里复用函数
  </script>
</body>

</html>

构造函数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>构造函数</title>
</head>

<body>
  <script>
    function area() { return this.width * this.length }
    // 手动方式: 创建一个对象 -- 麻烦,容易写错
    var r1 = { width: 10, length: 20, area }
    console.log(r1.area());

    // 利用函数快速创建对象

    // 相当于工厂/流水行:  提供原材料 即 30, 50
    // 内部就会加工成 矩形对象并 返回
    function Rectangle(a, b) {
      // 函数的形参名随便起
      var obj = {}

      obj.width = a
      obj.length = b
      obj.area = area

      return obj
    }

    var r2 = Rectangle(30, 50)
    console.log(r2);
    console.log(r2.area());

    // 自动化思想: 利用函数 把步骤都固定写好
    // 每次使用时, 提供参数即可, 函数自然会完成剩余的所有操作
    // 这种用来生产 对象的函数 -- 称为 构造(对象的)函数
    var r3 = Rectangle(10, 40)
    var r4 = Rectangle(440, 44)

  </script>
</body>

</html>

构造函数的两件事

  • this关键词: 把对象中共同的方法, 提取到外部存储

  • 构造函数: 自动化思想, 把制作对象的步骤 封装成函数, 调用函数即可以自动完成对象的创建

构造函数练习

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>练习</title>
</head>

<body>
  <script>
    // 员工对象
    // salary: 薪资
    var e1 = {
      ename: '楠姐',
      age: 18,
      salary: 22000,
      salary_year: function () {
        return this.salary * 12
      }
    }
    console.log(e1);
    console.log(e1.salary_year());

    // 制作一个生产 员工对象的 构造函数, 用来自动生成对象
    // 练习: 模仿上节课的, 实现这个 构造函数

    function salary_year() {
      return this.salary * 12
    }

    // 变量名: 见名知意
    function Employee(ename, age, salary) {
      // 1.制作一个空对象
      var obj = {}
      // 2.把传入的参数存储在对象里
      obj.ename = ename
      obj.age = age
      obj.salary = salary

      obj.salary_year = salary_year

      // 3.返回制作完毕的对象
      return obj
    }

    // e2   凯凯  32  15000
    var e2 = Employee('凯凯', 32, 15000)
    console.log(e2.salary_year());
    // e3   涛哥  40  30000
    var e3 = Employee('涛哥', 40, 30000)
    console.log(e3.salary_year());
  </script>

  <script>
    // 立方体: 
    var c1 = {
      length: 100, //长
      width: 40, //宽
      height: 10, //高
      //体积: 长x宽x高 
      volume: function () {
        return this.width * this.length * this.height
      },
      // 周长: (长+宽+高)*4
      perimeter: function () {
        return (this.width + this.height + this.length) * 4
      }
    }

    console.log(c1);
    console.log(c1.volume());
    console.log(c1.perimeter());

    // 练习: 制作 Cube 函数, 用来生产 立方体 对象
    function volume() {
      return this.width * this.length * this.height
    }

    function perimeter() {
      return (this.width + this.height + this.length) * 4
    }

    function Cube(length, width, height) {
      // 1. 空对象
      var obj = {}
      // 2. 加入数据
      obj.length = length
      obj.width = width
      obj.height = height
      // 3. 添加函数 -- 省内存, 把函数在外部声明
      obj.volume = volume
      obj.perimeter = perimeter
      // 4. 返回
      return obj
    }

    // 使用时:
    var c2 = Cube(10, 20, 30) // 顺序是 长 宽 高
    console.log(c2.volume());
    console.log(c2.perimeter());

  </script>
</body>

</html>

今日内容总结

  • 函数的arguments

    • 函数中自带的一个 隐藏的变量

    • 保存了 函数接收到的所有 实参

    • 类型是对象, 长得像数组 : 类数组 / 伪数组

      • 不能使用数组的各种方法

  • 函数的重载: 通过判断函数的参数 数量/类型 不同, 执行不同的逻辑操作

    • 作用: 让1个函数更加强大, 能完成不同的任务

    • 做法: 利用 if 判断, 搭配 arguments 属性

  • 访问器语法: 点语法 方括号语法 --- 到亮亮的部分复习

  • 对象是引用类型

    • 堆栈概念

    • 栈: 存储少量数据, 查询速度快 -- 类似图书的目录

    • 堆: 存储大量数据, 类似图书的详情页

    • 对象类型存储在栈中的是 其内存地址, 所以称为:地址传递

  • 克隆对象:

    • 制作一个空的新对象, 遍历目标对象, 挨个把属性赋值到空对象即可

  • this关键词: 函数是哪个对象调用的, 其中的this就是哪个对象

    • 楠姐.打亮亮(): 打亮亮中的this 就是楠姐

  • 为什么有this

    • 把多个对象中的 共同函数, 提取到外部存储.

    • 利用this的灵活变化, 在哪个对象执行 就服务于哪个对象

  • 对象的构造函数

    • 构造函数: 类似一个工厂, 提供原材料, 就会按照固定的步骤制作出商品

    • 给函数传参, 生成一个固定结构的对象

作业

函数重载练习

制作一个max函数, 能够找出最大值

max(11,22,33,44)

max(43,54,65,656,12,5)

max([32,4,546,57])

max([32,4,546,57, 5, 67,7])

提示:

  • 参数有两种传递方式: 传入1堆数字 或者 传入1个数组类型

  • 找出最大值的思路: 先声明一个变量a 等于 数组的第一个元素, 然后遍历数组从序号1开始. 如果新的值比a大, 就替换掉a中的值即可

构造函数练习

制作一个 生产 学生对象的 构造函数

学生对象的样子:

var s1 = {
 sname: '凯凯',
 age: 19,
 phone:'18844341111',
 // 返回是否成年
 isAdult: function(){
     return this.age >= 18
 }
}

制造的构造函数使用时的样子

var s2 = Student('亚楠', 18, '198382932323')
console.log(s2.isAdult())

var s3 = Student('梦瑶', 15, '158779787874')
console.log(s3.isAdult())

JSCORE03

复习

  • 声明提升

    • JS独有的特色 -- 不好

    • JS代码书写完毕后, 真正执行时会 隐式调整代码结构, 把声明操作都提升到作用域的顶部存放, 然后再执行

      面试考点: JS代码的运行 和 你眼睛看到的不一样

    • 声明关键词: var/function/class/let/const

  • 作用域: 本质是个对象类型, 只是因为有特殊作用 所以叫作用域

    • 全局: window对象

    • 局部: 函数运行时 会临时生成一个对象, 其中保存函数中声明的变量

    • 脚本: 在 script 中使用let/const/class 声明的

    • 块级: {} 中使用 let/const/class 声明的

  • 作用域链: 代码的书写环境 -- 词法环境

    • 当函数中使用一个变量, 优先在自身作用域查找. 如果没有 则向上级查找

  • 闭包

    • 函数在声明时, 会携带所在的所有作用域, 存储在 scopes 属性里

      • 静态: 函数声明 -- 作用域们存储在 scopes 里

      • 动态: 函数调用 () -- 从scopes里读取作用域中的值 来用

    • 其中: scopes中存储的 函数作用域 -- 称为闭包 Closure

    • 作用: 在ES6之前, 声明局部的变量, 避免全局污染

  • arguments

    • 函数中 隐式 带有的一个属性, 存储了 函数使用时 收到的所有实参

  • 函数的重载

    • 一种技巧, 函数体中 通过判断 参数的个数 或 类型, 来执行不同的逻辑

    • 作用: 让一个函数能做多件事, 更加强大

    • 做法: if 配合 arguments 使用

  • 引用类型

    • 数据类型有8种

      • 基础类型(体积小): string number boolean null undefined symbol bigInt

      • 引用类型(体积不固定,大): object

    • 内存部分: 堆内存 栈内存

      • 栈内存: 相当图书的目录, 适合存储小的数据, 查询速度快

      • 堆内存: 相当于图书的详情页, 适合存储大型数据, 带有内存地址

    • 对象类型作为大型的数据, 存储在堆内存里; 变量保存对象类型, 实际存储的是对象类型的地址 -- 地址传递/引用(内存地址)类型

  • this

    • 函数中的一个关键词, 代表函数运行时在哪个对象里

      • 楠姐.打亮亮(): 打亮亮方法中的this就是 楠姐

    • 作用: 让同一个函数 可以在不同的对象中使用, 因为this关键词所以会自动适应所在的对象 节省内存

  • 构造函数: 构造 固定结构的对象 的函数

    • 把一些对象的制造过程, 封装成一个函数. 以后调用函数就能自动生成对象

作业

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>作业</title>
</head>
​
<body>
  <script>
    // max函数, 求最大值
    function max() {
      // arguments: 存储了所有的参数
      // console.log(arguments);
      // 思维: 先看第一个, 暂定为最大
      // 然后再看下一个, 如果下一个更大, 则 替换掉之前 暂定的最大
      // 写程序 就是把 你的思维 转换成 代码 告诉计算机如何操作
      if (Array.isArray(arguments[0])) {
        //读取参数数组
        var arr = arguments[0]
        var m = arr[0]
​
        for (var i = 1; i < arr.length; i++) {
          if (arr[i] > m) m = arr[i]
        }
        console.log(m);
​
      } else {
        var m = arguments[0]
        for (var i = 1; i < arguments.length; i++) {
          // if判断的 {}中只有一行, 可以省略{}
          if (arguments[i] > m) m = arguments[i]
        }
        console.log('最大数:', m);
      }
    }
​
    max(12, 43, 4565, 675)
    max(12, 43, 4565, 675, 1111, 23, 43)
​
    max([43, 5, 65, 7, 78])
  </script>
​
  <script>
    // 构造学生对象
    // 把对象中使用的函数 在外部声明: 共享 省内存
​
    // 思考: 共享的函数 如果存储在 全局区, 会造成全局变量污染
    // 办法: 作者为函数提供了一个专有的属性 prototype(原型), 专门存储共享的方法
    function isAdult() {
      return this.age >= 18
    }
​
    function Student(sname, age, phone) {
      var obj = {}
      obj.sname = sname
      obj.age = age
      obj.phone = phone
      // 错误: function关键词会声明新的函数. 每次运行都会声明一个新的. 浪费内存
      // obj.isAdult = function () { }
      obj.isAdult = isAdult
​
      return obj
    }
​
    var s1 = Student('凯凯', 19, '18844341111')
    console.log(s1);
    console.log(s1.isAdult());
  </script>
</body>
​
</html>

JSCORE04

复习

  • 声明提升

    • JS代码在真正运行时, 需要隐式调整代码的顺序, 把声明操作的代码提升到作用域的顶部执行

    • 书写的代码 和 运行的代码, 实际顺序是不同的 -- 面试考点

    • 声明的关键词: var/function/let/const/class

  • 作用域

    • 什么是作用域? 本质就是一个对象类型, 只是因为有特殊作用, 所以称为 作用域

    • 4种作用域:

      • 全局作用域: 在浏览器这个宿主环境中, 就是 window 对象

      • 局部作用域: 函数在运行时临时创建的对象类型, 存储了函数中声明的变量

        • 在函数执行开始: 创建; 在函数执行结束: 销毁

      • 脚本作用域: ES6开始

        • 利用 let/const 在 script 中声明的, 存储在脚本区

      • 块级作用域: ES6 开始

        • 利用 {} 配合 let/const 使用

  • 闭包: 函数自带的一个技能 -- 函数在声明时, 会保存其所在的词法作用域scopes属性里

    • 词法作用域: 函数所在的外层的所有作用域

    • 闭包就是 函数作用域 在保存到 scopes 属性中时的一个特殊称呼

      a 函数在触发时, 会形成函数作用域, 其中有一个变量 x

      b 函数在a函数中声明, 使用了a函数的 变量x

      a函数的作用域就会存储在 b 函数的 scopes 属性里, 称为闭包

      function a(){
          var x = 10
          
          function b(){
              console.log(x)
          }
      }
      ​
      a()
  • 函数的重载

    • arguments: 函数中自带的一个变量, 存储了函数收到的所有参数

      • 使用场景: 当一个函数的参数数量不固定时, 使用

    • 应用: 函数重载

      • 在函数体中, 判断参数的 个数类型 不同, 执行不同的代码逻辑

      • 让1个函数 能做多种操作

  • 对象

    • 数据类型8种

      • 基础: string number boolean null undefined + symbol 和 bigInt

      • 对象类型 object

    • 引用类型:

      • 堆内存: 相当于图书的详情页, 存储的东西多, 带有内存地址

      • 栈内存: 相当于图书的目录, 查询速度快,存储的内容少

    • 对象类型, 存储在堆内存中

    • 变量存储在 栈内存里, 其值是 对象类型的地址

    • 引用了对象类型的地址, 对象之间的赋值---地址传递

  • this关键词

    • 函数的this关键词, 代表函数运行时所在的对象

    • 作用: 省内存

    • 如何实现: 一个函数可以在多个对象中重复使用: 复用

  • 构造函数

    • 用来创建对象类型的函数, 就是构造函数

    • 普通写法:

      function Demo(x, y){
          var obj = {} //1. 先创建空对象
          
          // 把参数存储在对象里
          obj.x = x
          obj.y = y
          
          // 原型链 __proto__ 指向 函数的原型
          obj.__proto__ = Demo.prototype
          
          return obj //2. 返回对象
      }
      ​
      // 对象共享的方法, 存储在原型中
      Demo.prototype.area = function(){}
      Demo.prototype.xxyy = function(){}
       
    • new运算符: 作者提供的 用于简化构造函数的关键词, 隐式完成 3 行代码

      function Demo(x, y){
          var this = {}
          
          this.x = x
          this.y = y
          
          this.__proto__ = Demo.prototype
          
          return this
      }
      ​
      // 配合new使用
      function Demo(x, y){
          this.x = x
          this.y = y
      }
      ​
      var d1 = new Demo(10, 20)
  • 严格模式

    • ES5 2009年出品的版本

    • 在严格模式下, 如果书写的代码有风险 后台会报错

    • 开启方式: 在JS代码的最上方书写 use strict

    • 变量必须先声明, 后使用: 防止写错变量名导致全局变量污染

    • 构造函数的this, 直接触发 this是undefined, 避免全局污染

    • 阻止静默失败: 以前失败但不报错的, 现在报错

  • 对象属性的精确配置

    • Object.defineProperty(对象, 属性名, 配置项)

    • 配置项:

      • configurable: 是否可重新配置 -- 是否可删除属性

      • enumerable: 可遍历 -- for..in 是否能遍历到

      • writable: 可修改 -- 值是否可改动

      • value: 默认值

      • get: 计算属性 -- 值是函数类型, 但是使用时不需要() 就能触发 -- 简单

set

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>精确属性配置 - 赋值监听</title>
</head>
​
<body>
  <script>
    var emp = {
      ename: '亚楠',
      _age: null, // 用于存储 检查站 age 检查出来的值
    }
​
    // 为 age 属性, 添加检查站: 监听器 set(设置)
    Object.defineProperty(emp, 'age', {
      // 计算属性get: 特点 值是函数,但使用时不用()
      // get: function () { },
      get() {
        // emp._age
        return this._age;
      },
      // set: 检查站属性, 值是函数
      // set: function (value) { }
      // 语法糖: 省略 function 
      // 参数value: 就是设置给 age 属性的值, 
      // 例如 emp.age = 200, 就是这个200
      set(value) {
        console.log('age赋值为:', value);
        // 检查标准: age 1~100
        if (value >= 1 && value <= 100) {
          // 如果正确, 则赋值给 _age
          // age是监听器/检查站, 只负责检查, 不负责存值
          // _age 是配合 age检查站使用的属性, 用来存储值
          this._age = value
        } else {
          // 抛出错误, 提醒用户
          throw Error('age合法范围1~100, 您的赋值:' + value)
        }
      }
    })
​
​
    // 修改age 年龄
    // 问题: 娅楠的年龄不可能是200, 明显的错误
    // 期望: 监听这个赋值操作, 如果值明显不对, 则报错, 给出提示
    // emp.age = 200
​
    // 用户看到: 把100 存储到 age 属性里
    // 本质age是检查站不存值, 但是用户不知道, 用户只看表面
    emp.age = 100
    // 外观上: 就应该从 age 读取值
    console.log(emp.age);
    // emp.age: 实际上age是个函数,但是因为是get计算属性, 不要()
    // 从外观上看起来: 好像是在读属性
​
    // 楠姐.打亮亮() : this就是楠姐
    // emp.age() : this就是 emp
​
    console.log(emp);
    // 读取年龄: 从_age读取完全没问题
    // 但是: 从用户角度来看
    console.log(emp._age);
  </script>
</body>
​
</html>

死循环

最终超出最大内存限制, 崩溃

由于 age 属性变身成检查站/监听器, 其不再具有存储值的作用

解决方案: 再制作一个属性用来存储 合法的值 即可

  • 命名规范: 习惯(非强制)上 存储 age 检查成功的值, 属性名叫 _age 比较合适, 添加一个_ , 读代码时 一目了然, 就知道 _age 存储的是 age检查站的内容

练习

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>练习</title>
</head>
​
<body>
  <script>
    var emp = {
      ename: "凯哥",
      // 配合 salary 检查站, 创造一个仓库 用来存储对应的值
      // 写法上: 最好是 _开头, 名字一目了然
      _salary: 20000
    }
​
    // 把 salary 属性修改成 检查站/监听器, 不再负责存储数据
    Object.defineProperty(emp, 'salary', {
      // 让 salary 使用时, 不用() 也能触发
      get() { return this._salary },
      set(value) {
        if (value >= 5000 && value <= 30000) {
          // salary属性本身已经变身为 检查站 - 无法存数据
          // 所以搭配一个 仓库用来存数据
          this._salary = value
        } else {
          throw Error('月收入不合理:' + value)
        }
      }
    })
​
    // 希望报错, 合理的薪资是 5000 ~ 30000
    // emp.salary = 100000
​
    // 用户角度: 眼看着存储在 salary 属性的
    // 但是却要从 _salary中读取 : 另用户感觉到迷惑
    emp.salary = 12000
    // 读取时: 增加一个 salary的 get计算属性, 能够返回 _salary的值
    console.log(emp.salary);
    console.log(emp);
  </script>
</body>
​
</html>
<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>练习</title>
</head>
​
<body>
  <script>
    var stu = {
      sname: "亮亮",
      _sid: 10001 //学号
    }
​
    // 把 sid 属性, 转变成 检查站, 不再能够存储值
    Object.defineProperty(stu, 'sid', {
      get() { return this._sid },
      set(value) {
        if (value >= 1 && value <= 100000) {
          this._sid = value
        } else {
          throw Error('学号范围错误:' + value)
        }
      }
    })
​
    // 学号范围是 1-100000
    // stu.sid = 200000 //要求弹出报错: 提示学号范围不对
​
    // 注释掉 19行代码
    stu.sid = 2000 //能够正常存储
    console.log(stu.sid) // 能够打印出2000  -- 计算属性
    console.log(stu);
​
  </script>
</body>
​
</html>

call

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>call</title>
</head>
​
<body>
  <script>
    // 函数的call方法: 用于指定函数使用时的this关键词指向
​
    // 函数的this: 指向运行时所在的对象
    // 例如  楠姐.打亮亮() -- this就是楠姐
​
    // 函数分两种状态:声明时 和 运行时
​
    // 声明:   问: this是什么?  不可能知道--运行时决定
    // 举例: 马路边停了一辆车, 问: 司机是谁? -- 开起来才知道
    function 打亮亮() {
      console.log(this.uname, '使劲打亮亮');
    }
​
    var emp = { uname: "娅楠" }
    // 把 打亮亮 这个函数, 存储到 emp 里
    emp.hit = 打亮亮
    emp.hit()
​
    var emp1 = { uname: '凯哥' }
    emp1.hit = 打亮亮
    emp1.hit()
    delete emp1.hit //删除 hit 属性, 毁灭证据
​
    // 又把 公用的锤子
    // 1. 把锤子拿到手里  2.用锤子打亮亮  3.把锤子扔掉
    console.log(emp1);
​
    // 打亮亮方法在使用时分两步: 
    // 1. 先把此方法存储到 对象里
    // 2. 再通过对象来调用
    // 3. 删除打亮亮方法, 因为是临时使用
​
    // 官方为了用户使用方便, 封装了一个call方法, 能够自动把函数放在对象里, 进行调用, 随后进行删除
    // 函数的构造方式:  new Function()
    // 所以 函数.__proto__ == Function.prototype
    console.dir(打亮亮);
    // 函数具有一个call
    // 函数.call(obj):  隐式 把函数存储在obj里, 然后通过obj调用
    打亮亮.call(emp1)
​
    var emp2 = { uname: '涛涛' }
    // 两种做法:
    // 做法1: 手动模式
    // - 把 打亮亮 存储在 emp2 中, 属性名叫 hit, 可以随便起名
    emp2.x = 打亮亮
    // - 调用 hit 方法
    emp2.x()  // 打亮亮的 this 就是 emp2
    // - 删除 hit 属性, 毁灭证据
    delete emp2.x
    console.log(emp2);
​
    // 做法2: 系统提供的快速隐式完成以上操作 -- call
    打亮亮.call(emp2)
  </script>
</body>
​
</html>

call带有参数

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>call携带参数</title>
</head>
​
<body>
  <script>
    function 打亮亮(time) {
      // time: 次数
      console.log(this.uname, '打亮亮', time, '次');
    }
​
    var emp1 = { uname: "楠姐" }
    // 1. 把 打亮亮 放在 emp1 里
    emp1.xx = 打亮亮
    // 2. 调用 emp1 的打亮亮
    emp1.xx(5)
    // 3. 删除掉 emp1 中存储的 打亮亮
    delete emp1.xx
​
    // 使用系统提供的 call 方式
    // 函数.call(对象, 参数): 自动隐式完成 - 把函数放对象里,调用后,删除
    // call() 从参数2开始, 就是传递给函数本身的参数
    打亮亮.call(emp1, 10) // emp1.打亮亮(10)
​
    // 范式:  fn.call(A, B) ->实际执行 -> A.fn(B)
​
    // 计算 n个月 支付的薪资
    function pay(n) {
      console.log(this.salary * n);
    }
​
    var emp2 = { ename: "凯凯", salary: 14000 }
    var emp3 = { ename: "涛涛", salary: 24000 }
​
    // 计算 emp2 10个月的薪资
    pay.call(emp2, 10)
    // 计算 emp3 4个月的薪资
    pay.call(emp3, 4)
​
    function show() {
      console.log(arguments);
      // 向 arguments 中, 新增 55 66 77 3个值
      // arguments: 类数组, 长得像数组 但 原型不是, 没有数组的方法
      // 数组: push
​
      // 把数组的 push 方法临时放到 arguments 中使用一次
      // arguments.push(55, 66, 77)
      Array.prototype.push.call(arguments, 55, 66, 77)
      // 数组的push方法: 构造函数Array的原型prototype里
    }
​
    show(11, 22, 33, 44)
  </script>
</body>
​
</html>

apply

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>apply</title>
</head>

<body>
  <script>
    // apply: 与 call 作用十分相似
    // 都是临时把函数放在对象里执行
    function pay(n, off) {
      // off: 折扣
      console.log(this.salary * n * off);
    }

    var emp = { ename: "凯凯", salary: 14000 }
    // 用call 算出10个月工资: 把pay临时放到 emp 中执行
    pay.call(emp, 10, 0.7)
    // apply: 临时把函数放在对象里执行, 差异: 参数放数组中传递
    pay.apply(emp, [10, 0.7])

    // apply的用途: 把 1个1个 传递的参数, 改为用数组传递

    // 求最大值: Math.max()
    var m = Math.max(12, 43, 2154, 657, 67, 21)
    console.log(m);

    // 找出数组中的最大值
    var nums = [123, 345, 546, 657, 67]
    // max: 只能接收 1个1个传递的参数
    // apply: 把数组作为函数的参数, 数组->1个1个传递的
    // Math.max.apply(A, B) -> A.Math.max(B)
    // Math.max 不需要存储在任何对象中, 独立就能运行, 所以参数1 随便写, 没有任何影响
    var m = Math.max.apply(0, nums)

    // ES6之前, 只能用 apply 来转换数组 为参数
    // ES6之后, 有扩展符  ... 更方便, 后续讲解
    console.log(m);
  </script>
</body>

</html>

bind

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>bind</title>
</head>

<body>
  <script>
    // bind: 与call相似
    // call: 临时 把函数放在对象中, 立即执行
    // bind: 永久 把函数放在对象中, 不会立即执行, 返回处理后的函数

    function pay(n) {
      console.log(this.salary * n);
    }

    var emp = { ename: "凯凯", salary: 14000 }
    // 用call实现 10个月工资
    pay.call(emp, 10)

    // bind(): 不会触发pay函数, 而是返回一个值
    // 返回值是一个函数, 其中的 BoundThis 属性存储了this指向
    // 其中的 BoundArgs : 存储了相关参数

    // 打比方: 一把手枪
    // call: 填充弹药 -> 立刻激发
    // bind: 填充弹药 -> 返回这把带有弹药的手枪
    var pay_b = pay.bind(emp, 10)
    console.dir(pay_b);

    // 在后续, 用()来触发
    pay_b()

    // 实际工作用途:
    // 1. 配合定时器使用: 延时执行
    // 2. 在 React 框架中常用: 2个月后的 5阶段会用到
  </script>
</body>

</html>

函数的call,bind,apply

面试常考题, 造火箭时使用, 日常用的较少

  • call: 临时修改函数的this指向, 立刻触发, 参数 1个1个 传递

  • apply: 临时修改函数的this指向, 立刻触发, 参数 数组 传递

  • bind: 把参数和函数捆绑在一起, 返回这个函数. 不会触发, 等到后续随时触发都可以

箭头函数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>箭头函数</title>
</head>

<body>
  <script>
    // ES6 2015年提供的 新语法 - 箭头函数
    // 优点1: 丰富的语法糖 带来简化写法
    // 作用: 带来了 新的 this 指向方式
    // 注意: 不能做 构造函数

    // function (){}
    // 从格式上: 简化了 匿名函数自调用的写法
    (function () { })();

    (() => { })()

    // 语法糖1: 箭头函数只有一个参数, () 可以省略
    var a = (name) => {
      console.log(name, '666');
    }

    // 简化
    var a = name => {
      console.log(name, '666');
    }

    a('凯凯')
    a('楠楠')

    // 语法糖2: 函数体只有1行代码时, 可以省略 { return ... }
    var a = (n) => { return 14000 * n }

    var a = n => 14000 * n

    console.log(a(12));

    // 练习: 尝试省略
    var b = (x, y) => { return x + y }
    var b = (x, y) => x + y

    var c = (y) => { return 12 * y }
    var c = y => 12 * y

    var d = () => { return 666 * 888 }
    var d = () => 666 * 888

    // 坑:
    var e = (name, age) => { return { name: name, age: age } }
    // 返回值是对象类型, 带有{}, 会被识别为 函数体的{}
    // 用()包围, 才会识别为 普通的对象类型
    var e = (name, age) => ({ name, age })

    console.log(e('马鑫鑫', 23));
  </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>箭头函数的this</title>
</head>

<body>
  <script>
    // function: this指向运行时所在的对象
    // 箭头函数: this指向声明时所在的作用域的this

    // 类似: 涛哥买个锤子, 打亮亮
    // function:  楠姐.打亮亮()   锤子的使用者是楠姐 this
    // 箭头函数:  楠姐.打亮亮()    锤子的购买者 - 涛哥: 永远不变

    // 另一个例子:
    // 家乐的妈妈: - 相当于 箭头函数, 家乐不管在哪里都不会变, 出生的时候就锁死

    // 家乐的女朋友: 今天是 热巴  明天是杨幂 后天 -马尔扎哈  - function. 会变化

    console.log(this) // 全局中打印this, 就是window

    // 这个箭头函数 在哪个对象中声明的: 全局中 window
    var 三打亮亮 = () => {
      console.log(this, '三打亮亮');
    }

    function 打亮亮() {
      console.log(this, '在打亮亮');
    }

    var emp = { uname: "楠姐" }

    emp.x = 打亮亮
    emp.x()

    // 把箭头函数放在 emp 里
    emp.y = 三打亮亮
    emp.y()
  </script>
</body>

</html>

every

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>every</title>
</head>

<body>
  <script>
    // 数组的高阶函数
    // 高阶函数: 如果一个函数中, 使用了另一个函数, 就叫高阶函数
    // 类似: 家乐 有孩子了, 家乐称为: 父亲
    // 例如: 数组有一个 sort 排序, 例如 sort( (a,b)=> a-b )

    // 以后: 从服务器上查询出数据, 往往都是数组类型, 在JS中提供了大量的处理 数组类型的 函数

    // every: 每一个. 用于遍历数组, 检查每一个元素是否符合条件
    var nums = [12, 22, 14, 12, 4]
    // 检查: 数组的每个元素是否都是 偶数

    // every的参数: 要求是函数类型, 函数固定接收3个参数
    // every会遍历 nums 中的每一个元素, 传递给函数
    var a = nums.every((value, index, array) => {
      // 箭头函数的形参名: 随便起
      // 顺序固定: 值, 序号, 数组本身
      console.log('value:', value);
      console.log('index:', index);
      console.log('array:', array);
      console.log('------------------');
      return value % 2 == 0
    })

    console.log('a:', a);
    console.log(a ? '都是偶数' : '非都是偶数');

    // 练习
    var nums = [12, 3, 5, 65, -3]

    // 判断是否都是正数 > 0
    var a = nums.every((value, index, array) => {
      return value > 0
    })

    // 没用到的参数, 可以不用声明
    var a = nums.every((value) => {
      return value > 0
    })

    // 箭头函数: 单参数省略()   函数体只有1行 {return }
    var a = nums.every(value => value > 0)
    // 参数名随便起, 但是原则: 见名知意
    var a = nums.every(x => x > 0)


    console.log(a);
    console.log(a ? '都是正数' : '非都是正数');

    var nums = [12, 3, 45, 5465, 765]
    // 判断是否都大于10
    var a = nums.every(v => v > 10)
    console.log(a ? '都大于10' : "非都大于10");

    // 实际案例:
    var products = [
      { pname: 'iPhone', price: 9000, count: 10 },
      { pname: 'v80x', price: 6000, count: 1 },
      { pname: 'findx5', price: 5500, count: 5 },
      { pname: 'magic 5', price: 6000, count: 6 },
    ]
    // 需求: 判断商品的单价是否都高于 5000
    // 数组的每个元素是对象类型
    var a = products.every(value => value.price > 5000)
    console.log(a ? '都大于5000' : '非都大于5000');
    // 判断: 数量是否都少于3个的
    var a = products.every(value => value.count < 3)
    console.log(a ? '都少于3个' : '非都少于3个');
  </script>
</body>

</html>

some

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>some</title>
</head>

<body>
  <script>
    // some: 有些, 判断数组中至少有一个满足条件的元素
    // 类似于 逻辑或 ||  -- 有一个真 最终结果是真
    var nums = [11, 43, 5, 677, 1]
    // 判断: 是否有偶数
    // some的所有参数 都和 every 一样
    var a = nums.some((value, index, array) => {
      return value % 2 == 0
    })

    // 简化后:
    var a = nums.some(value => value % 2 == 0)

    console.log(a ? '有偶数' : '没有偶数');

    var emps = [
      { ename: "凯凯", age: 32 },
      { ename: "亮亮", age: 37 },
      { ename: "亚楠", age: 18 },
    ]
    // 查看是否 有 小于20岁的人
    var a = emps.some(value => value.age < 20)

    console.log(a ? '有' : '没有');
  </script>
</body>

</html>

filter

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>filter</title>
</head>

<body>
  <script>
    // filter: 过滤器, 把数组中满足条件的元素 组成新的数组返回
    var nums = [12, 3, 43, 65, 12, 546, 7, 6]
    // 找出所有的偶数

    var a = nums.filter((value, index, array) => {
      // 真: 过滤出来   假: 阻拦
      return value % 2 == 0
    })

    // 简化:
    var a = nums.filter(value => value % 2 == 0)

    console.log(a);

    var emps = [
      { ename: "凯凯", salary: 14000 },
      { ename: "亮亮", salary: 34000 },
      { ename: "楠姐", salary: 20000 },
      { ename: "涛涛", salary: 24000 },
    ]
    // 找出所有 薪资大于20000的人
    var a = emps.filter(value => value.salary > 20000)
    console.log(a);
  </script>
</body>

</html>

map

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>map</title>
</head>

<body>
  <script>
    //map: 映射 - 把数组中的每个元素 处理后, 返回值组成新的数组
    var nums = [12, 34, 5, 65, 23, 43, 6]

    // 希望: 每个数字都 x2
    var a = nums.map((value, index, array) => {
      return value * 2
    })

    // 简化:
    var a = nums.map(value => value * 2)

    console.log(a);

    // 实际用途: 把数据转换成 HTML 字符串, 最常用
    var girls = ['迪丽热巴', '古力娜扎', '马尔扎哈', '努尔哈赤']
    // 把每个元素, 放在按钮标签里  <button>xx</button>
    var a = girls.map(value => {
      // ES6提供了 模板字符串, 转为拼接HTML而生
      // 符号 `` 反引号, 特殊标识 ${} 代表JS代码
      return `<button>${value}</button>`
    })

    console.log(a);

    var webs = [
      { name: "百度一下", href: "http://www.baidu.com" },
      { name: "Tmooc", href: "http://www.tmooc.cn" },
      { name: "斗鱼", href: "http://www.douyu.com" },
      { name: "哔哩哔哩", href: "http://www.bilibili.com" },
    ]

    // 要求: 映射成 超链接 字符串组成的数组
    // 例如  <a href="http://www.baidu.com">百度一下</a>
    var a = webs.map(value => `<a href="${value.href}">${value.name}</a>`)

    console.log(a);
  </script>
</body>

</html>

内容总结

  • 属性的 set配置

    • 把一个属性转换成 监听器/检测站: 实现赋值检测

    • 搭配 另一个属性, 来存储实际的值

    • 通过 get 计算属性, 欺骗用户, 提供一个友好的读值写法

  • 函数的3个触发方式

    • call: 触发函数同时指定this的对象

    • bind: 不触发函数, 把参数和this绑定好, 后期随时触发

    • apply: 触发函数同时指定this的对象, 其参数是数组

  • 箭头函数

    • 语法糖: (name)=>{ return name + 11 } 省略 (){}

      name => name + 11

    • this指向: 声明时所在作用域的this

  • 数组高阶函数

    • every: 每一个元素都满足条件, 类似逻辑与 && 必须都真才行

    • some: 至少有一个元素满足条件, 类似 逻辑或 || 有一个真就行

    • filter: 过滤器, 把满足条件的元素过滤出来, 组成新的数组

    • map: 映射, 把每个元素处理后的返回值, 组成新的数组 (很常用)

作业

作业1:

var p = { pname:"iPhone", count: 5, price: 9999}

p.count = -10 //报错: 范围在 0 - 100000
p.count = 40 //正常赋值
console.log(p.count) //正常输出 40

p.price = -10 //报错: 范围在 2000 - 20000
p.price = 4000 //正常赋值
console.log(p.price) //正常输出 4000

作业2:

var emps = [
    {ename:"凯凯1", age: 23, salary:7000, married:true},
    {ename:"凯凯2", age: 33, salary:17000, married:true},
    {ename:"凯凯3", age: 44, salary:9000, married:false},
    {ename:"凯凯4", age: 28, salary:12000, married:true},
    {ename:"凯凯5", age: 35, salary:33000, married:false},
]

// 判断是否所有人都大于30岁
// 判断是否有人工资超过3w
// 找出所有已婚的人
// 把数组映射成html组成的数组, 其中元素样子如下:  注意 已婚 class是ok, 未婚class是err
// <a class='ok'>凯凯1-23-7000</a>

JSCORE05

复习

  • 对象属性的精确配置

    • writable: 是否可写

    • enumerable: 是否可遍历

      • 在 谷歌浏览器中, 不可遍历的属性是 浅色

    • configurable: 是否可重新配置 -- 属性能否删除

    • value: 默认值

    • get: 计算属性, 值是函数, 但是使用时不用() 就能触发

    • set: 监听器, 把一个属性转换成监听器, 监听赋值

  • 函数的3个触发方式

    • call: 立刻触发函数; 临时把函数放在对象中执行 -- 修改this指向;

      • 参数部分, 1个1个传递

    • apply: 立刻触发函数; 临时把函数放在对象中执行 -- 修改this指向;

      • 特殊: 参数部分用 数组进行传递

    • bind: 不会触发函数; 把 参数和this指向 都保存在函数自身, 返回绑定好的函数; 以后随时都能触发 -- 延时执行

  • 箭头函数

    • 格式上提供了更简单的匿名函数写法: () =>{}

    • 两种语法糖:

      • 参数只有一个, () 可是省略: x => { return x * 2 }

      • 函数体只有一行, {return } 省略: x => x * 2

    • this关键词的指向

      • function的this: 运行时所在的对象 楠姐.打亮亮()

      • 箭头的this: 声明时所在的对象 -- 固定的,永远不变

      • 构造函数的this(new): 构造出来的那个对象

        • new隐式3件事:

          • var this = {};

          • this.__proto__ =函数.prototype

          • return this

      • 严格模式下, 函数的this

        • 如果直接在全局调用, 则this指向 undefined

  • 数组的高阶函数

    • 高阶函数: 函数中使用了其它的函数

    • every: 每一个元素都满足条件则为真, 类似于 逻辑与 &&

    • some: 至少有一个元素满足条件则为真, 类似于 逻辑或 ||

    • filter: 把满足条件的元素过滤出来, 组合成新的数组

    • map: 映射. 遍历每个元素, 处理后把返回值组成新的数组;

      • 使用场景: 把数据转换成HTML字符串

作业

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>作业</title>
</head>
​
<body>
  <script>
    var p = {
      pname: 'iPhone',
      _count: 5,
      _price: 9999,
      c() { return this._count },
      // get: 变身成计算属性, 使用时不需要()
      get d() { return this._count },
      get count() { return this._count }
    }
​
    // 使用时:
    console.log(p.c())
    console.log(p.d)
    console.log(p.count)
​
​
​
​
    // count: 对赋值进行检查 -- 改造成  监听器
    Object.defineProperty(p, 'count', {
      get() { return this._count },
      // set: 设置.  用于把属性改为赋值监听器
      set(value) {
        if (value >= 0 && value <= 100000) {
          // 把正确的值保存下来
          // count属性已经被转换成监听器, 不再具有保存的能力
          // 需要额外制作一个属性, 来存储数据
          // 属性名: 可以随便起, 但是见名知意是最佳的选择
          this._count = value
        } else {
          throw Error('数量赋值范围错误:' + value)
        }
      }
    })
​
    // p.count = -10 //报错
​
    p.count = 40
    // 读取 p._count 可以读取到值, 但是对于用户的观感不好
    // 用户的感觉:  存储在 count 属性里
    // 用户应该觉得: 从 p.count 读取更合理
    console.log(p.count);
​
    // defineProperties: 可以同时完成 price和count的制作
    Object.defineProperty(p, 'price', {
      get() { return this._price },
      set(value) {
        if (value >= 2000 && value <= 20000) {
          this._price = value
        } else {
          throw Error('价格赋值错误:' + value)
        }
      }
    })
​
    // p.price = -100
    p.price = 3000
    console.log(p.price);
  </script>
</body>
​
</html>
<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>作业</title>
</head>
​
<body>
  <script>
    var emps = [
      { ename: '凯凯1', age: 23, salary: 7000, married: true },
      { ename: '凯凯2', age: 33, salary: 17000, married: true },
      { ename: '凯凯3', age: 44, salary: 9000, married: false },
      { ename: '凯凯4', age: 28, salary: 12000, married: true },
      { ename: '凯凯5', age: 35, salary: 33000, married: false },
    ]
​
    // 判断是否所有人都大于30岁
    var a = emps.every(value => value.age > 30)
    console.log(a ? '都大于30' : '非都大于30');
​
    // 判断是否有人工资超过3w
    var a = emps.some(value => value.salary > 30000)
    console.log(a ? '有超过3w' : '没有超过3w');
​
    // 找出所有已婚的人
​
    // value.married == true:   真==真 , 返回真; 麻烦没必要
    var a = emps.filter(value => value.married)
    console.log(a);
​
    // 把数组映射成html组成的数组, 其中元素样子如下:  注意 已婚 class是ok, 未婚class是err
    // <a class='ok'>凯凯1-23-7000</a>
    var a = emps.map(value => {
      return `<a class='${value.married ? 'ok' : 'err'}'>${value.ename}-${value.age}-${value.salary}</a>`
    })
​
    console.log(a)
  </script>
</body>
​
</html>

forEach

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>foreach</title>
</head>
​
<body>
  <script>
    // foreach: 属于数组的高阶函数, 其功能非常简单: 简单的遍历数组, 没有返回值
    var nums = [11, 22, 33, 44, 55, 66, 77]
​
    // 遍历数组的4种方式
​
    // 最原始:
    for (let i = 0; i < nums.length; i++) {
      console.log(i, nums[i]);
    }
​
    // 简化版:  for..in   非数组独有, 是遍历对象的公用方法
    for (let i in nums) {
      console.log(i, nums[i]);
    }
​
    // ES6提供了专门遍历数组的 for..of
    // 用于直接遍历值, 不读取序号
    for (let value of nums) {
      console.log(value);
    }
​
    // 形参, 声明的变量: 都是自定义的
    // var/function/let/const/class: 声明自定义变量
​
    // forEach: 数组的高阶函数, 单纯遍历没有返回值
    nums.forEach((value, index, array) => {
      console.log(index, value);
    })
​
    // 计算出 nums 中元素的总和
    var a = 0
    for (const value of nums) {
      a += value
    }
    console.log(a);
​
    var a = 0
    // 得益于语法糖的存在, forEach完成一些简单的循环, 更加简洁
    nums.forEach(value => a += value)
    console.log(a);
​
    var products = [
      { pname: "iPhone1", price: 8999, count: 4 },
      { pname: "iPhone2", price: 7999, count: 1 },
      { pname: "iPhone3", price: 6999, count: 6 },
      { pname: "iPhone4", price: 5999, count: 8 },
    ]
    // 计算出所有商品的总金额
    var a = 0
    products.forEach(value => a += value.price * value.count)
    console.log(a);
  </script>
</body>
​
</html>

reduce

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>reduce</title>
</head>
​
<body>
  <script>
    // reduce: 合并数组数据, 遍历数组的元素, 把处理后的返回值累加在一起
    var nums = [11, 22, 33, 44, 55, 66, 77, 88]
​
    // 把 数组的所有元素 累加在一起
    // a : 相当于捐款箱, 收集每次累加的结果, 然后交给下一个元素
    // 参数2: 0 是a的初始值, 即第一次循环时的初值
    var s = nums.reduce((a, value) => {
      return a + value
    }, 0)
​
    // 每次遍历, 都会把结果传递给 下一次的参数1, 即a
​
    console.log(s);
​
    var products = [
      { pname: "iPhone1", price: 8999, count: 4 },
      { pname: "iPhone2", price: 7999, count: 1 },
      { pname: "iPhone3", price: 6999, count: 6 },
      { pname: "iPhone4", price: 5999, count: 8 },
    ]
​
    // reduce的流程
    // 1个雪糕 -> 家乐(吃一口) -> 思琪(吃一口) -> 波波(吃一口) -> 最终返回 雪糕棍
    var s = products.reduce((a, value) => {
      return a + value.price * value.count
    }, 0)
    // 首次1: a = 0,  累加第一个商品的数量x单价, 传递给下一次循环
    // 第2次: a = 上一次的返回值, 累加当前的, 再传给下一次
    // 第3次: a = 上一次的返回值, 累加当前的, 再传给下一次
    // ...
    // 最终reduce的结果就是 最后一次的 返回值
​
    console.log(s);
​
  </script>
</body>
​
</html>

let/const

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>let_const</title>
</head>
​
<body>
  <script>
    // let/const: 都是 ES6 中推出的新特性, 用来声明变量
    // 特征1: 没有全局变量污染, 存储在 script 作用域中
    // 特征2: 没有声明提升(不准确) -- 作者设定了 暂存死区 的概念
​
    // 没声明a: 报错 a is not defined
    // let声明的变量: 有声明提升, 阅读报错发现--知道a的存在
    // 报错: cannot access 'a' before initialization
    //      不能    访问    a  在  初始化代码 之前
    // 准确解释: let 提升了, 但是在声明代码行执行之前, 一直处于暂存死区状态 -- 无法使用 
    // 作者想法: 没有去掉声明提升, 但是利用报错强制用户必须在声明之后再使用变量 -- 效果相当于没有声明提升
    let a
    console.log(a);
​
    // const: 常量,  声明时必须赋值, 之后就无法重新赋值
    const PI = 3.14
    // const A  //必须赋值, 否则报错
​
    // PI = 999 //无法重新赋值
​
    // 注意: 常量的值如果是对象类型, 可以修改
    const 家乐的女孩 = { name: '古力娜扎', hair: '黑色' }
    // 对象特殊: 不更换地址, 可以修改其中的值
    家乐的女孩.hair = '金黄色'
    家乐的女孩.age = 33
​
    console.log(家乐的女孩);
​
    // 不允许换人, 指向其他内存地址
    // 家乐的女孩 = { name: "迪丽热巴" }
​
  </script>
</body>
​
</html>

保护对象的方式

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>保护对象的方式</title>
</head>
​
<body>
  <script>
    // defineProperty: 操作方式更加 细致, 可以配置每个属性
    // 当前学习的: 直接配置整个对象 而非某个属性 -- 影响范围广
​
    'use strict'
​
    var emp = { ename: "凯凯", age: 33 }
​
    // 修改操作有: 增删改
​
    // 让对象无法 增加 属性
    // prevent阻止 Extensions扩展
    // Object.preventExtensions(emp)
​
    // 不可 增删
    // seal: 密封
    // Object.seal(emp)
​
    // 不可 增删改
    // freeze: 冻结
    Object.freeze(emp)
​
    // emp.wife = '迪丽热巴'
    // delete emp.age
    emp.age = 49
    console.log(emp);
  </script>
</body>
​
</html>

数组展开符

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>数组的展开语法</title>
</head>
​
<body>
  <script>
    // 来自 ES6 的新增语法糖: 展开语法
    var a = [11, 22, 33, 767, 77, 88, 88]
    // ... 运算符: 去掉 数组的 []
    var b = [44, 55, ...a]
​
    console.log(b);
​
    // max: 的参数只能是 1个1个的, 不接数组类型
    // ES6之前:  Math.max.apply(0, a)
    // ES6之后: ... 来去掉[], 把数组里面的东西拿出来
    var m = Math.max(...a)
    console.log(m);
  </script>
</body>
​
</html>

对象的展开语法

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>对象的展开语法</title>
</head>
​
<body>
  <script>
    var a = { x: 10, y: 20 }
​
    var b = { y: 30, z: 40 }
​
    // ... : 去掉对象/数组的 外层括号
    // 同名的属性, 后写的覆盖先写的
    var c = { w: 100, ...a, ...b }
    // 相当于: { w:100, x: 10, y: 20, y: 30, z: 40 }
​
    console.log(c);
  </script>
</body>
​
</html>

剩余参数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>剩余参数</title>
</head>

<body>
  <script>
    // ES6之前: 通过 arguments 来使用函数的 不固定数量的参数
    // arguments的缺点:
    // - 隐藏的变量
    // - 类型是 类数组, 不具备数组的各种方法

    // ES6之后: 允许用 ... 来表示接受剩余参数
    function show(...args) {
      // 习惯上, 变量名叫 args, 名字随便
      console.log(arguments);
      console.log(args);
      // args: 数组类型, 可以用数组的方法
      args.push(1111)
    }

    show(11, 22, 33, 44, 55)

    // 剩余参数
    // ...: 代表剩余参数, 存储剩下的 没有专门声明参数的
    function showGirls(主食, ...菜品) {
      console.log(主食);
      console.log(菜品);
    }

    showGirls('米饭', '炒菜', '海鲜汤', '炒肉')
  </script>

  <script>
    // ...: 固定运算符
    // nums: 自定义变量, 名字无所谓
    // ... 就代表把收到的所有参数, 保存到后方的变量里(数组类型)
    function show(x, ...nums) {
      console.log('nums:', nums);
      // nums = [2, 3, 4]
      // nums[0]: 数组的下标取值, 获取数据
    }

    function show(x, y, ...nums) {
      console.log('x:', x);
      console.log('nums:', nums);
    }

    show(1, 2, 3, 4)
  </script>
</body>

</html>

参数默认值

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>参数的默认值</title>
</head>

<body>
  <script>
    // ES6的 函数参数增强特征: 可以为函数的参数设置默认值
    function show(name = '家乐') {
      // 相当于: var name = '家乐'
      // 如果传参了:  name = '思琦'
      console.log(name);
    }

    show() // 没有传递参数, 则 采用默认值
    show('思奇') // 如果传递参数, 则 使用传递的值
  </script>
</body>

</html>

数组解构

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>数组解构语法</title>
</head>

<body>
  <script>
    // 解构语法: 可以快速从数组中抓取元素 保存到变量里
    var skills = ['唱', '跳', 'rap', '打篮球']
    // 保存到不同的变量里
    let s1 = skills[0]
    let s2 = skills[1]
    let s3 = skills[2]
    let s4 = skills[3]

    // 解构语法:
    let [q, w, e, r] = skills

    console.log(q, w, e, r);

    // 练习
    var r1 = [10, 20, 50] // 分别存储到变量  x, y, z 中

    var [x, y, z] = r1
    console.log(x, y, z);

    // 可选解构
    var names = ['凯凯', '楠楠', '亮亮', '铭铭']
    //             n1     n2             n3
    var [n1, n2, , n3] = names  //不想解构的, 可以省略不写
    console.log(n1, n2, n3);

    // 灵活用法: 互换变量的值
    var yy = 100
    var rr = 200;

    // [yy, rr] = [200, 100]

    // 先组合出一个数组 [rr, yy] == [200, 100]
    // 然后解构
    [yy, rr] = [rr, yy] // = [200, 100]
    console.log(yy, rr);
  </script>
</body>

</html>

对象解构

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>对象解构语法</title>
</head>

<body>
  <script>
    // 
    var emp = {
      ename: "家乐",
      age: 18
    }

    // 传统写法
    var ename = emp.ename
    var age = emp.age

    // 解构语法
    var { ename, age } = emp
    console.log(ename, age);

    // 练习
    var p1 = { pname: "iPhone", price: 9999, count: 5 }
    // 把属性读取出来, 保存在变量里
    var { count, pname } = p1
    // 对象类型:无序的
    console.log(count, pname);

    // 起别名
    var p2 = { pname: "mike", count: 10 }

    // count -> c2
    var { count: c2 } = p2
    console.log(c2);
  </script>
</body>

</html>

复杂解构

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>复杂解构</title>
</head>

<body>
  <script>
    var emp = {
      ename: '家乐',
      age: 23,
      skills: ['唱', '跳', 'rap', '篮球'],
      love: ['杨洋', '王一博', '胡歌']
    }

    // 把内容解构出来
    var { ename, age: eage, skills: [q, w, e, r], love } = emp

    console.log(ename, eage, q, w, e, r, love);

    var iPhone = {
      maker: "Apple",
      price: 9999,
      tags: ['高刷', 'iOS', '最佳cpu'],
    }

    // 
    var { maker, price: p_price, tags: [tag1, tag2, tag3] } = iPhone

    console.log(maker, p_price, tag1, tag2, tag3);
  </script>
</body>

</html>

参数解构

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>函数的参数解构</title>
</head>

<body>
  <script>
    // 计算 立方体的 体积
    // 参数解构: 收到对象类型的参数, 直接解构
    function volume({ x, y, z }) {
      // 解构出来再用
      // const { x, y, z } = rect
      console.log(x * y * z);
      // console.log(rect.x * rect.y * rect.z);
    }

    volume({ x: 10, y: 20, z: 30 })


    var emps = [
      { ename: '凯凯1', age: 23, salary: 7000, married: true },
      { ename: '凯凯2', age: 33, salary: 17000, married: true },
      { ename: '凯凯3', age: 44, salary: 9000, married: false },
      { ename: '凯凯4', age: 28, salary: 12000, married: true },
      { ename: '凯凯5', age: 35, salary: 33000, married: false },
    ]
    // map: 把数组映射成 HTML 代码
    var a = emps.map(value => {
      return `<a class='${value.married ? 'ok' : 'err'}'>${value.ename}-${value.age}-${value.salary}</a>`
    })

    // 利用解构简化:
    var a = emps.map(value => {
      const { ename, age, salary, married } = value

      return `<a class='${married ? 'ok' : 'err'}'>${ename}-${age}-${salary}</a>`
    })

    // 参数解构: 参数解构, 需要用() 包围, 防止歧义
    var a = emps.map(({ ename, age, salary, married }) => {
      return `<a class='${married ? 'ok' : 'err'}'>${ename}-${age}-${salary}</a>`
    })

    console.log(a)

    var products = [
      { price: 1000, count: 4, pname: '凯凯1' },
      { price: 2000, count: 7, pname: '凯凯2' },
      { price: 1500, count: 6, pname: '凯凯3' },
      { price: 1200, count: 1, pname: '凯凯4' },
    ]
    // 过滤出总价格大于 4000 的项目:  filter
    var a = products.filter((value) => value.price * value.count > 4000)

    var a = products.filter(({ price, count }) => price * count > 4000)

    // 别名:
    var a = products.filter(({ price: p, count: c }) => p * c > 4000)
    
    console.log(a);
  </script>
</body>

</html>

class语法

静态属性

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>class语法</title>
</head>

<body>
  <script>
    // class语法: 从 Java 语言中出现
    // JS最初开发的时候, 作者认为 Java 的class语法过于庞大, 所以设计了更加简单的 万物皆对象/构造函数 语法

    // 随着时间的发展: JS竟然一跃成为 非常火的语言, 广受关注
    // 群众的呼声: 希望引入 Java 中更加强大的 class 语法, 在 2015 年 的 ES6 中引入了 class 语法 -- 对于会java的程序员很友好

    // 但是: 尴尬的情况 -- 传统的JS程序员比较守旧, 排斥 class 语法
    // 所以: 目前class好用 但是 用的少

    // 对象类型: 在js中
    var emp = {
      ename: "凯凯",
      age: 22,
      phone: '18800120033'
    }

    console.log(emp);

    // 关键词 class 用于声明 类(java的称呼)
    class Emp1 {
      // static: 称为静态属性(java的称呼)
      // 特点: 存储在对象中, 可以直接使用
      static ename = '凯凯'
      static age = 22
      static phone = '19900120033'
    }
    // 本质上是 JS 的函数
    console.dir(Emp1);

    console.log(Emp1.ename, Emp1.age, Emp1.phone);
  </script>

  <script>
    // JS 的对象
    var math = {
      PI: 3.1415,
      LN2: 0.69314,
      E: 2.718,
      LN2: 0.693
    }

    // 改造成: class 的写法, 名字不能重复, 所以叫 math1
    class math1 {
      // 在JS中, 结尾分号可以省略
      static PI = 3.1415;
      static LN2 = 0.69314;
      static E = 2.718;
      static LN2 = 0.693;
    }

    console.dir(math1);
    console.log(math1.PI, math1.LN2);
  </script>
</body>

</html>

构造函数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>构造语法-class</title>
</head>

<body>
  <!-- 
    总结:
    在 JS中制作一个构造函数, 需要两步:
    - 构造函数本身
    - 构造函数的原型 prototype 中添加共享方法  (需要程序员会原型)

    在 java中制作一个构造函数
    - constructor: 固定的名称, 放构造函数
    - 不需要会原型, 就能制作出完美的构造函数 --(对java程序员极具吸引力)
   -->


  <script>
    // JS的构造函数: 分两部分书写
    // 1. 生成对象 -矩形
    function Rect(x, y) {
      // this是什么?  new触发, 当前生成的对象, 此处就是r1
      this.x = x
      this.y = y
    }

    // 2. 原型中存储共享的方法
    // JS程序员必须懂原型, 才能写出构造函数
    Rect.prototype.area = function () {
      return this.x * this.y
    }

    Rect.prototype.perimeter = function () {
      return (this.x + this.y) * 2
    }

    var r1 = new Rect(10, 30)
    console.dir(Rect);
    console.log(r1);
  </script>

  <script>
    // java的构造函数 -- 精髓
    class Rect1 {
      // java的class中, 不需要function关键词, 就能声明函数
      // 关键词,不可修改: constructor, 固定名称, 称为 构造方法
      // 当 new 运算符时, 自动触发
      constructor(x, y) {
        console.log('constructor: 被触发');
        this.x = x
        this.y = y
      }

      // 在java中, 书写的所有方法, 都会 `自动` 放到原型里
      area() {
        return this.x * this.y
      }

      // get关键词: 变身为计算属性, 使用时不用() 能自动触发
      get perimeter() {
        return (this.x + this.y) * 2
      }
    }

    var r2 = new Rect1(20, 50)
    console.dir(Rect1); //查看 prototype 属性
    console.log(r2);

    console.log(r2.area());
    // 由于 是 get 计算属性, 所以不用() 自动就能触发
    console.log(r2.perimeter);
  </script>
</body>

</html>

继承

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>继承</title>
</head>

<body>
  <script>
    // 面向对象开发的 三大特征: 封装 继承 多态
    // 封装: 利用{} 把一些代码括起来, 形成一个整体, 以后可以复用
    //       在JS中的具体表现:  函数

    // 继承: 当使用对象的一个属性时, 自身没有, 则到父中查找
    //       在JS中的具体表现:  原型链

    // 多态: 同一个构造函数生成的对象, 对象可以自定义. 效果如下:
    // 自身有用自己的, 自身没有用父的 -- 龙生九子,各有不同
    var emp = { age: 99 }
    emp.__proto__.age = 18
    console.log(emp.age);
    console.log(emp);

    class A {
      show() {
        console.log('我是A');
      }
    }

    // extends: 继承.  本质上把原型链 指向 A,  认A做父
    class B extends A { }

    console.dir(B); //查看原型 prototype
    var b = new B()
    b.show()
  </script>
</body>

</html>

多态

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>多态</title>
</head>

<body>
  <script>
    class A {
      constructor(x, y) {
        this.x = x
        this.y = y
      }

      area() {
        return this.x * this.y
      }
    }
    // B继承A, 可以使用A的东西
    class B extends A {
      // java的称呼: 重写 -- 书写一个与父中相同的函数
      // 根据原型链的就近原则: B中有area, A中也有area
      // 优先使用 B中的area : 自己有用自己的
      area() {
        // java提供了关键词 super: 代表父元素, 即A
        console.log(super.area())

        console.log('我是B中的area');
      }
    }

    var b = new B(10, 50)
    console.log(b);
    console.log(b.area());
  </script>
</body>

</html>

class总结

class是来自java 语言的一套语法, 特别擅长制作 构造函数

但是: JS开发实战中, 传统的 function 使用更多, class使用较少

  • 建议: 了解为主

  • 实在感兴趣: 百度搜下视频 JS的class语法

作业题

var products = [
  { pname: "iPhone1", price: 8999, count: 4, checked:true },
  { pname: "iPhone2", price: 7999, count: 1, checked:true },
  { pname: "iPhone3", price: 6999, count: 6, checked:false },
  { pname: "iPhone4", price: 5999, count: 8, checked:true },
]

// 计算出 checked 为 true, 即 勾选状态 为真的 商品总价格,  --- 关联以后的 购物车制作
// 分别用foreach 和 reduce 两种方式实现
// 使用参数的解构语法书写
var obj = {
    nickname: "睡懵的渣皇",
    rid: 8922441,
    roomName: "有梦想谁都了不起!!",
    tags:['颜值', '帅气逼人', '直播达人']
}

// 把 obj 中的变量都解构出来,  rid 别名为 roomId.   tags解构成 t1 t2 t3 
// JS的构造函数如下, 修改成 class 语法
function Cube(width, length, height){
    this.width = width
    this.length = length
    this.height = height
}

Cube.desc = '立方体'

Cube.prototype.volume = function (){
    return this.width * this.length * this.height
}

Cube.prototype.area = function (){
    const {width:w, height:h, length:l} = this
    return (w*h + w*l + h+l)*2
}

Cube.prototype.perimeter = function (){
    const {width:w, height:h, length:l} = this
    return (w+h+l)*4
}

var c1 = new Cube(100, 40, 22)

console.log(Cube.desc)

console.log(c1.volume())
console.log(c1.area())
console.log(c1.perimeter())

JSCORE06

复习

  • 数组的高阶函数

    • every: 判断数组中每一个元素都符合条件. 类似逻辑与, 都真 才是真

    • some: 判断数组中至少有一个元素符合条件. 类似逻辑或, 有真 就是真

    • filter: 过滤器. 把满足条件的元素 组成新的数组

    • map: 映射. 把数组的数据处理后 返回值组成新的数组. 特别适合 数据 ->HTML

    • forEach: 简单的遍历数组. 没有返回值

      • ES6提供了: for...of 也能快速遍历数组

    • reduce: 归纳. 把数组中的元素挨个遍历后, 归纳成一个值

  • ES6新增的语法

    • 展开符 ...

      • ...数组: 把数组展开, 即去掉数组的[]包围

      • ...对象: 把对象展开, 即去掉对象的{}包围

    • 函数增强语法

      • 剩余参数: function 函数名(...变量)

        • 变量是数组类型, 接收收到的所有参数, 作用与arguments一样

      • 参数默认值: function 函数(参数=值)

        • 使用函数时, 如果不传递参数, 则 参数使用默认值

    • 解构语法

      • 数组解构: var [a, b, ,c] = [11, 22, 33, 44]

        • a=11; b=22; c=44

      • 对象解构: var {age, salary, sid} = {sid:11, salary:9999, age:33}

        • 别名语法: var {salary: s} = {salary: 4488}

      • 函数的参数解构 - 必须能看得懂

        var obj = {x: 10, y:30, z: 44}
        ​
        function show( {x, y} ){
            return x + y
        }
        ​
        show(obj)
  • class语法

    • 来自于 Java 的语法, 特别擅长 构造函数 的创建

    • 但是: 好用 然而 JS程序员比较守旧 -- 目前普及率不高

    • 静态属性写法: 不如JS方便

      // js中
      var emp1 = {ename:'凯凯', age:33}
      emp1.ename
      ​
      // java
      class emp2 {
          static ename = '凯凯';
          static age = 33
      }
      emp2.ename
    • 构造函数

      class Emp{
          // 固定名称的函数, 在new 的时候触发
          constructor(x, y){
              this.x = x
              this.y = y
          }
          
          // 全自动操作原型, 把所有的函数自动存储到原型中
          area(){
              return this.x * this.y
          }
      }
      ​
      var e =  new Emp(11,22)
    • 面向对象的 3 大特征:

      • 封装: 用{}把代码封装在一起, 后续随时触发

        • 具体表现: 函数

      • 继承: 原型链指向其他的对象. 自己没有的 到原型中查找

      • 多态: 子中可以重写一个 与 父相同的方法, 使用时优先使用子中带有的

        class A{
            show(){
                console.log('我是A')
            }
        }
        ​
        class B extends A{
            // B中可以书写一个同名的 show方法
            // 使用时, 优先使用自身的show方法
            show(){
                console.log('我是B')
                // 关键词: super
                super.show() // 使用父, A的show方法
            }
        }
        ​
        var b = new B()
        b.show() // B自身没有show方法, 则使用父A的 show方法

回调地狱

<!DOCTYPE html>
<html lang="en">
​
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>回调地狱</title>
</head>
​
<body>
    <!-- 回调地狱: 在JS中进行一些异步操作时(网络操作), 层层嵌套, 会导致代码阅读困难 -->
​
    <!-- 如下场景: 注册操作 -->
    <!-- 1. 先发请求, 验证用户名是否合法 -->
    <!-- 2. 再发请求, 验证邮箱是否合法 -->
    <!-- 3. 再发请求, 验证手机号是否合法 -->
    <!-- 4. 再发请求, 开始注册 -->
​
    <script>
        function register() {
            // 用 定时器 来模拟 延时操作, 0.5 秒完成
            console.log('检查用户名...');
            setTimeout(() => {
                // 通过随机数来 随机决定成功与否
                if (Math.random() > 0.7) {
                    console.log('用户名正确, 开始验证邮箱...');
                    setTimeout(() => {
                        if (Math.random() > 0.7) {
                            console.log('邮箱正确, 开始验证手机号...');
                            setTimeout(() => {
                                if (Math.random() > 0.7) {
                                    console.log('手机号正确, 开始注册...');
                                    setTimeout(() => {
                                        if (Math.random() > 0.7) {
                                            console.log('注册成功');
                                        } else {
                                            console.log('注册失败!');
                                        }
                                    }, 500);
                                } else {
                                    console.log('手机号错误!');
                                }
                            }, 500);
                        } else {
                            console.log('邮箱错误');
                        }
                    }, 500);
                } else {
                    console.log('用户名错误!');
                }
            }, 500);
        }
    </script>
</body>
​
</html>

解决方案 : Promise

Promise

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Promise</title>
</head>
​
<body>
  <!-- Promise: ES6提供的一个构造函数, 专门解决回调地狱问题 -->
  <!-- 代码量一点都不少, 只是格式上更加容易阅读 -->
  <script>
    // 先练习基本的格式
    // 固定写法, 先抄10遍
    new Promise((resolve, reject) => { }).then(res => { }).catch(err => { })
​
    // 安排工作.做完了.失败了
​
    // Promise: 承诺
    // resolve: 解决
    // reject: 拒绝
    // then: 然后
    // catch: 抓取
    new Promise((resolve, reject) => {
      // 当调用 resolve 时, 会触发 then 中的箭头函数
      // resolve的参数, 会传递给 then 中的箭头函数
      // resolve('调用resolve') // 这代表成功
​
      // reject 会触发 catch, 其参数传递到 catch 中
      reject('调用reject') // 这代表失败
​
      // resolve和 reject 互斥, 同一时间只能触发一种
    })
      .then(res => console.log('res:', res))
      .catch(err => console.log('err:', err))
​
  </script>
</body>
​
</html>

 

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Promise的使用</title>
</head>
​
<body>
  <script>
    // 家乐 学开车
    // 教练: 踩油门 车就能跑;  踩刹车 车就能停
    // 家乐问题: 油门哪来的? 踩油门为什么能跑, 发动机原理是什么?
    // 但是: 家乐应聘当司机
    // 考试题: 说说汽车的原理 -- 面试造火箭 工作拧螺丝
    // 面试考 Promise原理的极少, 但是确实存在 -- 不是课上讲解的
    //  -- 考 3-4年 高级工程师的题 -- 扩展视频
​
    function jiale() {
      return new Promise((resolve, reject) => {
        // 利用定时器模拟异步操作
        setTimeout(() => {
          var a = Math.random()
          if (a > 0.5) {
            resolve('家乐求婚成功:' + a)
          } else {
            reject('家乐被拒绝了:' + a)
          }
        }, 1000);
      })
​
      // return p
    }
​
    // 波波求婚
    function bobo() {
      // prom : 安装 ES6 插件后
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          Math.random() > 0.5 ? resolve('波波求婚成功') : reject('波波失败!')
        }, 1000);
      });
    }
​
    function haoyang() {
      // prom
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          Math.random() > 0.5 ? resolve('浩洋成功求婚') : reject('浩洋失败!')
        }, 1000);
      });
    }
​
    jiale()
      .then(res => {
        console.log('res:', res)
        // 如果成功, 则触发波波的求婚操作
        return bobo()
        // 返回值, 会触发下一次的 .then
      })
      .then(res => {
        console.log('res:', res)
        // 
        return haoyang()
      })
      .then(res => console.log('res:', res))
      .catch(err => console.log('err:', err))
  </script>
</body>
​
</html>

关于Promise的常考概念:

Promise有3种状态

  • 刚new出来: new Promise -- pending

  • 触发 resolve 之后 -- fulfilled

  • 触发 reject 之后 -- rejected

正则表达式

官网: 正则表达式 - JavaScript | MDN

一套用与模糊匹配字符串格式的语法

字符含义
\依照下列规则匹配:在非特殊字符之前的反斜杠表示下一个字符是特殊字符,不能按照字面理解。例如,前面没有 "" 的 "b" 通常匹配小写字母 "b",即字符会被作为字面理解,无论它出现在哪里。但如果前面加了 "",它将不再匹配任何字符,而是表示一个字符边界。在特殊字符之前的反斜杠表示下一个字符不是特殊字符,应该按照字面理解。详情请参阅下文中的 "转义(Escaping)" 部分。如果你想将字符串传递给 RegExp 构造函数,不要忘记在字符串字面量中反斜杠是转义字符。所以为了在模式中添加一个反斜杠,你需要在字符串字面量中转义它。/[a-z]\s/inew RegExp("[a-z]\\s", "i") 创建了相同的正则表达式:一个用于搜索后面紧跟着空白字符(\s 可看后文)并且在 a-z 范围内的任意字符的表达式。为了通过字符串字面量给 RegExp 构造函数创建包含反斜杠的表达式,你需要在字符串级别和正则表达式级别都对它进行转义。例如 /[a-z]:\\/inew RegExp("[a-z]:\\\\","i") 会创建相同的表达式,即匹配类似 "C:" 字符串。
^匹配输入的开始。如果多行标志被设置为 true,那么也匹配换行符后紧跟的位置。例如,/^A/ 并不会匹配 "an A" 中的 'A',但是会匹配 "An E" 中的 'A'。当 '^' 作为第一个字符出现在一个字符集合模式时,它将会有不同的含义。反向字符集合 一节有详细介绍和示例。
$匹配输入的结束。如果多行标志被设置为 true,那么也匹配换行符前的位置。例如,/t$/ 并不会匹配 "eater" 中的 't',但是会匹配 "eat" 中的 't'。
*匹配前一个表达式 0 次或多次。等价于 {0,}。例如,/bo*/ 会匹配 "A ghost boooooed" 中的 'booooo' 和 "A bird warbled" 中的 'b',但是在 "A goat grunted" 中不会匹配任何内容。
+匹配前面一个表达式 1 次或者多次。等价于 {1,}。例如,/a+/ 会匹配 "candy" 中的 'a' 和 "caaaaaaandy" 中所有的 'a',但是在 "cndy" 中不会匹配任何内容。
?匹配前面一个表达式 0 次或者 1 次。等价于 {0,1}。例如,/e?le?/ 匹配 "angel" 中的 'el'、"angle" 中的 'le' 以及 "oslo' 中的 'l'。如果紧跟在任何量词 *、 +、? 或 {} 的后面,将会使量词变为非贪婪(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)正好相反。例如,对 "123abc" 使用 /\d+/ 将会匹配 "123",而使用 /\d+?/ 则只会匹配到 "1"。还用于先行断言中,如本表的 x(?=y)x(?!y) 条目所述。
.(小数点)默认匹配除换行符之外的任何单个字符。例如,/.n/ 将会匹配 "nay, an apple is on the tree" 中的 'an' 和 'on',但是不会匹配 'nay'。如果 s ("dotAll") 标志位被设为 true,它也会匹配换行符。
(x)像下面的例子展示的那样,它会匹配 'x' 并且记住匹配项。其中括号被称为捕获括号。模式 /(foo) (bar) \1 \2/ 中的 '(foo)' 和 '(bar)' 匹配并记住字符串 "foo bar foo bar" 中前两个单词。模式中的 \1\2 表示第一个和第二个被捕获括号匹配的子字符串,即 foobar,匹配了原字符串中的后两个单词。注意 \1\2、...、\n 是用在正则表达式的匹配环节,详情可以参阅后文的 \n 条目。而在正则表达式的替换环节,则要使用像 $1$2、...、$n 这样的语法,例如,'bar foo'.replace(/(...) (...)/, '$2 $1')$& 表示整个用于匹配的原字符串。
(?:x)匹配 'x' 但是不记住匹配项。这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。看看这个例子 /(?:foo){1,2}/。如果表达式是 /foo{1,2}/{1,2} 将只应用于 'foo' 的最后一个字符 'o'。如果使用非捕获括号,则 {1,2} 会应用于整个 'foo' 单词。更多信息,可以参阅下文的 Using parentheses 条目.
x(?=y)匹配'x'仅仅当'x'后面跟着'y'.这种叫做先行断言。例如,/Jack(?=Sprat)/会匹配到'Jack'仅当它后面跟着'Sprat'。/Jack(?=Sprat|Frost)/匹配‘Jack’仅当它后面跟着'Sprat'或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。
(?<=y)x匹配'x'仅当'x'前面是'y'.这种叫做后行断言。例如,/(?<=Jack)Sprat/会匹配到' Sprat '仅仅当它前面是' Jack '。/(?<=Jack|Tom)Sprat/匹配‘ Sprat ’仅仅当它前面是'Jack'或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。
x(?!y)仅仅当'x'后面不跟着'y'时匹配'x',这被称为正向否定查找。例如,仅仅当这个数字后面没有跟小数点的时候,/\d+(?!.)/ 匹配一个数字。正则表达式/\d+(?!.)/.exec("3.141")匹配‘141’而不是‘3.141’
(?<!*y*)*x*仅仅当'x'前面不是'y'时匹配'x',这被称为反向否定查找。例如, 仅仅当这个数字前面没有负号的时候,/(?<!-)\d+/ 匹配一个数字。 /(?<!-)\d+/.exec('3') 匹配到 "3". /(?<!-)\d+/.exec('-3') 因为这个数字前有负号,所以没有匹配到。
x|y匹配‘x’或者‘y’。例如,/green|red/匹配“green apple”中的‘green’和“red apple”中的‘red’
{n}n 是一个正整数,匹配了前面一个字符刚好出现了 n 次。 比如, /a{2}/ 不会匹配“candy”中的'a',但是会匹配“caandy”中所有的 a,以及“caaandy”中的前两个'a'。
{n,}n是一个正整数,匹配前一个字符至少出现了n次。例如, /a{2,}/ 匹配 "aa", "aaaa" 和 "aaaaa" 但是不匹配 "a"。
{n,m}n 和 m 都是整数。匹配前面的字符至少n次,最多m次。如果 n 或者 m 的值是0, 这个值被忽略。例如,/a{1, 3}/ 并不匹配“cndy”中的任意字符,匹配“candy”中的a,匹配“caandy”中的前两个a,也匹配“caaaaaaandy”中的前三个a。注意,当匹配”caaaaaaandy“时,匹配的值是“aaa”,即使原始的字符串中有更多的a。
[xyz\]一个字符集合。匹配方括号中的任意字符,包括转义序列。你可以使用破折号(-)来指定一个字符范围。对于点(.)和星号(*)这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义,不过转义也是起作用的。 例如,[abcd] 和[a-d]是一样的。他们都匹配"brisket"中的‘b’,也都匹配“city”中的‘c’。/[a-z.]+/ 和/[\w.]+/与字符串“test.i.ng”匹配。
[^xyz\]一个反向字符集。也就是说, 它匹配任何没有包含在方括号中的字符。你可以使用破折号(-)来指定一个字符范围。任何普通字符在这里都是起作用的。例如,abc 和 a-c 是一样的。他们匹配"brisket"中的‘r’,也匹配“chop”中的‘h’。
[\b\]匹配一个退格(U+0008)。(不要和\b混淆了。)
\b匹配一个词的边界。一个词的边界就是一个词不被另外一个“字”字符跟随的位置或者前面跟其他“字”字符的位置,例如在字母和空格之间。注意,匹配中不包括匹配的字边界。换句话说,一个匹配的词的边界的内容的长度是0。(不要和[\b]混淆了)使用"moon"举例: /\bm/匹配“moon”中的‘m’; /oo\b/并不匹配"moon"中的'oo',因为'oo'被一个“字”字符'n'紧跟着。 /oon\b/匹配"moon"中的'oon',因为'oon'是这个字符串的结束部分。这样他没有被一个“字”字符紧跟着。 /\w\b\w/将不能匹配任何字符串,因为在一个单词中间的字符永远也不可能同时满足没有“字”字符跟随和有“字”字符跟随两种情况。备注: JavaScript的正则表达式引擎将特定的字符集定义为“字”字符。不在该集合中的任何字符都被认为是一个断词。这组字符相当有限:它只包括大写和小写的罗马字母,十进制数字和下划线字符。不幸的是,重要的字符,例如“é”或“ü”,被视为断词。
\B匹配一个非单词边界。匹配如下几种情况:字符串第一个字符为非“字”字符字符串最后一个字符为非“字”字符两个单词字符之间两个非单词字符之间空字符串例如,/\B../匹配"noonday"中的'oo', 而/y\B../匹配"possibly yesterday"中的’yes‘
\c*X*当X是处于A到Z之间的字符的时候,匹配字符串中的一个控制符。例如,/\cM/ 匹配字符串中的 control-M (U+000D)。
\d匹配一个数字。``等价于[0-9]。例如, /\d/ 或者 /[0-9]/ 匹配"B2 is the suite number."中的'2'。
\D匹配一个非数字字符。``等价于[^0-9]。例如, /\D/ 或者 /[^0-9]/ 匹配"B2 is the suite number."中的'B' 。
\f匹配一个换页符 (U+000C)。
\n匹配一个换行符 (U+000A)。
\r匹配一个回车符 (U+000D)。
\s匹配一个空白字符,包括空格、制表符、换页符和换行符。等价于[ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如, /\s\w*/ 匹配"foo bar."中的' bar'。经测试,\s不匹配"\u180e",在当前版本Chrome(v80.0.3987.122)和Firefox(76.0.1)控制台输入/\s/.test("\u180e")均返回false。
\S匹配一个非空白字符。等价于 [^\f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如,/\S\w*/ 匹配"foo bar."中的'foo'。
\t匹配一个水平制表符 (U+0009)。
\v匹配一个垂直制表符 (U+000B)。
\w匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_]。例如, /\w/ 匹配 "apple," 中的 'a',"$5.28,"中的 '5' 和 "3D." 中的 '3'。
\W匹配一个非单字字符。等价于 [^A-Za-z0-9_]。例如, /\W/ 或者 /[^A-Za-z0-9_]/ 匹配 "50%." 中的 '%'。
\*n*在正则表达式中,它返回最后的第n个子捕获匹配的子字符串(捕获的数目以左括号计数)。比如 /apple(,)\sorange\1/ 匹配"apple, orange, cherry, peach."中的'apple, orange,' 。
\0匹配 NULL(U+0000)字符, 不要在这后面跟其它小数,因为 \0<digits> 是一个八进制转义序列。
\xhh匹配一个两位十六进制数(\x00-\xFF)表示的字符。
\uhhhh匹配一个四位十六进制数表示的 UTF-16 代码单元。
\u{hhhh}或\u{hhhhh}(仅当设置了u标志时)匹配一个十六进制数表示的 Unicode 字符。

正则匹配

<!DOCTYPE html>
<html lang="en">
​
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>正则表达式</title>
</head>
​
<body>
  <script>
    // 正则表达式: Regular Expression  简称 RegExp
    // 正则不是 JS 的特性, 而是一套通用的 字符串模糊 匹配方案
    // 官方提供了一个字典: 一些字符 对应 一些特殊的说明含义
​
    // \d  : 匹配一个数字
​
    // 要求: 查找到 字符串中所有的 数字
​
    // match: 匹配.字符串的一个方法,用来查询出符合正则表达式要求的字符
    // 参数要求是正则表达式, 正则要求存放在 // 中
    // 类似于 字符串要放在 引号中;  "\d" -字符串   /\d/ -正则    
​
    // g: global 全局 ; 是正则表达式的修饰符, 代表匹配出所有符合条件的
    var words = '家乐兜里有500元, 花了100, 还剩400. Nice!'
​
    // 找到英文字母
    // []: 代表自定义的字符集合
​
    // 查询出中文:  中文在Unicode字典中的编码是 \u4e00 开始, \u9fa5 结束
    var a = words.match(/[\u4e00-\u9fa5]/g)
    console.log(a);
​
    // i : ignore 忽略, 属于正则修饰符, 代表 忽略大小写
    var a = words.match(/[a-z]/ig)
    console.log(a);
​
    var a = words.match(/[a-zA-Z]/g)
    console.log(a);
​
    var a = words.match(/[a-z]/g)
    console.log(a);
​
    var a = words.match(/\d/g)
    console.log(a);
  </script>
</body>
​
</html>

正则替换

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>正则替换</title>
</head>

<body>
  <script>
    // replace: 替换
    var words = '家乐欠债500元, 已还100, 还剩400未还'

    // 把数字变为 *
    // 把参数1 正则匹配到的字符, 替换成 参数2
    var a = words.replace(/\d/g, '*')
    var a = words.replace(/家乐/, '**')
    console.log(a);

    // 进阶用法: 搭配捕获组使用
    var phone = '18879798888'
    // 要求 把手机号中间 4位, 变为 ****
    var a = phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3')
    // 猜: $1 和 $3 是什么意思?
    // () :称为捕获组, 用于抓取出匹配到的指定内容
    // {n} : 有n个   例如 \d{3} 代表3个数字
    // $n: 代表 第n个捕获组中的值, 例如 $1就是第一个小括号里的值
    console.log(a);
    // 练习: 改为 188-7979-8888

    var phone = '18879798888'
    var a = phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3')
    console.log(a);

    // 颠倒位置 18879798888 -> 18888887979
    var a = phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1$3$2')
    //                    第1个()  第2个   第3个
    console.log(a);

  </script>
</body>

</html>

正则验证

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>正则验证</title>
</head>

<body>
  <script>
    // 通过浏览器 收集用户输入的号码
    var p = prompt('请输入手机号')
    console.log('p:', p)

    // 判定是否为手机号:  11位数字  1开头   第二位 3-9
    // ^ : 代表字符串的开头
    // $ : 代表字符串的结尾
    var phone_reg = /^1[3-9]\d{9}$/
    // 使用正则 来 验证字符串
    var a = phone_reg.test(p) // test:验证,测试
    console.log(a ? '是手机号' : '不是手机号');
  </script>
</body>

</html>

正则的构造方式

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>正则的构造方式</title>
</head>

<body>
  <script>
    // 所有的字面量, 都有对应的构造方式 -- 本体
    // new Array();  new Object();
    // new Number();  new String()
    // new RegExp()   创造正则

    var words = '亮亮99 涛涛88 家乐59 波波dd'

    // 构造方式创建正则
    // JS的 \ 是转义符, \d 转义成 d
    // 需要用 \\d 来转义成 普通的 \d
    var r = new RegExp('\\d\\d', 'g')
    var a = words.match(r)
    console.log(a)

    var a = words.match(/\d\d/g)
    console.log(a);
  </script>
</body>

</html>

关于JS高级

总体分两个环节:

  • 理论: 面试必考, 作为成熟的JS程序员就应该会的理论

    • 声明提升

    • 闭包

    • 原型

    • 精确配置对象

  • 实战应用

    • ES5新特性

    • ES6新特性 -- 实际开发用得到

      • 展开符

      • 解构

      • 函数增强

      • 正则

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值