5.1 语言基础

~~~~~~~~ 数据类型 ~~~~~~~

function sayHi() {
  console.log(name);
  console.log(age);
  var name = 'Lydia';
  let age = 21;
}
sayHi()

以上代码的输出是什么?

var, let

输出:

  • undefined

  • ReferenceError: Cannot access 'age' before initialization

解析:

  • var 声明的变量会被预解析,var name 会被提升到作用域的最顶部

    • 所以在第 2 行 console.log(name) 时,name 是存在的,但是没有被赋值

    • 所以第 2 行是输出 undefined

  • 而 let 会有暂时性死区

    • 所以在 let 声明变量之前,都无法使用该变量

var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
} })();

以上代码的输出是什么?

var

输出:'Goodbye Jack'

解析:用 var 定义的变量“都”会被提升至所有代码的最前面。所以以上代码,实际上类似:

TODO:where 会提升到 where

以上代码的输出是什么?

var

输出:

  • 1

  • ReferenceError: x is not defined

解析:在作用域内,var 变量定义和函数定义会被先行提升。

所以,上面的代码,实际上是:

考察点:var 变量提升、隐式定义全局变量

以上代码的输出是什么?

var

输出:22

定义时绑定

以上代码的输出是什么?

var

输出:5 个 5

每次 for 循环的时候 setTimeout 都会执行,但是里面的 function 不会被立即执行,而是会放进任务队列。

写一个函数,第 n 秒打印 n
暂时性死区

块级作用域

在代码块内,使用 let, const 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”。

symbol

Symbol 是 ES6 的新增属性,代表用给定名称作为唯一标识,这种类型的值可以这样创建,let id=symbol('id')

symbl 确保唯一,即使采用相同的名称,也会产生不同的值,我们创建一个字段,仅为知道对应 symbol 的人能访问。

symbol 并不是 100% 隐藏:

  • 有内置方法 Object.getOwnPropertySymbols(obj) 可以获得所有的 symbol。

  • 也有一个方法 Reflect.ownKeys(obj) 返回对象所有的键,包括 symbol

但大多数内置方法和语法结构,都遵循通用约定——它们是隐藏的。

以上代码的输出是什么?

Number 精度

输出:111111111111111110000(和 a 的值相等)

解析:

对于整数部分,有效数字最多能存 16 位。

关于浮点数精度的详情可查阅:https://anjia1.gitbook.io/cs/floating-point/reading

以上代码的运行结果是什么?

对象字面量

结果:",,"

解析:JavaScript 在使用字面量创建数组时,如果末尾有个 , 会被省略。

所以 [,,,] 实际上只有三个元素 [,,],值都为空即 undefined。而当三个元素使用 join(,) 时,会有两个分隔符 ,

以上代码的输出是什么?

函数参数 arguments

输出:21

解析:函数内部的 arguments 是个引用,且和变量之间存在着值联动。

  • arguments 是个类数组,有 length 属性的 Object

  • arguments 对象的内部属性是用形参创建的 getter 和 setter 方法,它们任意一个的改变都会影响到另一方

arguments

arguments 是类数组对象。

它有 length 属性,但不能调用数组方法,可用 Array.from() 转换

箭头函数获取 arguments

可用 ...rest 参数获取

判断变量类型的方法
  1. 运算符 typeof

  2. 运算符 instanceof

  3. 属性 constructor

  4. 方法 toString()

类型转换规则

