0%

Introduction

这篇文章主要介绍一下JavaScript中的比较运算符==。先看几道题,如果你都能答上来,那么可以不用看此文了。

1
2
3
4
5
6
7
8
9
console.log('abc' == String('abc'));
console.log('abc' === String('abc'));
console.log('abc' == new String('abc'));
console.log('abc' === new String('abc'));

console.log([] == []);
console.log({} == {});

console.log(NaN == NaN);

Difference between String('abc') and new String('abc')

  • String(xxx)是将xxx转换为字符串,所以它返回的是字符串,而'abc'原本就是字符串,这里相当于直接返回'abc'。typeof String(‘abc’)返回的是string
  • new String(xxx)是创建一个字符串对象,其内容是xxx,所以它返回的是一个对象类型,不是字符串类型。typeof new String(‘abc’)返回的是object

How == works?

==采用Loose equality算法,其过程如下:

  1. 如果两个操作数类型相同,则按如下规则比较:
    1. Object类型:当两个操作数指向同一个对象时返回true,否则返回false
    2. String类型:当两个操作数的字符序列相同,返回true
    3. Number类型:当两个操作数的数值相同,返回true。注意:+0-0被认为是相等的,NaN和任何值都不相等,包括其自身。
    4. Boolean类型:当两个操作数都是true或者都是false时返回true
    5. BigInt类型:当两个操作数的数值相同,返回true
    6. Symbol类型:当两个操作数都是相同的Symbol值时返回true
  2. 如果两个操作数类型不同,则按如下规则比较:
    1. 如果一个操作数是null,另一个操作数是undefined,返回true
    2. 如果一个操作数是primitive类型,另一个操作数是Object类型,将Object类型转换为primitive类型(转换规则在此),再按如下规则比较。
    3. 到这一步时,两个操作数都是primitive类型了,按如下规则继续比较。
      1. 如果两个操作数类型相同,按照第1条规则比较。
      2. 如果一个操作数是Symbol类型,但另一个不是,返回false
      3. 如果一个操作数是Boolean类型,将其转换为Number类型(true -> 1, false -> 0),继续往下比较。
      4. Number类型与String类型比较,将String类型转换为Number类型进行比较。转换规则在此
      5. Number类型与BigInt类型比较,比较他们的值,如果有一个操作数是+Infinity/-Infinity/NaN,返回false
      6. String类型与BigInt类型比较,将String类型转换为BigInt类型(用BigInt()转换),若转换失败则返回false

类型相同时,比较好理解,我们重点梳理一下类型不同时的比较。

  1. 两个对象类型之间的比较是比较引用地址,也比较简单,无需赘述。
  2. 一个对象和一个基本类型的比较,会将对象转换为基本类型,然后再比较。
  3. 两个基本类型之间的比较,其实,最终都会归类到此类比较。

所以,我们重点讨论一下基本类型之间的比较。JS中有7种基本类型,分别是String, Number, BigInt, Boolean, Symbol, null, undefined

  1. nullundefined之间的比较,返回true
  2. Symbol和其他类型之间的比较,都返回false
    至此,还剩下String, Number, BigInt, Boolean之间的比较。剩下的这几个类型比较,基本围绕Number进行。
  3. BooleanNumber之间的比较,将Boolean转换为Number,然后再比较。
  4. StringNumber之间的比较,将String转换为Number,然后再比较。
  5. BooleanString之间的比较,将他们都转换为Number然后再比较。
  6. StringBigInt之间的比较,将String转换为BigInt,然后再比较。
  7. NumberBigInt之间的比较,比较他们的值,如果有一个操作数是+Infinity/-Infinity/NaN,返回false

注意:

  1. Loose Equality算法满足交换律,即A == B等价于B == A
  2. document.all被视为undefined.

Examples

以下给出一些采用==进行比较的例子,我们会给出每个例子的比较规则对应的序号(参考How == works一节)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"1" == 1; // true, 2.3.4
true == "1" // true, 2.3.3 and 2.3.4
[] == ""; // true, 2.2, 2.3.1
0 == false; // true, 2.3.3
0 == null; // false, 2.1
0 == undefined; // false, 2.1
null == undefined; // true, 2.1
0 == !!undefined; // true, 2.3.3

