解密JavaScript之旅

318 阅读17分钟

在本小册子中,您将学习使用 JavaScript 语法的编程基础知识。本课程涵盖的概念为在任何环境中使用 JavaScript 奠定了基础!

完成本课程后,您将能够了解到:

  • JavaScript?
  • 为什么叫JavaScript?
  • JavaScript的用途?
  • JavaScript解决了哪些问题?
  • 说到封装JavaScript你会想到哪些?
  • 注释是作用?
  • 数据类型可以做什么?
  • 运算符在哪些场景里使用?
  • 为啥叫条件语句?
  • 数组是什么?
  • 循环是什么?
  • 循环解决了哪些问题?
  • 何为对象?
  • 函数是什么?
  • 函数是如何调用和传参的?
  • 什么是闭包有什么副作用?
  • 日期有哪些方法?
  • 日期方法的使用场景?
  • 如何封装日期方法应用到实际项目中
  • 封装最常用的JS方法解决方案

欢迎学习JavaScript

我们很高兴您开始学习 JavaScript 的旅程!

概览

JavaScript 是一种多范式的动态语言,它包含类型、运算符、标准内置( built-in)对象和方法。它的语法来源于 JavaC,所以这两种语言的许多语法特性同样适用于 JavaScript

为什么到如今JavaScript依然很受大多数开发者的欢迎?

JavaScript 是目前所有主流浏览器上唯一支持的脚本语言,这也是早期JavaScript的唯一用途。其主要作用是在不与0服务器交互的情况下修改HTML页面内容,因此其最关键的部分是DOM(文档对象模型),也就是HTML元素的结构。通过Ajax可以使HTML页面通过JavaScript,在不重新加载页面的情况下从服务器上获取数据并显示,大幅提高用户体验。通过JavaScript,使Web页面发展成客户端成为可能。

现在我们开启JavaScript的旅程吧!

一.JavaScript

JavaScript 不难理解是一种编程语言,和python一样,都是按照一定的代码规范,根据一定的业务场景来实现想要的功能。

什么是代码规范,就是比如 你突然之间在脑海里想到了单词,你想定义一下,比如new发现在执行的过程中提示了错误,,这就是规范。

   new 
   error: new 为关键字,请重新定义值

X 还有就是() [] 这样的字符会经常用到,不能随意的去写要符合规范

 ( new code){ ()}  // 这样的错误写法,**程序无法识别**

 ( new code){ ()} // 这样就是对的,这就是程序里的规范,也是我们学习这个语言的规范**。**

二.为什么叫JavaScript

布兰登·艾奇(Brendan Eich,1961年~),1995年在网景公司,发明的JavaScript。一开始JavaScript叫做LiveScript,当时java非常火, 为了跟上时代的潮流,就叫做JavaScript,当时还出了很多vbScript,EcScript,等等就不一一介绍了,都被avaScript给打败了,所以现在的浏览器中,只运行一种脚本语言就是JavaScript,这就是为什么叫JavaScript

三.JavaScript的用途?

  • 页面交互上
  • 对浏览器赋予生命(现在可以这样说)
  • 更容易的操作Dom
  • 做页面缓存,cookies session

四.JavaScript解决了哪些问题?

  • 嵌入动态文本与Html文本实现交互
  • 读写Html元素,操作Dom,增删改查
  • 完成部分渲染指的是ajax请求
  • 基于V8引擎做前端node.js开发(不适合做CPU多核缓冲层)
  • 封装业务逻辑,逻辑清晰,易读性高

五.说到封装JavaScript你会想到哪些?

封装(encapsulation)

封装是面向对象的三个基本特征之一,将现实世界的事物抽象成计算机领域中的对象, 对象同时具有属性行为(方法),这种抽象就是封装

对象(object)

对象就是一组没有特定顺序的值。对象的每个属性或方法都由一个名称来标识,这个名称映射到一个值。

函数封装(function encapsulation)