x == y 的结果是 true 或者 false

  1. 若 Type(x) 和 Type(y) 相同,则

    1. Type 是 Undefined,返回 true

    2. Type 是 Null,返回 true

    3. Type 是 Boolean,同为 true/false 返回 true,否则返回 false

    4. Type 是 String,当是完全相同的字符序列时返回 true,否则返回 false

    5. Type 是 Number,则

      • 若有一个为 NaN,则返回 false

      • 若是 +0 和 -0,则返回 true

      • 若是相等数值,返回 true

      • 否则返回 false

    6. Type 是对象,若引用的是同一个对象则返回 true,否则返回 false

  2. 一个是 null,一个是 undefined

    • 若 x 是 null,y 是 undefined,则返回 true

    • 若 x 是 undefined,y 是 null,则返回 true

  3. 一个是 String,一个是 Number

    • 若 Type(x) 是 Number,Type(y) 是 String,则返回 x == ToNumber(y) 的结果

    • 若 Type(x) 是 String,Type(y) 是 Number,则返回 ToNumber(x) == y 的结果

  4. 其中一个是 Boolean

    • 若 Type(x) 是 Boolean,返回 ToNumber(x) == y 的结果

    • 若 Type(y) 是 Boolean,返回 x == ToNumber(y) 的结果

  5. 一个是 String 或 Number,一个是 Object

    • 若 Type(x) 是 String 或 Number,Type(y) 是 Object,返回 x == ToPrimitive(y)

    • 若 Type(x) 是 Object,Type(y) 是 String 或 Number,返回 ToPrimitive(x) == y

  6. 返回 false

相关内容

  • ToPrimitive()

    • 若是转成 string,则先调 toString()

    • 若是转成 number,则先调 valueOf()

  • ToNumber()

数据类型

在 JavaScript 中,基本数据类型 = 原始数据类型(值类型) + 对象(引用类型)

  1. 值类型:按值访问,可操作保存在变量中的实际值

  2. 引用类型

    • Object, Array, RegExp, Date, Function

    • 值类型的包装器:String, Number, Boolean

    • 内置对象:Global, Math

~~~~~~~~ 类型转换 ~~~~~~~

其它类型转换为 Number 的规则
  1. 原始类型

    1. Undefined, NaN

    2. Null, 0

    3. Boolean, true 1, false 0

    4. String, '' 0, 非空非数值 NaN, 非空数值 Number

    5. Number

    6. BigInt

    7. Symbol, 不能转, 会报错

  2. Object

    • 先转换成相应的原始类型值

      • ToPrimitive 会先调用 valueOf() 再调用 toString()

    • 再按原始类型的规则转

以上代码的输出是什么?

boolean 和 ==

输出:false

解析:

  • 第 2 行,转 boolean 类型:数组 [0] 不为空,所以值是 true

  • 第 3 行,运算符 ==:如果一个操作数是 boolean,则另一个操作值在比较之前先转换为 Number,而 Number([0]) 的值是 0,于是最终变成了 0 == true

以上代码的输出是什么?

+!

输出:1 false

解析:类型转换

  • 第 1 行,把 true 转成 Number

  • 第 2 行,把 String 转成 Boolean

以上代码的输出是什么?

数组比大小

输出:false, false, false, true

解析:在 JavaScript 中

  • Array 本质上是 Object(引用类型)

    • 引用类型比相等,比的是地址,所以,===== 都不相等

  • 而 Array 的 >< 运算符,方式却类似于字符串,比较字典顺序

    • 所以,a < c 是 true

~~~~~~~~~ 运算符 ~~~~~~~

Object.is()==, === 的区别
  • == 会进行隐式类型转换

  • === 不进行类型转换,是严格相等

  • Object.is() 是在 === 的基础上特别处理了 NaN, -0, +0

最重要的是,Object.is() 是有其特殊用途的,而不应该理解为是更严格的相等。

以上代码的输出是什么?

+? 的优先级

输出:"Something"

解析:+ 的优先级要高于 ?

以上代码的输出是什么?

?.

输出:Mara undefined Lydia/Hallie undefined

解析:?. 可选操作链,ES2020 新特性。 当 ? 前面的属性不是 undefined 或 null 时才会继续执行后续代码,否则直接返回左侧结果。

~~~~~~~~~ 语句 ~~~~~~~~

以上代码是 IIFE 吗?为什么?

不是 IIFE。而且代码运行时会报错:Uncaught SyntaxError: Unexpected token ')'

解析:以 function 关键字开头的语句,会被解析为函数声明,而函数声明是不允许直接运行的。只有当解析器把这句话解析为函数表达式时,才能直接运行。比如改成以运算符开头:

任何消除函数声明和函数表达式之间歧义的方法,都可以被解析器正确识别。

如果不在乎返回值,添加一元运算符都是有效的:

基础概念:语句、表达式、表达式语句

详情可查阅:https://swordair.com/function-and-exclamation-mark/

以上代码的输出是什么?

for-in

输出:name, age

解析:for-in 遍历的是对象自身的属性和方法,let key in obj

以上代码的输出是什么?

for-infor-of

输出:

  • 0 1 2 3

  • 吃 喝 玩 乐

解析:

  • for-inlet key in obj

  • for-oflet item in obj

~~~~~~~~~ 方法 ~~~~~~~~

以上代码的输出是什么?

Array.flat()

输出:[1, 2, 3, 4, [5]]

扁平化,会创建一个新的、被扁平化的数组。扁平化的深度,取决于参数。

Array.prototype.flat() 会创建一个 new array,用所有的 sub-array elements 递归地 concat 到一起。depth 由参数指定,默认是 1。

以上代码的运行结果是什么?

Array.map()

结果:['1', empty × 2]

解析:Array.prototype.map() 只有在数组中被初始化过的元素,才会被触发

以上代码的输出是什么?

Array.reduce()

输出:

  • x=1 y=2

  • x=undefined y=3

  • x=undefined y=4

解析:最容易理解 reduce() 情况,是求返回数组中所有元素的总和。如下:

而当回调函数中没有返回值时,则第一个参数为 undefined

以上代码的输出是什么?

isNaN()Number.isNaN()

输出:false, false, true, false

解析:

  • isNaN() 会先将传入的参数转换为 Number 类型

  • Number.isNaN() 是先判断值的类型是不是 Number,若不是则直接返回 false

    • 即只有当值是 NaN 时才会返回 true

常用的 string 方法

去除字符串首尾空格

使用正则 (^\s*)|(\s*$)

常用的 Array 方法

实现深度拷贝
数组去重
  1. ES6 Set 去重 Array.from(new Set(array))

  2. indexOf 循环去重

  3. Object 键值对去重:把数组的值存成 Object 的 key 值,比如 Object[value1] = true

获得对象上的属性

从 ES5 开始,有三种方法可以列出对象的属性

  1. for(let i in obj) 访问对象及其原型链中所有可枚举的类型

  2. object.keys 返回一个数组,包括所有可枚举的属性名称

  3. object.getOwnPropertyNames 返回一个数组包含不可枚举的属性

~~~~~~~~ 原型和类 ~~~~~~~~

  • 原型链的顶端是什么?

  • Object 的原型是什么?

  • Object 的原型的原型是什么?

  • 在数组的原型链上,实现删除数组重复数据的方法

原型链

new 操作符做了哪些事情

new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数后返回这个对象。

new 操作符原理

  1. 创建一个类的实例:创建一个空对象 obj,然后把这个空对象的 proto 设置为构造函数的 prototype

  2. 初始化实例:构造函数被传入参数并调用,关键字 this 被设定指向该实例 obj

  3. 返回实例 obj

以上代码的输出是什么?

Function 和 Object
  • undefined, 2

  • 1, 2

Function._proto_(getPrototypeOf) 是什么?

获取一个对象的原型:

  • 在 chrome 中,可以用 _proto_

  • 在 ES6 中,可以用 Object.getPrototypeOf

Function.proto 是指 Function 继承自什么对象。

Function 的原型也是 Function

  1. Hero("37er") 执行结果为 Hi! This is 37er

  2. Hero("37er").kill(1).recover(30) 执行结果为 Hi! This is 37er Kill 1 bug Recover 30 bloods

  3. Hero("37er").sleep(10).kill(2) 执行结果为 Hi! This is 37er 等待 10s 后 Kill 2 bugs

编写代码,满足以上条件
类的创建和继承
  1. 基于原型链的 new function + prototype

  2. ...

P59