// 这里有一个窍门,只要两个操作数都是对象字面量,那么一定不相等。无论==还是===都不相等。
[] == []; // false, 1.1
{} == {}; // false, 1.1
{ name: 'Philip', age: 18 } == { name: 'Philip', age: 18 }; // false, 1.1

NaN == NaN; // false, 1.3, not NaN === NaN is also false.
+0 == -0; // true, 1.3

document.all == undefined; // true, 2.1
document.all == null; // true, 2.1

const number1 = new Number(3);
const number2 = new Number(3);

number1 == 3; // true, 2.3.1
number1 == number2; // false, 1.1

const object1 = {
key: "value",
};

const object2 = {
key: "value",
};

console.log(object1 == object2); // false, 1.1
console.log(object1 == object1); // true, 1.1

下面这个例子要注意:

1
2
3
const a = [1, 2, 3];
const b = "1,2,3";
a == b; // true, 2.3.1

Array.prototype.toString()方法重写了Object.prototype.toString()方法,数组转化为字符串时,会执行以下步骤:

  1. 对数组内每个元素调用toString()方法,将其转化为字符串。
  2. 将这些字符串用逗号连接起来。
  3. 返回这个字符串。

所以上面的例子就是[1, 2, 3].toString() == "1,2,3",所以返回true
所以空数组和空字符串之间的比较也是true

1
[] == ""; // true, 2.3.4

数组转字符串还有很多细节,详情请看这里:Array.prototype.toString()

总结

  1. ==比较时,不要老想着转换为Boolean,其实大多数时候是转换为Number
  2. 对象字面量使用==比较时,一定是false
  3. new String(...)String(...)完全不同,前者是创建String对象,后者是将...转换为字符串。

References

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality

Introduction

本来用英语写了一篇,结果看起来乱糟糟的,还是用中文写一下吧。

原型是JavaScript中非常重要的概念,面试中也常常会问到这个问题。那么,原型到底是什么?
原型的英文是prototype,他是函数的一个属性,是一个对象。

下面来看一张图,这个图乍一看非常复杂,但是理解之后,就会发现原型其实非常简单。
js-prototype

在理解这张图之前,我们先明确几个概念:

  1. 对象具有__proto__constructor属性。
  2. 函数具有prototype属性。
  3. 因为JS中函数也是对象,所以函数也有__proto__constructor属性。

所以,我们可以得出以下结论:

  1. 对象具有__proto__constructor属性。
  2. 函数具有prototype, __proto__constructor属性。

这两条结论反映在上面的图中就是:

  1. 所有蓝色节点都向外发出两条线,分别对应__proto__constructor属性。
  2. 所有绿色节点都向外发出三条线,分别对应prototype, __proto__constructor属性。

我们以一个简单的Person函数阐述上面这张图。

1
2
3
4
5
6
7
8
9
10
function Person(name) {
this.name = name;
}

Person.prototype.printName = function () {
console.log(this.name);
};

const p = new Person('Philip');
console.log(p);

__proto__属性

在这个例子中,Person函数是一个构造函数,pPerson的一个实例。为了方便,我们将上图简化一下,只保留__proto__属性。
js-prototype-proto

先看左边纵向的这条线。

  1. 因为对象pPerson函数的一个实例,所以p__proto__属性指向Personprototype属性。
  2. Person继承自Object,所以Person__proto__属性指向Objectprototype属性。
  3. Object__proto__属性指向null

p, Person, Object, null之间通过__proto__属性形成了一条链,这就是所谓的原型链。当我们在某个对象上查找一个属性时,如果该对象没有这个属性,那么JS会通过原型链向上查找,直到找到这个属性或者到达null。举几个例子:

  • p.printName() - p对象上没有printName属性,所以JS会通过原型链向上查找,找到Personprototype属性,从而找到printName方法。
  • p.hasOwnProperty('name') - p对象上没有hasOwnProperty属性,向上查找到PersonPerson的原型对象上也没有hasOwnProperty属性,继续向上查找到ObjectObject原型上有hasOwnProperty属性,于是调用之,所以返回true
  • p.foo() - p对象上没有foo属性,向上查找到PersonPerson的原型对象上也没有foo属性,继续向上查找到ObjectObject的原型上还是没有foo属性,继续向上查找到null,而null上也没有foo属性,所以会报错。— TypeError: p.foo is not a function