将零散的的语句写进函数的花括号内,成为函数体,然后就可以调用了

    <body>
        <div>
            <h1>love china</h1>
        </div>
    </body>
   <script>
        var body = document.getElementsByTagName("body")[0];
        var h1 = document.createElement("h1");
        console.log(h1,"h1")
        body.style.backgroundColor="red"
        h1.innerText="是红的"; // 显示
        body.appendChild(h1) // appendChild() 方法向节点添加最后一个子节点。
    </script>
封装后
   <script>
       function num(){
            var body = document.getElementsByTagName("body")[0];
            var h1 = document.createElement("h1");
            console.log(h1,"h1")
            body.style.backgroundColor="red"
            h1.innerText="是红的"; // 显示
            body.appendChild(h1) // appendChild() 方法向节点添加最后一个子节点。
       }
       num()
    </script>
对象(object)--字面量表示法与命名空间
<script>
       var myNamespace = {
           myProp:'我的名字',
           myConf:{
               cache:true,
               lang:'zh'
           },
           // 基本方法
           myMethod:function(){
               console.log("一句不成问题的问题!")
           },
           // 根据配置信息进行输出
           readConf:function(){
               console.log(this.myConf.cache?'启用了缓存':'禁用了缓存','系统语言为:'+ (this.myConf.lang=="zh"?'中文':'English'))
           }

       }
       myNamespace.myMethod()
       myNamespace.readConf()
        //一句不成问题的问题!
        // 启用了缓存 系统语言为:中文
</script>

关心实例与原型之间联系的封装
<script>
        //假定我们把人看成一个对象,它有"名字"和"性别"两个属性(property),以它作为原型,通过字面量(对象直接量)表示如下:
      let Person = {
          name:'',
          sex:''
      }
      let man = {}; // 创建一个空对象

      man.name = "李雷";
      man.sex = "1";
      var women = {};
      women.name =  '韩梅梅' // 重新赋值名字
      women.sex = 10;// 重新赋值年龄
</script>
进阶封装-工厂模式
<script>
      function createPerson(name,sex){
          return {
              name:name,
              sex:sex
          }
      }
      // 变体

      function createPersonHandle(name,sex){
          var o = new Object();
          o.name = name;
          o.sex = sex;
          return o
      }
      var admin = createPerson('李雷',1)
      var man = createPersonHandle('韩梅梅',10)
      console.log(admin,man)
</script>
 // {name: '李雷', sex: 1}name: "李雷"sex: 1[[Prototype]]: Object {name: '韩梅梅', sex: 10}
构造函数式模式
 <script>
       function Person(name,sex){
           console.log(this,"&&&&&&&&&") // window
           this.name = name;
           this.sex = sex;
           this.sayName = function(){
               console.log(this.name)
           }
       }
       var adam = new Person("李雷",10)
       var eve = new Person("韩梅梅",15)
       console.log(adam,eve) // 李雷 // 韩梅梅
</script>
闭包定义常量的封装
    <script>
        var PI = (function(){
            var _pi = 3.14115926;
            return {
                get:function(){
                    return _pi
                }
            }
        }()) // 回调
        console.log(PI.get()) // 3.14115926
    </script>
对象(object)--通过访问器属性的封装
    <script>
         var person ={
             name:"张国伟"
         };

         // 定义一个只有getter的age属性
         Object.defineProperty(person,"age",{
             value:new Date().getFullYear() - 1998,
             get function() {
                 return this.age
             }

         })
         console.log(person.age); // 24
         person.age = 22;
         console.log(person.age) // 24
    </script>
类式封装
<script>
        // 内部通过this指向当前对象,通过this添加属性或者方法
        var Book = function (id, name, price) {

            // 私有属性

            var name = "",
                price = 0;

            // 私有方法
            function checkId() {

            }
            // 对象共有属性
            this.id = id;

            //特权方法--- 可操作私有属性的方法
            this.getName = function () { }
            this.setName = function () { }
            this.getPrice = function () { }
            this.setPrice = function () { }

            // 对象共有方法

            this.copy = function () { }
            // 安全模式

            if (this instanceof Book) {
                this.setName(name);
                this.setPrice(price);
            } else {
                return new Book(id, name, price)
            }
        }
        // 类静态共有属性和方法(实例对象不能访问)
        Book.isChinses = true;

        Book.resetTime = function () {
            console.log("new Time")
        }
        // 共有属性和方法
        Book.prototype = {
            isJSBook: false,
            display: function () {
                console.log("display")
            }
        }
