重学前端之JS系列(模块一)


前言

  • 编程语言的一般规律:用一定的词法和语法,表达一定语义,从而操作运行时

1.文法

2.语义


3.运行时

程序 = 数据结构+算法。“算法“ -> 逻辑,“数据结构“ -> 存储。

3.1 数据结构

定义:计算机存储、组织数据的方式。

3.1.1 7种基本类型(值类型+引用类型 或者 原始类型+对象类型)
  • 原始类型(值类型):Undefined;Null;Boolean;String;Number;Symbol
  • 对象类型(引用类型):Object
3.1.1.1 Undefined、Null

​ JavaScript的最初版本是这样区分的:null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN。

​ Undefined 类型表示”缺少值”,就是此处应该有一个值,但是还没有定义,它的类型只有一个值,就是 undefined。 JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,这是JavaScript 语言公认的设计失误之一。为了避免无意中被篡改,建议使用void 0 来获取 undefined 值。void 运算符 对给定的表达式进行求值,然后返回 undefined。

​ 典型用法:

(1) 调用函数时,应该提供的参数没有提供,该参数等于undefined。

(2)函数没有返回值时,默认返回undefined。

(3)变量被声明了,但没有赋值时,就等于undefined。

(4)对象没有赋值的属性,该属性的值为undefined。

补充:undefined在全局环境没法被赋值,在局部环境是可以被赋值的。

​ 验证如下,左图表示在全局环境,右图表示在局部环境:


<img src=”https://i.loli.net/2019/04/28/5cc56b483c4db.png"" width=”350” height=”80”/>

​ Null 类型表示”没有对象”,即该处不应该有值”,也只有一个值,就是 null。它的语义表示空值,与 undefined 不同,null 是JavaScript 关键字,所以在任何代码中,你都可以放心用 null 关键字来获取 null 值。

​ 典型用法:

(1) 作为函数的参数,表示该函数的参数不是对象。

(2) 作为对象原型链的终点。

3.1.1.2 Number
  • JavaScript 中的 Number 类型基本符合 IEEE 754-2008 规定的双精度浮点数规则。即第一位用来表示符号,接下来的11位用来表示指数,其他的位数用来表示有效位。

正确比较浮点数的方法:

1.Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON

2.parseFloat((0.1+0.2).toFixed(10)) === 0.3

补充知识:ES6 在Number对象上面,新增一个极小的常量Number.EPSILONNumber.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。

3.1.1.3 Object
  • 为什么给对象添加的方法能用在基本类型(值类型或者原始类型)上?

    ​ 原因是装箱和拆箱操作。JavaScript 中的几个基本类型,都在对象类型中有一个“亲戚”。它们是:Number;String;Boolean;Symbol。装箱:将值类型转化为对应的引用类型的操作。 拆箱:将引用类型转换为对应的值类型的操作

    举栗子~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //装箱
    var s1 = "abc";
    var s2 = s1.indexOf("a")

    /*实现机制如下
    (1)创建String类型的一个实例;
    (2)在实例上调用指定的方法;
    (3)销毁这个实例;
    */
    var s1 = new String("some text");
    var s2 = s1.substring(2);
    s1 = null;

    //拆箱
    var objNum = new Number(123);
    var objStr =new String("123");
    console.log( typeof objNum ); //object
    console.log( typeof objStr ); //object
    console.log( typeof objNum.valueOf() ); //number
    console.log( typeof objStr.valueOf() ); //string

    console.log( typeof objNum.toString() ); // string
    console.log( typeof objStr.toString() ); // string

补充知识:

1.每一类装箱对象皆有私有的 Class 属性,这些属性可以用 Object.prototype.toString 获取。在JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此 Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。

2.在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即拆箱转换)。拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。在 ES6 之后,还允许对象通过显式指定 Symbol.toPrimitive 来覆盖原有的行为。

​ 举栗子~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var symbolObject = Object(Symbol("a"));
console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]
var numberObject = Object(1);
console.log(Object.prototype.toString.call(numberObject)); //[object Number]

var o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
o * 2
//valueOf
//toString
//TypeError