再看右侧部分,Person, Function, Object这三者皆是函数,而JS中函数也是对象,故而这三者亦有__proto__属性,而且都指向Functionprototype属性。而Function__proto__属性指向Objectprototype属性。

由此我们可以得出结论:

  1. 所有对象的__proto__属性指向其构造函数的prototype属性。
  2. 所有函数的__proto__属性指向Functionprototype属性。
  3. Function__proto__属性指向Objectprototype属性。
  4. Object__proto__属性指向null

所有__prototype__的终点都是null.

prototype属性

照例现简化一下上图,只保留prototype属性。
js-prototype-prototype

因为prototype是函数的一个属性(对象没有prototype),所以图中只有绿色节点有prototype属性。对于一个函数Foo来说,他的prototype属性是一个对象,指向Foo.prototype

constructor

最后来看constructor属性。
js-prototype-constructor

constructor就是某个对象的构造函数,因为JS中函数也是对象,所以函数也有constructor属性。

  1. 对于普通对象,其constructor属性指向其构造函数。
  2. 对于函数,其constructor属性指向Function
  3. Functionconstructor属性指向其自身。

根据以上结论,不难得出如下结论:

  1. 对象pconstructor属性指向Person函数, 因为pPerson的一个实例。
  2. Person.prototypeconstructor指向Person函数,这个是为啥?- 硬性规定
  3. PersonObjectconstructor都指向Function,因为PersonObject都是函数。
  4. Functionconstructor属性指向Function

由以上结论可以推导出:Function是所有constructor的终点。

总结

我们把上面的结论公式化一下,对于如下代码:

1
2
function Person() {}
const p = new Person();

我们有如下结论成立:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
console.log(p.__proto__ === Person.prototype); // true
console.log(p.constructor === Person); // true

console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Person.prototype.constructor === Person); // true

console.log(Object.prototype.__proto__ === null); // true

console.log(Person.__proto__ === Function.prototype); // true
console.log(Person.constructor === Function); // true

console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Function.prototype.constructor === Function); // true

console.log(Object.__proto__ === Function.prototype); // true
console.log(Object.constructor === Function); // true

console.log(Function.constructor === Function); // true

其他杂项

  1. 箭头函数没有prototype属性,因为箭头函数不可以做构造函数。
  2. Bound函数(使用bind创建的函数)也没有prototype属性。why?

Introduction

prototype is an very important concept in JavaScript. It’s used to implement inheritance in JavaScript.

prototype is a property of a function, it’s an object. When you create a function, JavaScript engine will automatically create a prototype object for you. This object has a property called constructor, which points back to the function.

The prototype data property of a Function instance is used when the function is used as a constructor with the new operator. It will become the new object’s prototype.

Take the following code as an example, its prototype is an empty object {}.

1
2
3
4
function Person(name) {
}
console.log(Person.prototype.constructor === Person); // true
console.log(Person.prototype); // {}

Let’s create a functions on its prototype, and it’s prototype now has a property(function) called printName.

1
2
3
4
5
6
7
8
9
function Person(name) {
this.name = name;
}

Person.prototype.printName = function () {
console.log(this.name);
};

console.log(Person.prototype); // { printName: [Function (anonymous)] }

Create an instance of Person

When you create an instance of Person, the instance will have a property called __proto__, which points to the prototype of the constructor function(the Person function).

1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
this.name = name;
}

Person.prototype.printName = function () {
console.log(this.name);
};

const person = new Person('Philip');
console.log(person.__proto__ === Person.prototype); // true
console.log(person.printName()); // Philip

Functions defined on the prototype are shared by all instances of the constructor function.

1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
this.name = name;
}

Person.prototype.printName = function () {
console.log(this.name);
};

const p1 = new Person('zdd');
const p2 = new Person('Philip');
console.log(p1.printName === p2.printName); // true

But, functions defined in the constructor function are not shared by all instances.

1
2
3
4
5
6
7
8
9
10
function Person(name) {
this.name = name;
this.printName = function () {
console.log(this.name);
};
}