</script>

五.注释是作用?

ECMAScript 采用 C 语言风格的注释,包括单行注释和块注释。单行注释以两个斜杠字符开头, 如: // 单行注释 块注释以一个斜杠和一个星号(/*)开头, 以它们的反向组合(*/)结尾, 如: / 这是多行 注释 /

六.数据类型可以做什么?

①避免浪费内存防止内存不够用

②解决“存”的问题:决定使用此类型需要开辟内存空间大小

③解决“取”的问题:改变看内存视角,可以一个类型一个类型的看,而非一个比特位一个比特位的看。

1.整形:

image.png 其中每个整形又分为有符号类型的和无符号类型的即unsigned x和signed x,一般而言int是signed int其他也相似,特别的char有三种char,unsigned char,char到底是有符号还是无符号取决于编译器的实现。 有符号类型与无符号类型数据“有效”的二进制位不同,最大值不同,范围不同,就比如int的有符号和无符号,一个范围是-231231-1,另外一个是02^32-1.

2.浮点数 float[4字节]: 精度较低,存储数值范围较小 double[8字节]: 精度较高

3.构造类型/自定义类型

数组类型

结构体类型 关键字 struct

枚举类型 关键字 enum

联合类型 关键字 union

  1. 指针类型 int*,char*,void等[4/8字节]

由于在C语言中是这样的数据类型,为了两者更容易区分,或者两者不会打架,JS的数据类型是这样的:

ECMAScript 有 6 种简单数据类型(也称为原始类型):Undefined(未定义)NullBoolean(布尔值)Number(数值)String(字符串)Symbol(符号)Symbol(符号)是 ECMAScript 6 新增的。还有一种复杂数据类型叫 Object(对象)Object 是一种无序名值对的集合。因为在 ECMAScript 中不能定义自己的数据类型,所有值都可 以及上述 7 种数据类型之一来表示。只有 7 种数据类型似乎不足以表示全部数据。但 ECMAScript 的数 据类型很灵活,一种数据类型可以当作多种数据类型来使用。

** Undefined和null的区别? Null 类型 同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null,会返回object的原因:

let handles = null;
console.log( typeof car); // object
undefined 值是由 null 值派生而来的,因此 ECMA-262 将它们定义为表面上相等

undefined 类型 Undefined 类型只有一个值,就是特殊值 undefined。当使用 var 或 let 声明了变量但没有初始 化时,就相当于给变量赋予了 undefined 值:

let message; console.log(message == undefined); // true

Boolean 类型 Boolean(布尔值)类型是 ECMAScript 中使用最频繁的类型之一,有两个字面值:true 和 false。 这两个布尔值不同于数值,因此true不等于1,false不等于0。下面是给变量赋值布尔值的例子

let found = true
let lost = true

注意,布尔值字面量 true 和 false 是区分大小写的,因此 True 和 False(及其他大小混写形式) 是有效的标识符,但不是布尔值。

Number类型 ECMAScript 中最有意思的数据类型或许就是 Number 了。Number 类型使用 IEEE 754 格式表示整 数和浮点值(在某些语言中也叫双精度值)。不同的数值类型相应地也有不同的数值字面量格式最基本的数值字面量格式是十进制整数,直接写出来即可:

let  intNum = 55;

String 类型