继承的实现方式
  1. 原型链继承,将父类的实例作为子类的原型,他的特点是实例是子类的实例也是父 类的实例,父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实 现,缺点是来自原型对象的所有属性被所有实例共享,无法实现多继承,无法向父类构 造函数传参。

  2. 构造继承,使用父类的构造函数来增强子类实例,即复制父类的实例属性给子类, 构造继承可以向父类传递参数,可以实现多继承,通过 call 多个父类对象。但是构造继 承只能继承父类的实例属性和方法,不能继承原型属性和方法,无法实现函数服用,每 个子类都有父类实例函数的副本,影响性能

  3. 实例继承,为父类实例添加新特性,作为子类实例返回,实例继承的特点是不限制调用方法,不管是 new 子类还是子类返回的对象具有相同的效果,缺点是实例是父类的实例,不是子类的实例,不支持多继承

  4. 拷贝继承

    • 特点:支持多继承

    • 缺点:效率较低,内存占用高(因为要拷贝父类的属性),无法获取父类不可枚举的方法(不可枚举方法,不能使用 for in 访问到)

  5. 组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

  6. 寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

更多 https://www.cnblogs.com/humin/p/4556820.html

两个构造函数,实现 A 继承 B

P108

~~~~~~~~~ 运行时 ~~~~~~~~

作用域类型

以上代码的输出是什么?

this 和箭头函数

输出:obj1_name obj1_name obj2_name

解析:

  • 普通函数,this 指向看执行时的声明

  • 箭头函数,this 值继承父级函数的

    • 箭头函数不绑定自己的 this

bind,apply,call 的区别

改变函数内部 this 指针的指向函数。

通过 apply 和 call,改变函数的 this 指向:

  • 它们两个函数的第一个参数都是一样的,表示要改变指向的那个对象

  • 第二个参数,apply 是数组,而 call 则是 arg1,arg2...这种形式。

通过 bind 改变 this 作用域会返回一个新的函数,这个函数不会马上执行。

bind 除了返回的是函数之外,其参数和 call 一样。

this 的指向有哪几种?
  1. 默认绑定:全局环境中,this 默认绑定到 window(非严格模式时)

  2. 隐式绑定:一般地,被直接对象所包含的函数调用时,也称为方法调用,this 隐式绑定到该直接对象

  3. 隐式丢失:隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到 window

  4. 显式绑定:通过 call(), apply(), bind() 方法把对象绑定到 this 上

  5. new 绑定:如果函数或者方法调用之前带有关键字 new,它就构成构造函数调用。对于 this 绑定来说,称为 new 绑定

    • 构造函数通常不使用 return 关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值

    • 如果构造函数使用 return 语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果

    • 如果构造函数显式地使用 return 语句返回一个对象,那么调用表达式的值就是这个对象

闭包

闭包就是能够读取其它函数内部变量的函数,或者子函数在外调用, 子函数所在的父函数的作用域不会被释放。

TODO.

  • 闭包本身的含义?

  • 在 JS 里,闭包的形式通常是xxx,因为它符合闭包的定义。

  • 在其它语言里,闭包是否有不同的形式?

  • 闭包的使用场景

以上代码的输出是什么?

Event Loop 中的 Job queue
setTimeout() 的时间含义

setTimeout() 函数只是将事件插入了任务列表,必须等当前代码都执行完,主线程才会去执行它指定的回调函数,有可能要等很久,所以没办法保证回调函数一定会在 setTimeout 指定的时间内执行。

设置的那个时间 ≈ 插入队列的时间 + 等待的时间

如何实现 sleep()
  1. promise

  2. async

  3. generate

  4. while

如何解决异步回调地狱?
  • promise

  • async/await

  • generator

promise, generator, async 的使用,P95

promise

promise 及其底层如何实现, P89

async 和 await

generator

让三个 promise 串行执行

P108

实现一个简单的 promise

Promises/A+ https://promisesaplus.com/

实现一个完美符合Promise/A+规范的Promise https://github.com/forthealllight/blog/issues/4

垃圾回收机制

必要性:

  • 由于字符串、对象和数组没有固定大小,所以只有当它们的大小已知时,才能进行动态的存储分配。

  • JavaScript 程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。