const p1 = new Person('zdd');
const p2 = new Person('Philip');
console.log(p1.printName === p2.printName); // false

References

  1. https://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
  2. https://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
  3. https://zhuanlan.zhihu.com/p/65858062

What’s the difference between require and import?

前端如何做性能优化?

我能想到的点,做性能优化前一定要有benchmark,否则你不知道你的优化是否有效果。

网络层面优化

  1. 減少Http请求
    1. 合并文件,将多个css或者javascript文件合并为一个,减少http请求次数。但同时要避免单个文件过大,导致加载时间过长。一个文件的大小最好不要超过1MB。
    2. 使用css sprite,将多个小图片合并为一个大图片,通过css的background-position来显示不同的图片。
    3. 较小的css可以内联到html中,减少http请求。
    4. 使用base64编码,将小图片转为base64编码,直接写在css文件中,减少http请求。
    5. 使用svg/webp代替png/jpg图片。
    6. 启用gzip压缩,减少文件大小。
  2. 启用缓存:
    1. 强缓存:通过设置http header中的Expires和Cache-Control: max-age 来开启强缓存。
    2. 协商缓存:通过设置http header中的Last-Modified和ETag来开启协商缓存。
  3. 使用CDN,将静态资源放到CDN上,减少服务器的压力,提高访问速度。

打包优化

  1. Tree-shaking, 删除无用代码
  2. lazy loading,按需加载, 通过webpack等打包工具,lazy loading的组件可以打包成单独的chunk文件,按需加载
  3. code splitting,代码分割,将代码分割成多个chunk,按需加载 - 这个和lazy loading是一样的。
  4. 代码压缩,删除空格,注释,压缩代码 - 这都是打包工具的基础功能。

代码层面的优化

  1. 书写高效代码,避免使用过多的循环,避免使用过多的递归。
  2. 避免Reflow和Repaint,减少DOM操作,尽量一次性修改DOM。

启用性能监控

性能优化的前提是知道哪里慢,才能有针对性的优化,所以优化前一定要进行监控,找到嗯提的根源,对症下药。

JavaScript中new操作符做了哪些操作?

在JavaScript中,new 操作符用于创建一个对象。当你使用 new 操作符时,它执行以下步骤:

  1. 创建空对象
  2. 将新对象的 __prototype__属性链接到构造函数的 prototype 属性
  3. 执行构造函数中的代码,并将新对象绑定到 this 上。
  4. 返回新对象

下面是一个简单的例子:

1
2
3
4
5
6
7
function Person(name, age) {
this.name = name;
this.age = age;
}

const p = new Person('zdd', 40);
console.log(p);

面试官可能接着问,如何写一个函数,模拟new操作符的行为?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function NewPerson(fn, ...args) {
// Create an empty object
const obj = {};

// Set the prototype of the object to the prototype of the function
obj.__proto__ = fn.prototype;

// Call the function with the object as the context
const result = fn.apply(obj, args);

// Return the object
return result instanceof Object ? result : obj;
}

const p1 = NewPerson(Person, 'zdd', 40);
console.log(p1);

其实上面的代码可以简化一下,比如前两行代码其实可以用下面的一行代码代替。

1
const obj = Object.create(fn.prototype);

如何实现instanceof操作符?

instanceof操作符用于检查一个对象是否是一个类的实例。下面是一个简单的实现:

1
2
3
4
5
6
7
8
9
10
11
12
function myInstanceof(obj, constructor) {
let proto = obj.__proto__;
while (proto) {
if (proto === constructor.prototype) {
return true;
}
proto = proto.__proto__;
}
return false;
}

myInstanceof([1, 2, 3], Array); // true

__proto__属于非标准属性,所以不推荐使用。可以使用Object.getPrototypeOf来获取对象的原型。

1
2
3
4
5
6
7
8
9
10
function myInstanceof(obj, constructor) {
let proto = Object.getPrototypeOf(obj);
while (proto) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}

什么是Webpack?

Webpack是一个打包工具,它是基于Node.js的,也就是说它是用JS写的,所以它的速度比较慢,但是它的功能非常强大。而其他的打包工具,比如ESBuild是用Go语言书写的,速度会快很多。