String(字符串)数据类型表示零或多个 16 位 Unicode 字符序列。字符串可以使用双引号(")、 单引号(')或反引号(`)标示,因此下面的代码都是合法的:

 let firstName = "John";
 let lastName = "jacob";
 let lastName = `ingleheimerschmidt`

模板字面量 ECMAScript 6 新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量 保留换行字符,可以跨行定义字符串:

 let myMultiLineString = 'first line\nsecond line';
 let myMultiLineTempLateLiteral = `first line second line`

Symbol 类型

symbol(符号)是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。 尽管听起来跟私有属性有点类似,但符号并不是为了提供私有属性的行为才增加的(尤其是因为 Object API 提供了方法,可以更方便地发现符号属性)。 相反,符号就是用来创建唯一记号,进而用作非 字符串形式的对象属性。

符号的基本用法

符号需要使用 Symbol()函数初始化。因为符号本身是原始类型,所以 typeof 操作符对符号返回 symbol。

let sym = Symbol();

调用 Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),将来可以通 过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关:

let genericSymbol = Symbol(); 
let otherGenericSymbol = Symbol(); 
let fooSymbol = Symbol('foo'); 
let otherFooSymbol = Symbol('foo'); console.log(genericSymbol == otherGenericSymbol); //false

七.变量的使用需要注意哪些?

变量

ECMAScript 变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一 个用于保存任意值的命名占位符。有 3 个关键字可以声明变量:var、const 和 let。其中,var 在 ECMAScript 的所有版本中都可以使用,而 const 和 let 只能在 ECMAScript 6 及更晚的版本中使用。

var 声明提升

使用 var 时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域 顶部:

 function foo(){
  console.log(age);
  var age = 26;
 }
 foo(); // undefined
 function foo(){
  var age;
  console.log(age);
  age= 26;
 }
 foo(); // undefined

这就是所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次 使用 var 声明同一个变量也没有问题:

 function foo(){
  var age = 16;
  var age = 16;
  var age = 16;
  console.log(age)
 }
 foo(); // 36

let 声明

let 跟 var 的作用差不多,但有着非常重要的区别。最明显的区别是,let 声明的范围是块作用域, 而 var 声明的范围是函数作用域。

if(true){
  var name = 'Mait';
  console.log(name); // Mait
}
console.log(name); // Matt
if(true){
  let age = 26;
  console.log(age); // 26
}
console.log(age); // ReferenceError: age 没有定义

let 也不允许同一个块作用域中出现冗余声明。这样会导致报错:

var name;
var name; 
let age; 
let age; // SyntaxError;标识符 age 已经声明过了

暂时性死区:

let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升。

 // name 会被提升
 console.log(name); // undefined
 var name = "Matt";
 // age 不会被提升
 console.log(age); // ReferenceError:age 没有定义
 let age = 26;

全局声明: 与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var 声 明的变量则会)。

 var name = "Mait";
 console.log(window.name); // 'Matt';
 let age= 26;
 console.log(window.age); // undefined

不过,let 声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了 避免 SyntaxError,必须确保页面不会重复声明同一个变量。

条件声明 在使用 var 声明变量时,由于声明会被提升,JavaScript 引擎会自动将多余的声明在作用域顶部合 并为一个声明。因为 let 的作用域是块,所以不可能检查前面是否已经使用 let 声明过同名变量,同 时也就不可能在没有声明的情况下声明它。

   <script>
       var name = "Mine me";
       let age=  37;
       // 当前已经声明过
       var name = "Mine me- to"; // 不会检查,会作为一个变量提升来声明
       let age = 36;
       // 如果之前声明age,这里会报错,已经重复声明了
   </script>

为此,对于 let 这个新的 ES6 声明关键字,不能依赖条件声明模式。

注意 不能使用 let 进行条件式声明是件好事,因为条件声明是一种反模式,它让程序变 得更难理解。如果你发现自己在使用这个模式,那一定有更好的替代方式。

for 循环中的 let 声明

在 let 出现之前,for 循环定义的迭代变量会渗透到循环体外部:

 for (let i = 0; i<5; ++i){
    // 循环逻辑
 }
 console.log(i) // i 没有定义

在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改:

 for(var i=0; i<5; ++i){
    setTimeout(() =>{
     console.log(i),0)
    })
 }
 // 

const的使用