var o = {
valueOf : () => {console.log("valueOf"); return {}},
toString : () => {console.log("toString"); return {}}
}
o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
console.log(o + "")
// toPrimitive
// hello
  • js中string和number类型互转换技巧(不用原生的 Number 和 parseInt)

    1
    2
    3
    4
    5
    6
    7
    8
    function numSwitchStr(param){
    let type=typeof param;
    if(type==='string'){
    return typeof (param * 1);
    }else if(type==='number'){
    return typeof (param + '1');
    }
    }
  • JavaScript:面向对象还是基于对象?

    对象的特点:具有唯一标识性、有状态、有行为。

    唯一标识性:通过内存地址来体现的。任何不同的 JavaScript 对象其实是互不相等的,栈内存不同,堆内存相同。

    状态和行为:C++ 中称它们为“成员变量”和“成员函数”,Java 中则称它们为“属性”和“方法”,在 JavaScript 中,将状态和行为统一抽象为“属性”。考虑到 JavaScript 中将函数设计成一种特殊对象,所以 JavaScript 中的行为和状态都能用属性来抽象。

    ​ 在实现了对象基本特征的基础上, JavaScript 中对象独有的特色是:对象具有高度的动态性,这是因为 JavaScript 赋予了使用者在运行时为对象添改状态和行为的能力

    ​ 为了提高抽象能力,JavaScript 的属性被设计成比别的语言更加复杂的形式,它提供了数据属性和访问器属性(getter/setter)两类。

    数据属性具有四个特征:value:就是属性的值。writable:决定属性能否被赋值。enumerable:决定 for in 能否枚举该属性。configurable:决定该属性能否被删除或者改变特征值。 [Object.defineProperty 来定义属性 , Object.getOwnPropertyDescriptor 来查看属性 ]

    访问器属性也具有四个特性:getter:函数或 undefined,在取属性值时被调用。setter:函数或 undefined,在设置属性值时被调用。enumerable:决定 for in 能否枚举该属性。configurable:决定该属性能否被删除或者改变特征值。

    ​ 实际上 JavaScript 对象的运行时是一个“属性的集合”,属性以字符串或者 Symbol 为 key,以数据属性特征值 或者访问器属性特征值为 value。

    ​ JavaScript 语言标准也已经明确说明,JavaScript 是一门面向对象的语言,我想标准中能这样说,正是因为 JavaScript 的高度动态性的对象系统。(多态:同一接口的多种不同的实现方式。有继承才有多态。)

  • JavaScript:我们真的需要模拟类吗?(模拟基于类的面向对象)

    前置知识:所有对象都有私有字段 [[prototype]],就是对象的原型;读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。

    补充知识:

    1.ES6 以来,JavaScript 提供了一系列内置函数,以便更为直接地访问操纵原型。三个方法分别为:Object.create 根据指定的原型创建新对象,原型可以是 null;Object.getPrototypeOf 获得一个对象的原型;Object.setPrototypeOf 设置一个对象的原型。

    2.在早期版本的 JavaScript 中,“类”的定义是一个私有属性 [[class]],语言标准为内置类型诸如Number、String、Date 等指定了 [[class]] 属性,以表示它们的类。语言使用者唯一可以访问 [[class]] 属性的方式是 Object.prototype.toString。

    3.在 ES5 开始,[[class]] 私有属性被 Symbol.toStringTag 代替,Object.prototype.toString 的意义从命名上不再跟 class 相关。

    栗子~

    1
    2
    3
    4
    5
    6
    7
    8
    var o = {}
    console.log(o + "");//[object Object]

    //Symbol.toStringTag 修改[[class]]属性
    var o = { [Symbol.toStringTag]: "MyObject" }
    console.log(o + "");//[object MyObject]

    /*代码解释:o + ''会触发类型转换,Object转String需要进行拆箱转换,调用o.toString()得到"[object Object]" */

    ​ ES6 中加入了新特性 class,new 跟 function 搭配的怪异行为终于可以退休了(虽然运行时没有改变),在任何场景,我都推荐使用 ES6 的语法来定义类,而令 function回归原本的函数语义。

    ​ ES6 中引入了 class 关键字,并且在标准中删除了所有 [[class]] 相关的私有属性描述,类的概念正式从属性升级成语言的基础设施,从此基于类的编程方式成为了 JavaScript 的官方编程范式,因此不再需要模拟类了

  • JavaScript对象:你知道全部的对象分类吗?

    • 宿主对象(host Objects):由 JavaScript 宿主环境提供的对象,它们的行为完全由宿主环境决定。

      • 所有的 BOM 和 DOM 对象都是宿主对象。
      • BOM 对象 : window、location、navigator、screen、history…
      • DOM 对象:Document、Body、Event、Form、Image、事件对象 event…
    • 内置对象(Built-in Objects):由 JavaScript 语言提供的对象。

      • 固有对象(Intrinsic Objects ):由标准规定,随着 JavaScript 运行时创建而自动创
        建的对象实例。eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent
        encodeURI encodeURIComponent Math JSON Reflect

      • 原生对象(Native Objects):可以由用户通过 Array、RegExp 等内置构造器或者特殊语法创建

        的对象。

      • 普通对象(Ordinary Objects):由{}语法、Object 构造器或者 class 关键字定义类创建的对象,它能够被原型继承。

3.1.2 对象的分类

3.2 算法(执行过程)

定义:一系列解决问题的明确指令。换言之,对于符合一定规范的输入,能够在有限时间内获得要求的输出

前置知识:

JavaScript运行环境一般都由宿主环境和执行期环境共同构成。其中宿主环境是由外壳程序生成的,如Web浏览器就是一个外壳程序,它提供了 一个可控制浏览器窗口的宿主环境。执行期环境则由嵌入到外壳程序中的JavaScript引擎(或称为JavaScript解释器)生成。

宏观任务:宿主发起的任务。微观任务: JavaScript 引擎发起的任务。

当拿到一段 JavaScript 代码时,浏览器或者 Node 环境首先要做的就是:传递给 JavaScript 引擎,并且要求它去执行。

3.2.1 事件循环
3.2.2 函数的执行
3.2.3 语句级的执行