为什么需要使用Webpack呢?
在前端技术日益发展的今天,前端技术极大丰富,各种框架和库层出不穷,比如Angular/React/Vue等,我们的前端项目可能是用不同的框架书写的,可能还要支持typescript,我们还可能使用了不同的CSS预处理器,比如SASS/LESS等,而浏览器是不懂框架的语法的,需要转换成原生JS,浏览器也不懂typescript,也需要转换成JavaScript,也不懂SASS/LESS语法,需要转换成原生的CSS。

此外,如果你使用了新的ES语法,而浏览器此时尚未支持该语法,那么Webpack也可以帮你转换成浏览器支持的语法。

Webpack的主要功能有:

  1. 打包JS、CSS、图片等资源文件。
  2. 代码转换,比如ES6转ES5、SCSS转CSS等。对于比较新的语法,Webpack可以弥补浏览器未实现的部分。
  3. 代码分割,将代码分割成多个文件,按需加载。- Lazy loading,将需要lazy load的模块单独打包成一个文件,当需要的时候再加载。
  4. 代码压缩,减少文件体积。
  5. 代码优化,比如去除无用代码、去除重复代码等。- Tree shaking
  6. Webpack-dev-server - 本地开发服务器,实时刷新。

Webpack中的hash、chunkhash、contenthash有什么区别?

This post includes the Browser DOM related interview questions

Performance optimization

前端性能优化的方法有哪些?

  1. 使用懒加载 - 减少首屏加载时间
  2. 減少包体积 - 提升首页加载速度
  3. 合并API call - 减少网络请求次数
  4. 使用CDN - 加速资源加载速度
  5. 使用缓存 - 减少重复请求

What’s the event loop in Chrome browser?

事件循环的核心流程如下:

  1. 执行同步代码 :
    所有同步代码会被依次执行,并压入调用栈。
    同步代码执行完毕后,调用栈清空。
  2. 检查微任务队列 :
    如果调用栈为空,事件循环会优先检查微任务队列。
    将微任务队列中的任务依次取出并执行,直到微任务队列为空。
  3. 渲染更新 :
    在浏览器环境中,如果需要更新 DOM 或绘制页面,此时会进行渲染。
  4. 检查宏任务队列 :
    当微任务队列为空时,事件循环会从宏任务队列中取出一个任务并执行。
    宏任务执行完毕后,再次回到步骤 2,检查微任务队列。

以下代码输出结果是什么?

1
2
3
4
5
6
7
8
9
10
11
console.log('Start'); // 1. 同步代码,立即执行

setTimeout(() => {
console.log('Timeout'); // 5. 宏任务,最后执行
}, 0);

Promise.resolve().then(() => {
console.log('Promise'); // 3. 微任务,优先于宏任务
});

console.log('End'); // 2. 同步代码,立即执行

答案:

1
2
3
4
Start
End
Promise
Timeout

什么是cookie?如何保证cookie的安全性?

我们现不纠结cookie的定义,而是看为什么需要cookie,可能你已经听过这个结论:HTTP是无状态的,也就是说每次请求都是独立的,服务器无法知道这次请求和上次请求是否来自同一个用户。cookie就是为了解决这个问题而诞生的。关于cookie的使用方法,可以看这篇

保证cookie的安全性,主要有以下几个方面:

  1. 设置HttpOnly属性:HttpOnly属性是cookie的一个属性,它的作用是禁止JavaScript访问cookie,这样可以防止XSS攻击。
  2. 设置Secure属性:Secure属性是cookie的一个属性,它的作用是要求cookie只能通过HTTPS协议传输,这样可以防止MITM攻击。
  3. 设置SameSite属性:SameSite属性是cookie的一个属性,它的作用是指定cookie是否可以随着跨域请求发送,这样可以防止CSRF攻击。
  4. 合理设置cookie的过期时间,过期时间够用就好,不要太长。
  5. 不要在cookie中存储敏感信息,如密码等。

以下是使用Express设置cookie的示例代码:

1
2
3
4
5
6
7
8
9
app.get('/set-cookie', function(req, res) {
res.cookie('session_id', 'your-session-id-here', {
httpOnly: true, // 设置HttpOnly标志
secure: true, // 强制HTTPS传输
sameSite: 'strict', // 防止CSRF攻击
maxAge: 3600 // 设置Cookie有效期为1小时.
});
res.send('Cookie has been set');
});