常量是块级范围的,非常类似用 [let]语句定义的变量。但常量的值是无法(通过重新赋值)改变的,也不能被重新声明。

 const number = 42;
 console.log(number) // 42
const name1 = value1 [, name2 = value2 [, ... [, nameN = valueN]]];
  • nameN

    常量名称,可以是任意合法的[标识符]

    valueN

  • 常量值,可以是任意合法的[表达式]

此声明创建一个常量,其作用域可以是全局或本地声明的块。与([var])变量不同,全局常量不会变为 window 对象的属性。需要一个常数的初始化器;也就是说,您必须在声明的同一语句中指定它的值(这是有道理的,因为以后不能更改)

const 声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。例如,在引用内容是对象的情况下,这意味着可以改变对象的内容(例如,其参数)。

关于“[暂存死区]”的所有讨论都适用于[let]和const。 一个常量不能和它所在作用域内的其他变量或函数拥有相同的名称

const 基本用法

常量在声明的时候可以使用大小写,但通常情况下全部用大写字母。

// 定义常量 MY_FAV 并赋值 7
const MY_FAV = 7;

// 报错 - 未捕捉到分配的常量 常量不可重新赋值
MY_FAV = 20;

// MY_FAV is 7
console.log('my favorite number is: ' + MY_FAV);

// 尝试重新声明会报错
// 语法错误:已声明标识符“MY_FAV”
const MY_FAV = 20;

// MY_FAV 保留给上面的常量,这个操作会失败
var MY_FAV = 20;

// 也会报错
let MY_FAV = 20;

块作用域

const 也会涉及到块级作用域

const MY_FAV = 7;

if (MY_FAV === 7) {

// 没问题,并且创建了一个块作用域变量 MY_FAV
// (works equally well with let to declare a block scoped non const variable)

let MY_FAV = 20;

// MY_FAV 现在为 20

console.log("my favorite number is4564564564 " + MY_FAV);

// 这被提升到全局上下文并引发错误

// var MY_FAV = 20;

}

// MY_FAV 依旧为 7

console.log("my favorite number is " + MY_FAV);

常量要求一个初始值

// 报错
//  必须有一个初始化值
declaration
const FOO;

常量可以定义成对象和数组

const MY_OBJECT = {'key': 'value'};

// 重写对象和上面一样会失败
MY_OBJECT = {'OTHER_KEY': 'value'};

// 对象属性并不在保护的范围内
// 下面这个声明会成功执行
MY_OBJECT.key = 'otherValue';

// 也可以用来定义数组
const MY_ARRAY = [];
// 可以向数组填充数据
MY_ARRAY.push('A'); // ["A"]
// 但是,将一个新数组赋给变量会引发错误
MY_ARRAY = ['B'];

八.运算符在哪些场景里使用?

有哪些运算符??

1.数值分割符

“引入了数值分割符 _,在数值组之间提供分隔,使一个长数值读起来更容易。Chrome 已经提供了对数值分割符的支持,可以在浏览器里试起来。”

 let num = 1000_000_00000
 console.log(num,"")

此外,十进制的小数部分也可以使用数值分割符,二进制、十六进制里也可以使用数值分割符。

2.逗号运算符

什么,逗号也可以是运算符吗?是的,曾经看到这样一个简单的函数,将数组的第一项和第二项调换,并返回两项之和

 function handleFun(arr){
   return [arr[0],arr[1]]= [arr[1],arr[0]],arr[0] + [arr]+1
 }
 const ArrayNum = [1,2];
handleFun(ArrayNum)
 // 3

逗号操作符对它的每个操作数求值(从左到右),并返回最后一个操作数的值。

let expr1 = "我的猫咪";

let expr2 = "撒汤小子";

let expr3 = "不是一只猫是一个包子";

let y = (expr1,expr2,expr3)

console.log(y,"y")

3.零合并操作符 ??

零合并操作符 ?? 是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回右侧操作数,否则返回左侧操作数。

let expr1 = "我的猫咪";

let expr2 = "撒汤小子";

let expr3 = "不是一只猫是一个包子";

