0%

Console妙用知多少?

平时我们使用console的时候,基本都是console.log(xxx), 其实console的用途不只是打log,今天我们来看看console的其他用法。

1
2
3
console.log(xxx); // print log
console.warn(xxx); // print warning
console.error(xxx); // print error

1. console.time

console.timeconsole.timeEnd 可以用来计算代码执行时间,用法如下:

1
2
3
4
5
console.time('my-timer');
for (let i = 0; i < 1000000; i++) {
// do something
}
console.timeEnd('my-timer');

如果在代码执行中打印阶段性时间,可以使用console.timeLog

1
2
3
4
5
6
7
console.time('my-timer');
for (let i = 0; i < 1000000; i++) {
if (i % 100000 === 0) {
console.timeLog('my-timer', i);
}
}
console.timeEnd('my-timer');

2. console.table

console.table 可以将数组或对象以表格形式打印出来,用法如下:

1
2
3
4
5
6
const data = [
{ name: 'Alice', age: 18 },
{ name: 'Bob', age: 20 },
{ name: 'Cathy', age: 22 },
];
console.table(data);

3. console.assert

console.assert 可以用来断言某个条件是否为真,如果为假,则输出错误信息,用法如下:

1
console.assert(1 === 2, '1 is not equal to 2');

输出结果如下:

VM479:1 Assertion failed: 1 is not equal to 2

4. console.count

console.count 可以用来统计某个函数被调用的次数,用法如下:

1
2
3
4
5
6
7
8
9
function greet(user) {
console.count();
return `hi ${user}`;
}

greet("bob");
greet("alice");
greet();
console.count();

输出结果如下:

1
2
3
4
default: 1
default: 2
default: 3
default: 4

5. console.group

console.groupconsole.groupEnd 可以用来将输出的内容分组,用法如下:

1
2
3
4
console.group('group1');
console.log('hello');
console.log('world');
console.groupEnd();

6. console.dir

console.dir 可以用来打印对象的属性,用法如下:

1
2
const obj = { name: 'Alice', age: 18 };
console.dir(obj);

7. console.clear

console.clear 可以用来清空控制台,用法如下:

1
console.clear();

8. console.trace

console.trace 可以用来打印当前调用栈,用法如下:

1
2
3
function foo() {
console.trace();
}

What’s the differences between interface and type in typescript.

  1. types can create primitive type alias, while interface cannot.

    1
    2
    type Name = string;
    const name: Name = 'zdd';
  2. interface can do declaration merging together

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    interface Person {
    name: string;
    }

    interface Person {
    age: number;
    }

    const person: Person = {
    name: 'zdd',
    age: 18
    }

    console.log(person);

    while type can not do declaration merging

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    type Person = {
    name: string;
    }

    type Person = { // Error, Duplicate identifier 'Person'
    age: number;
    }

    const person: Person = {
    name: 'zdd',
    age: 18,
    }
  3. Extends and implements

    • interface can extends another interface or class
    • class can implement interface
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Person {
    sayHi() {
    console.log('Hi')
    }
    }

    interface IPerson extends Person {
    name: string;
    }

    class Student implements IPerson {
    name: string;
    constructor(name: string) {
    this.name = name;
    }

    sayHi() {
    console.log('Hi, I am a student')
    }
    }

    Interface extends interface

    1
    2
    3
    interface PartialPointX { x: number; }
    interface Point extends PartialPointX { y: number; }

    Type alias extends type alias

    1
    2
    type PartialPointX = { x: number; };
    type Point = PartialPointX & { y: number; };

    Interface extends type alias

    1
    2
    type PartialPointX = { x: number; };
    interface Point extends PartialPointX { y: number; }

    Type alias extends interface

    1
    2
    interface PartialPointX { x: number; }
    type Point = PartialPointX & { y: number; };
  4. types can create interaction types, but interface not.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    type Name = {
    name: string;
    }

    type Age = {
    age: number;
    }

    type Person = Name & Age; // OK

    interface Name {
    name: string;
    }

    interface Age {
    age: string;
    }

    type Person = Name & Age; // OK
    interface Person = Name & Age // Error.
  5. types can create union types, but interface not.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    type Man = {
    name: string;
    }

    type Women = {
    name: string;
    }

    type Person = Man | Women; // ok

    interface Man {
    name: string;
    }

    interface Woman {
    name: string;
    }

    type Person = Man & Woman; // ok
    interface Person = Man & Woman; // Error.
  6. types can define tuple, but interface not.

    1
    2
    type Response = [string, number]; // ok
    interface Response = [string, number]; // not work

总结一下,主要的区别就是:

  • interface可以做declaration合并,但是type不能。
  • interface can be extend by class, while type can not be extend.
  • type 可以定义union interaction类型,但是interface不能。

How === works ?

Suppose the following expression

1
A === B

How strict equality works in JavaScript? Here are the steps:

  1. If the operands are of different types, return false
  2. If both operands are objects, return true only if they refer to the same object.
  3. If both operands are null or both operands are undefined. return true.
  4. If either operand is NaN, return false
  5. Otherwise, compare the two operand’s value as below
    1. Numbers must have the same numeric values.
    2. Strings must have the same characters in the same order.
    3. Booleans must be both true or both false.

Code Example

1
2
3
4
5
6
7
8
9
console.log(1 === '1'); // false, different type.
console.log({ name: 'zdd' } === { name: 'zdd' }); // false, different object with same value.
console.log([] === []); // false
console.log({} === {}); // false
console.log(null === undefined); // false, different type.

// Pay more attention to the following examples
console.log(NaN === NaN); // false
console.log(+0 === -0); // true

References

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

Introduction

How to implement lazy load images in html page?

Use loading=lazy attribute of img tag

With html5, you can use loading="lazy" attribute to lazy load images. This attribute tells the browser to defer loading of the image until it is needed. The browser will load the image when it is about to come into view.

1
<img src="image.jpg" loading="lazy" alt="描述性文字">

For old browsers which do not support loading attribute, you can use JavaScript to implement lazy load images.

1
2
<!-- HTML -->
<img data-src="image.jpg" class="lazyload" alt="描述性文字">
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
<script>
document.addEventListener("DOMContentLoaded", function() {
let lazyImages = [].slice.call(document.querySelectorAll("img.lazyload"));

if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove("lazyload");
lazyImageObserver.unobserve(lazyImage);
}
});
});

lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
} else {
// Fallback for browsers that do not support Intersection Observer
let oldLoad = function(imgs) {
imgs.forEach(function(img) {
img.src = img.dataset.src;
});
};
oldLoad(lazyImages);
}
});
</script>

Please note that, window.load event won’t wait for lazy loaded resources.

References

  1. https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes

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;
}