不像 C/C++,JS 自己有一套垃圾回收机制(Garbage Collection)。JavaScript 的解释器可以检测到什么时候不再使用一个对象了,一旦确定了一个对象是无用的,它就可以把它所占用的内存释放掉了。

1. 标记清除

这是最常见的垃圾回收方式。

  • 当变量进入环境时,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占的内存,只要执行流程进入相应的环境就可能用到它们。

  • 当离开环境时,就标记为离开环境。

  • 垃圾回收器在运行的时候,会给存储在内存中的变量都加上标记(所有都加),然后去掉环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器完成了内存的清除工作,并回收它们所占用的内存。

2. 引用记数法

记录每个值被引用的次数。

  • 当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为 1

  • 相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次 数就减 1,

  • 当这个值的引用次数为 0 的时候,说明没有办法再访问这个值了,因此就把 所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为 0 的这些值。

用引用计数法会存在内存泄露。比如:

~~~~~~~~~ 其它 ~~~~~~~~~

前端模块化(CommonJS,AMD,ES Modules)

将复杂的文件拆成一个个独立的模块(比如单个 JS 文件),这样有利于重用(复用性)和维护(版本迭代)。一个模块是能实现特定功能的文件,有了模块就可以方便的使用别人的代码,想要什么功能就能加载什么模块。

与此同时,也会引来模块之间相互依赖的问题,所以有了 commonJS 规范,AMD,CMD 规范等等,以及用于 JS 打包(编译等处理)的 工具 webpack。

  1. CommonJS 是一种模块规范,最初被用在 Node.js,成为 Node.js 的模块规范

  2. 而浏览器端,在 ES6 之前,类似的模块规范有 AMD

  3. ES6 Module 是在语言标准的层面上实现了模块功能。有望成为浏览器和服务器通用的模块解决方案

CommonJS vs AMD

CommonJS 开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域。

  • 模块输出 modules.exports

  • 模块加载 require() 引入模块

AMD,异步模块定义。

requireJS 实现了 AMD 规范,主要用于解决下述两个问题:

  1. 多个文件有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器

  2. 加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应的时间越长

CommonJS vs ES Module

使用上的差别,主要有:

  1. 模块输出:值的拷贝 vs 值的引用

  2. 时机:运行时加载 vs 编译时输出接口

  3. 形式:单个值导出 vs 可导出多个

  4. 动态语法可以写在判断里 vs 静态语法只能写在顶层

  5. this 值:当前模块 vs undefined(默认是严格模式)

"use strict";

ES5 引入了严格模式,旨在让代码更合理、更安全。

它对 JavaScript 的使用添加了一些限制,比如:

  • 禁止使用 with 语句

  • 禁止 this 指向全局对象 (???)

  • 对象的属性名不能重复

此外,针对严格模式,编译器也会有特定的优化,所以代码的运行速度也会快一些。

严格模式,还可以很好地向后兼容,为未来的 JavaScript 做好铺垫。

eval

功能:将对应的字符串解析成 JS 并执行

应该避免使用,因为非常消耗性能(2 次,一次解析成 JS,一次执行)

ES6,ES7 的新语法
  1. 变量的声明和定义

    • 增加了 let、const 声明变量

    • 它们是块级作用域的(ES5 只有全局作用域和函数作用域)

    • 块级作用域的好处是不再需要立即执行的函数表达式,循环体中的闭包不再有问题

  2. 箭头函数

  3. 异步的 promise, generator, async/await

  4. 模块化,更方便地进行模块化编程

  5. class,可以更好的面向对象编程

其它:

  1. 数据类型

    • 新的原始类型 symbol

    • 新的数据结构 set 和 map

  2. 对已有类型的对象包装器,新增了一些方法。比如

    • 字符串:模板字符串

    • 函数:默认参数

    • 对象属性:?.

  3. 运算符

    • 结构赋值

    • rest

  • JS 的语言特性

  • C++, Java, JavaScript 这三种语言的区别

Last updated