let Y = expr3  ?? expr2
console.log(Y) // 不是一只猫是一个包子

另外在赋值的时候,可以运用赋值运算符的简写 ??=

let b = "你好";

let A = 0;

let c = null;

let d = "123";
//当左侧的值null、undefined才会将右侧变量的值赋值给左侧变量.其他所有值都不会进行赋值
b ??= A;

c ??= d;

console.log(A, d, c); // 0 "123" "123"

let q = { b: null, c: 10 };  

q.b ??= 30;

// b = 30 左侧等于null;

console.log(q); // 30

q.c ??= 40;

console.log(q); // c的值没有更改

可选链操作符 ?.

允许读取位于连接对象链深处的属性的值,而不必验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为 null 或者 undefined 的情况下不会引起错误,该表达式短路返回值是 undefined。

可选链操作符 ?. 允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。
此方法不兼容IE浏览器

// 判断是否存在
  obj && prop
  
  obj ?.prop
  
  arr && [index]
  
  arr?.[index] // 数组元素是否存在
  
  等同于 func && ...args
  
  func?.(...args) // 函数或对象方法是否存在
  

二、基本用法

  1. 查找一个深度嵌套的可能不存在的子属性时
 let nestedProp = obj.first && obj.first.secord
 // 等同于
 let nestedProp = obj.first?.second

通过使用?.操作符取代 . 操作符,JavaScript 会在尝试访问obj.first.second 之前,先隐式地检查并确定 obj.first 既不是 null 也不是 undefined。如果obj.first 是 null 或者 undefined,表达式将会短路计算直接返回 undefined

  1. 调用一个可能不存在的方法 函数调用时如果被调用的方法不存在,使用可选链可以使表达式自动返回undefined而不是抛出一个异常。
let result = someInterFace.customMethod?.();

备注:如果存在一个属性名且不是函数, 使用 ?. 仍然会产生一个 TypeError 异常 (x.y is not a function). 备注:如果 someInterface 自身是 null 或者 undefined ,异常 TypeError 仍会被抛出 someInterface is null 如果你希望允许 someInterface 也为 null 或者 undefined ,那么你需要像这样写 someInterface?.customMethod?.()

处理可选的回调函数或者事件处理器

function doSomething(onContent, onError) {

try {

} catch (err) {

if (onError) {

onError(err.message);

   }

 }

}
// 使用可选链进行函数调用
function doSomething(onContent, onError) {
  try {
   // ... do something with the data
  }
  catch (err) {
    onError?.(err.message); // 如果onError是undefined也不会有异常
  }
}

可选链不能用于赋值

let object = {}; object?.property = 1; 
// Uncaught SyntaxError: Invalid left-hand side in assignment

可选链和表达式

当使用方括号与属性名的形式来访问属性时,你也可以使用可选链操作符

let nestedProp = obj?.['prop' + 'Name'];

当在表达式中使用可选链时,如果左操作数是 null 或 undefined,表达式将不会被计算,例如:

let potentiallyNullObj = null;

let x = 0;

let prop = potentiallyNullObj?.[x++];

console.log(x); // x 将不会被递增,依旧输出 0

可选链访问数组元素

let arrayItem = arr?.[42];

使用空值合并操作符设置默认值

空值合并操作符可以在使用可选链时设置一个默认值:

let customer = {

name: "Car1",

details: { age: 82 }

};

// 如果当前值为null,undefined

// 将右侧赋值给左侧

let customerCity = customer?.city; // undefined

let cunsrter = customer?.city ?? "张国伟";

console.log(customerCity, "customerCity");

console.log(cunsrter, "cunsrter");

介绍一下常用的运算符

九.为啥叫条件语句?

十.数组是什么?

十一.循环是什么?

十二.循环解决了哪些问题?

十三.何为对象?

十四.函数是什么?

十五.函数是如何调用和传参的?

十六.什么是闭包有什么副作用?

十七.日期有哪些方法?

十八.日期方法的使用场景?

十九.如何封装日期方法应用到实际项目中

二十.封装最常用的JS方法解决方案