基本类型:Number、String、Undefined、Null、Boolean、Symbol
内置对象:Array、Function、Object、Map、Set
这些内置对象其实是函数,即构造函数,由new产生函数调用。

  1. var strPrimitive = "I am a string";
  2. typeof strPrimitive; // "string"
  3. strPrimitive instanceof String; // false
  4. var strObject = new String( "I am a string" );
  5. typeof strObject; // "object"
  6. strObject instanceof String; // true
  7. // 检查 sub-type 对象
  8. Object.prototype.toString.call( strObject ); // [object String]

“I am a string”是一个字面量,是一个不可变的值,若要在字面量上执行一些操作,就必须将其转化为String对象,实际上JS在必要时会自动进行转换:

  1. 'I am boy'.length
  2. 222.toFixed(2)

原型、原型链

image.png
JS基础 - 图2
image.png
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。即 x instanceof y 用于判断x.__proto__ ===? y.prototype
核心是原型链的向上查找:

  1. function myInstanceof(left, right) {
  2. //基本数据类型直接返回false
  3. if(typeof left !== 'object' || left === null) return false;
  4. //getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
  5. let proto = Object.getPrototypeOf(left);
  6. while(true) {
  7. //查找到尽头,还没找到
  8. if(proto == null) return false;
  9. //找到相同的原型对象
  10. if(proto == right.prototype) return true;
  11. proto = Object.getPrototypeOf(proto);
  12. }
  13. }
  14. console.log(myInstanceof("111", String)); //false
  15. console.log(myInstanceof(new String("111"), String));//true

2021.10.22
补充一个例子方便理解,在React中的原型链查找(来自React如何区分Class和Function):

  1. class Greeting extends React.Component {
  2. render() {
  3. return <p>Hello</p>;
  4. }
  5. }
  6. let c = new Greeting();
  7. console.log(c.__proto__); // Greeting.prototype
  8. console.log(c.__proto__.__proto__); // React.Component.prototype
  9. console.log(c.__proto__.__proto__.__proto__); // Object.prototype
  10. c.render(); // 在 c.__proto__ (Greeting.prototype) 上找到
  11. c.setState(); // 在 c.__proto__.__proto__ (React.Component.prototype) 上找到
  12. c.toString(); // 在 c.__proto__.__proto__.__proto__ (Object.prototype) 上找到

typeof 运算符:

Undefined "undefined"
Null **"object"**
Boolean "boolean"
Number "number"
BigInt "bigint"
String "string"
Symbol
(ECMAScript 2015 新增)
"symbol"
宿主对象(由 JS 环境提供) 取决于具体实现
Function
对象 (按照 ECMA-262 规范实现 [[Call]])
"function"
其他任何对象 "object"

关于为何typeof null值为”object”:其实是javascript语言的bug,不同的对象在底层都表示为二进制。二进制前三位都为0时判定为object,而null二进制表示全为0,自然就返回object。—- 来自《你不知道的JS》

判断相等

  1. == 会进行类型转换
  2. === 不会进行类型转换,但是NaN不等于自身,以及+0等于-0
  3. Object.is() 完全相等