localStorage和sessionStorage的区别是什么?

localStorage和sessionStorage都是HTML5提供的用于在浏览器端存储数据的机制,它们属于Web Storage API的一部分。尽管两者有相似之处,但也存在一些关键区别:

  • 生命周期:
    • localStorage:数据没有过期时间,一旦数据被存储,它将永远保留在那里,除非用户通过浏览器设置手动清除数据,或者使用JavaScript代码显式地删除它。
    • sessionStorage:数据的生命周期与页面会话等同。页面会话在浏览器打开期间一直保持,包括页面重新加载或恢复。一旦浏览器关闭,所有在sessionStorage中保存的数据都会被清除。
  • 作用范围:
    • localStorage:在同一源(协议+域名+端口)下的所有窗口、标签页之间共享数据。这意味着即使你在一个标签页中修改了localStorage中的数据,在同一网站的其他标签页中也能访问到更新后的数据。
    • sessionStorage:对于来自同一源的每个页面,都有其自己的独立存储空间,并且这些存储空间互不干扰。也就是说,不同的标签页或窗口间无法共享sessionStorage中的数据,即使是同一个网站。
  • 使用场景:
    • localStorage适用于需要长期存储的数据,如用户偏好设置、应用状态等。
    • sessionStorage则更适合于临时性的、仅在当前会话期间有用的场景,比如保存表单数据以防止刷新页面时丢失信息。

What’s the benefit of web component?

  1. Cross platform, framework free.

What’s the differences between document.write and innerHTML?

Answer:

  • document.write update the entire page.
  • innerHTML update the content of an element, it can be used at any time.

What’s the output of the following code?

1
2
3
4
5
6
7
8
<body>
<script>
document.body.style = "background-color: blue;";
Promise.resolve().then(() => {
document.body.style = "background-color: red;";
});
</script>
</body>

Answer:
The browser start to render the page after micro task queue is empty in each event loop. So the background color of the body will be red directly(You won’t see the blue background color).

How to create group for options in a select element?

Answer:
You can use optgroup element to group options in a select element.

1
2
3
4
5
6
7
8
9
10
<select>
<optgroup label="China">
<option>Beijing</option>
<option>Dalian</option>
</optgroup>
<optgroup label="America">
<option>Washington</option>
<option>New York</option>
</optgroup>
</select>

Closure

Use case 1: cache data.

In the following example, the inner function getId referenced the variable id in outer function createIdGenerator. The variable id is memorized and each call to generateId will return the next id.

1
2
3
4
5
6
7
8
9
10
11
function createIdGenerator() {
let id = 0;
return function getId() {
return id++;
};
}

const generateId = createIdGenerator();
console.log(generateId()); // 0
console.log(generateId()); // 1
console.log(generateId()); // 2

event loop

prototype

this

this in javascript is a very complex concept, and we need a separated post to elaborate it. Here we just give a brief introduction.

promise

async/await

据说async/await底层是借助Promise和generator来实现的,有待学习。

Primitive wrapper objects

1
2
3
4
5
6
7
8
9
10
11
const s1 = new String('abc');
const s2 = new String('abc');

const s3 = String('abc');
const s4 = String('abc');

console.log(s1 == s2);
console.log(s3 == s4);

console.log(s1 == s3);
console.log(s1 === s3);

Answer:

1
2
3
4
false
true
true
false
  • s1和s2都是object类型,所以它们的引用地址不同,所以s1 == s2返回false
  • s3和s4都是string类型,所以它们的值相同,所以s3 == s4返回true
  • s1是object类型,s3是string类型,比较时先将s1转换为primitive类型,也就是字符串类型,所以s1 == s3返回true
  • s1是object类型,s3是string类型,所以s1 === s3返回false
    关于JavaScript中==的详细规则,请看这里

输出结果是什么?

1
2
const data = new Uint8Array();
console.log(Array.isArray(data));

Answer: false
Uint8ArrayArrayBuffer 对象的一个视图,它表示一个8位无符号整数的数组。Uint8Array 实例不是一个数组,所以 Array.isArray(data) 返回 false