image.png(为什么可以查看翎羽的博客

词法作用域和动态作用域

JavaScript采用的是词法(静态)作用域,函数的作用域基于函数创建的位置。

执行上下文栈

什么是执行上下文
执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。
执行上下文的类型

  • 全局执行上下文:不在任何函数中的代码都位于全局执行上下文中。做了两件事:1. 创建了一个全局对象,在浏览器中是window对象;2. 将this指向该对象。一个程序中只存在一个全局执行上下文。
  • 函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。
  • Eval 函数执行上下文: 运行在 eval 函数中的代码也获得了自己的执行上下文。

每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

执行栈
执行栈,在其他编程语言中也称作调用栈。是一个后进先出的栈结构。用于存储代码执行期间所创建的执行上下文。
当 JavaScript 引擎首次读取你的脚本时,它会创建一个全局执行上下文并将其推入当前的执行栈。每当发生一个函数调用,引擎都会为该函数创建一个新的执行上下文并将其推到当前执行栈的顶端。
引擎会运行执行上下文在执行栈顶端的函数,当此函数运行完成后,其对应的执行上下文将会从执行栈中弹出,上下文控制权将移到当前执行栈的下一个执行上下文。
JS基础 - 图5

变量对象

变量对象
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
全局上下文下的变量对象:就是全局对象。
函数上下文下的变量对象:用活动对象(activation object, AO)来表示变量对象。活动对象的意思是只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,被激活了就叫做活动对象。
执行上下文的代码会分成两个阶段进行处理:分析和执行,我们也可以叫做:

  1. 进入执行上下文
  2. 代码执行

image.png
image.png
总结如下:

  • 函数上下文的变量对象初始化只包括 Arguments 对象
  • 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
  • 代码执行阶段,会再次修改变量对象的属性值
    1. // 初始化时
    2. AO = {
    3. argument:{ }
    4. }
    1. // 进入执行上下文,但未执行代码时
    2. AO = {
    3. argument:{
    4. 0:1,
    5. length:1
    6. },
    7. a: 1,
    8. b: undefined
    9. }
    1. // 进入函数执行上下文后
    2. AO = {
    3. argument:{
    4. 0:1,
    5. length:1
    6. },
    7. a: 1,
    8. b: 123
    9. }

    作用域链

    当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
    JS基础 - 图8

    关于this

    内容查看所整理的思维导图

    闭包

    变量提升

    来自:你不知道的JS

  1. // 例子1
  2. console.log(a)
  3. var a = 2
  4. // 例子2
  5. foo();
  6. function foo(){
  7. console.log(a);
  8. var a = 2;
  9. }
  10. //例子3
  11. //函数声明与函数表达式的区别
  12. foo();
  13. bar();
  14. var foo = function bar(){
  15. console.log('hello');
  16. }
  17. // 相当于
  18. var foo;
  19. foo();
  20. bar();
  21. foo = function bar(){...}
  22. //例子4
  23. foo();
  24. var foo; // 重复声明会被忽略
  25. function foo(){
  26. console.log(1);
  27. }
  28. foo = function(){
  29. console.log(2);
  30. }
  31. //例子5
  32. foo();
  33. function foo(){
  34. console.log(1);
  35. }
  36. var foo = function(){
  37. console.log(2);
  38. }
  39. function foo(){
  40. console.log(3);
  41. }

精度问题

注意浮点数的精度问题,计算机无法用IEEE754标准精确表示0.1
内容查看所整理的思维导图 - 组成原理 - 数据表示与运算。

类型转换

原始值转布尔
undefined、null、0、false、””、NaN都会转化位false
其它转化为true
内容查看所整理的思维导图**JS基础 - 图9

判断是否为数组

  1. //判断是否为数组:
  2. arr = [1,2,3,4]
  3. typeof arr // object 不可判断
  4. arr instanceof Array // true
  5. Object.prototype.toString.call(arr) // "[object Array]"
  6. Array.isArray(arr) // true
  7. Array.prototype.isPrototypeOf(arr) // true

arguments是一个类数组而不是数组

类数组 VS 数组

相同点:

  • 都可用下标访问每个元素
  • 都有length属性

不同点:

  • 数组对象的类型是Array,类数组的对象类型是Object
  • 类数组对象不能直接调用数组API
  • 数组的遍历可以使用for in 和for循环,类数组只能使用for循环

arguments

  • 实现重载(overload):比如我们要实现:一个参数时,做乘法运算;二个参数时,做加法运算;
  • 实现递归:arguments.callee:返回当前函数本身(严格模式无法使用)

类数组转数组

  1. Array.from(arguments)
  2. Array.prototype.slice.apply(``arguments``)
  3. 扩展运算符[...arguments]
  4. for...of遍历依次放入空数组中
  5. [].slice.call(arguments)

几个方法

  • Array.prototype.push.apply(a,b) ```javascript var a = [1,2,3]; var b = [4,5,6];

a.push.apply(a, b); //等价于 Array.prototype.push.apply(a, b);

console.log(a) //[1,2,3,4,5,6]

  1. - Array.prototype.slice.call(arguments)
  2. MDN解释slice方法可以用来将一个类数组(Array-like)对象/集合转换成一个新数组。等价于:
  3. ```javascript
  4. [...arguments]
  • Object.prototype.hasOwnProperty.call(object,prop)

等价于

  1. ({}).hasOwnProperty.call(object,prop)
  • Object.prototype.toString.call()

为了判断一个变量是什么类型的数据。

  1. var toString = Object.prototype.toString;
  2. toString.call(123); //"[object Number]"
  3. toString.call('abcdef'); //"[object String]"
  4. toString.call(true); //"[object Boolean]"
  5. toString.call([1, 2, 3, 4]); //"[object Array]"

遍历对象

  1. for…in

    1. for(key in obj){
    2. console.log(key + ': ' + obj[key])
    3. }
  2. Object.keys(obj):获取对象属性名的集合

  3. Object.values(obj)
  4. Object.entries(obj)

    遍历类数组for…of

    for…of语句在可迭代对象(包括 Array, Map, Set, String, TypedArray,arguments 对象等等)上创建一个迭代循环。

    异步编程

    异步
    异步的意思是,一个任务不是连续完成的,而是分为两个部分完成的。比如“读取文件”的操作:
    同步的情况:文件系统发送读取文件的请求,程序一直等待操作系统读取完毕,读取完毕后程序对读取的文件进行操作;
    异步的情况:文件系统发送读取文件的请求,程序继续往下执行其他代码,待主任务执行完毕以后,才会去执行“宏任务”和“微任务”(此时操作系统已经读取完毕),而程序对读取的文件进行操作的步骤就在“宏任务”、“微任务”之中。
    异步函数有:ajax,
    回调函数
    JS实现异步编程,就靠回调函数。异步请求完成以后,第二步就在回调函数中执行。
    回调函数本身没有问题,但回调函数容易产生嵌套的写法,即回调函数里又包含了另外的回调函数,也就被称为“回调地狱”。

    1. fs.readFile(fileA, 'utf-8', function (err, data) {
    2. fs.readFile(fileB, 'utf-8', function (err, data) {
    3. // ...回调地狱
    4. });
    5. });

    Promise对象的出现就是为了解决这个问题的。允许将回调函数的嵌套,改为链式调用,是一种新的写法。Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
    从 回调函数 到 Promise、Generator 再到 Async/Await。表面上只是写法的变化,但本质上则是语言层的一次次抽象。让我们可以用更简单的方式实现同样的功能,而不需要去考虑代码是如何执行的。
    Promise
    Promise.race
    Promise.all
    Async/await
    使用规则

  5. async/await使用规则一:凡是在前面添加了async的函数在执行后都会自动返回一个Promise对象

  6. async/await使用规则二:await必须在async函数里使用,不能单独使用
  7. async/await使用规则三:await后面需要跟Promise对象,不然就没有意义,而且await后面的Promise对象不必写then,因为await的作用之一就是获取后面Promise对象成功状态传递出来的参数。

错误处理方式
await可以获取到后面Promise对象成功状态传递出来的参数,但无法捕获其错误的状态。有两种办法可以解决:

  • 对async所在的函数配合使用then、catch来解决

    1. async function test() {
    2. let result = await promiseDemo
    3. return result //这里的result是promiseDemo成功状态的值,如果失败了,代码就直接跳到下面的catch了
    4. }
    5. test().then(...).catch(...)
  • 使用try/catch进行包裹解决

    1. async function test() {
    2. try{
    3. let result = await promiseDemo;
    4. return result;
    5. }
    6. catch (error){
    7. console.log(error)
    8. }
    9. }

    同步与异步

    1. function sleep2000() { // sleep函数
    2. return new Promise(function (resolve, reject) {
    3. setTimeout(function () {
    4. console.log('sleep2000 执行完成');
    5. resolve(new Date());
    6. }, 2000);
    7. })
    8. };
  • 同步:即串行执行,第一个完成以后才执行第二个。

    1. (async () => {
    2. console.time("delay");
    3. await delay(2000);
    4. await delay(2000);
    5. console.timeEnd("delay");
    6. })(); // delay: 4001.626953125ms,p1和p2串行执行
  • 异步:即并行执行,两个同时执行。

    1. (async () => {
    2. const p1 = delay(2000);
    3. const p2 = delay(2000);
    4. console.time("delay");
    5. await p1;
    6. await p2;
    7. console.timeEnd("delay");
    8. })(); // delay: 2000.9482421875ms,p1和p2并行执行
  • async/await 中使用 promise.all 并行处理异步操作

    1. async function getdate () {
    2. let res1 = sleep3000();
    3. let res2 = sleep2000();
    4. let res3 = sleep4000();
    5. let res = await Promise.all([res1, res2, res3]);
    6. console.log(res);
    7. }

    如何恰当选择使用
    中间值:async更优雅
    在前端编程中,我们偶尔会遇到这样一个场景:我们需要发送多个请求,而后面请求的发送总是需要依赖上一个请求返回的数据。对于这个问题,我们可以用Promise的链式调用来解决,也可以用async/await来解决,然而后者会更简洁些。

  • 使用Promise ```javascript function request(time) { return new Promise((resolve, reject) => {

    1. setTimeout(() => {
    2. resolve(time)
    3. }, time)

    }) }

request(500).then(result => { return request(result + 1000) }).then(result => { return request(result + 1000) }).then(result => { console.log(result) }).catch(error => { console.log(error) })

  1. - 使用await/async
  2. ```javascript
  3. function request(time) {
  4. return new Promise((resolve, reject) => {
  5. setTimeout(() => {
  6. resolve(time)
  7. }, time)
  8. })
  9. }
  10. async function getResult() {
  11. let p1 = await request(500)
  12. let p2 = await request(p1 + 1000)
  13. let p3 = await request(p2 + 1000)
  14. return p3
  15. }
  16. getResult().then(result => {
  17. console.log(result)
  18. }).catch(error => {
  19. console.log(error)
  20. })

Object.prototype.toString.call