前端进阶之路

拨开一切的迷雾,最终一定能看到事物的本质!

一: JS 相关

1.1 基础类型

七种原始数据类型

  • boolean
  • null
  • undefined
  • number
  • string
  • symbol
  • bigint

引用类型

  • Object 对象
    • Object 普通对象
    • Array 数组对象
    • RegExp 正则对象
    • Date 日期对象
    • Math 数学函数
    • Function 函数对象

1.2 检测类型

1.2.1 typeof

对于原始类型来说,除了 null 都可以调用typeof显示正确的类型。

1
2
3
4
5
6
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
复制代码

但对于引用数据类型,除了函数之外,都会显示”object”。

1
2
3
4
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
复制代码

因此采用typeof判断对象数据类型是不合适的,采用instanceof会更好,instanceof的原理是基于原型链的查询,只要处于原型链中,判断永远为true

1.2.2 instanceof

因此采用typeof判断对象数据类型是不合适的,采用instanceof会更好,instanceof的原理是基于原型链的查询,只要处于原型链中,判断永远为true

1
2
3
4
5
6
7
8
9
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true

var str1 = 'hello world'
str1 instanceof String // false

var str2 = new String('hello world')
str2 instanceof String // true

1.2.3 实现 instanceof 的功能

核心: 原型链的向上查找。

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

测试:

1
2
console.log(myInstanceof("111", String)); //false
console.log(myInstanceof(new String("111"), String)

1.3 类型转换

1.3.1 类型转换

  • 转换为布尔值

  • 转换为数字

  • 转换为字符串

1.3.2 [] == ![]结果是什么?为什么?

== 中,左右两边都需要转换为数字然后进行比较。

[]转换为数字为0。

![] 首先是转换为布尔值,由于[]作为一个引用类型转换为布尔值为true。

因此![]为false,进而在转换成数字,变为0。

0 == 0 , 结果为true。

1.4 闭包

红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一个函数作用域中的变量的函数.

1.4.1 闭包产生的原因?

首先要明白作用域链的概念,其实很简单,在ES5中只存在两种作用域————全局作用域和函数作用域,当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链,值得注意的是,每一个子函数都会拷贝上级的作用域,形成一个作用域的链条。

1
2
3
4
5
6
7
8
9
function f1() {
var a = 2
function f2() {
console.log(a);//2
}
return f2;
}
var x = f1();
x();

1.4.2 闭包有哪些表现形式?

明白了本质之后,我们就来看看,在真实的场景中,究竟在哪些地方能体现闭包的存在?

  1. 返回一个函数。刚刚已经举例。

  2. 作为函数参数传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var a = 1;
    function foo(){
    var a = 2;
    function baz(){
    console.log(a);
    }
    bar(baz);
    }
    function bar(fn){
    // 这就是闭包
    fn();
    }
    // 输出2,而不是1
    foo();
  1. 在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。

  2. IIFE(立即执行函数表达式)创建闭包, 保存了全局作用域window当前函数的作用域,因此可以全局的变量。

1
2
3
4
5
var a = 2;
(function IIFE(){
// 输出2
console.log(a);
})();

1.5 原型链相关

1.5.1 .原型对象和构造函数有何关系?

在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个prototype属性,这个属性指向函数的原型对象。

当函数经过new调用时,这个函数就成为了构造函数,返回一个全新的实例对象,这个实例对象有一个proto属性,指向构造函数的原型对象。

1.5.2 描述一下 原型链

JavaScript对象通过proto 指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条, 即原型链。

img

  • 对象的 hasOwnProperty() 来检查对象自身中是否含有该属性
  • 使用 in 检查对象中是否含有某个属性时,如果对象中没有但是原型链中有,也会返回 true

1.5.3 原型和原型链

其实原型就是那么简单,接下来我们再来看一张图,相信这张图能让你彻底明白原型和原型链

看完这张图,我再来解释下什么是原型链吧。其实原型链就是多个对象通过 __proto__ 的方式连接了起来。为什么 obj 可以访问到 valueOf 函数,就是因为 obj 通过原型链找到了 valueOf 函数。

对于这一小节的知识点,总结起来就是以下几点:

  • Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它
  • 函数的 prototype 是一个对象
  • 对象的 __proto__ 属性指向原型, __proto__ 将对象和原型连接起来组成了原型链

1.6 继承相关

1.6.1 借助call

1
2
3
4
5
6
7
8
function Parent1(){
this.name = 'parent1';
}
function Child1(){
Parent1.call(this);
this.type = 'child1'
}
console.log(new Child1);

这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类原型对象中一旦存在方法那么子类无法继承。那么引出下面的方法。

1.6.2 借助原型链

1
2
3
4
5
6
7
8
9
10
function Parent2() {
this.name = 'parent2';
this.play = [1, 2, 3]
}
function Child2() {
this.type = 'child2';
}
Child2.prototype = new Parent2();

console.log(new Child2());

看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:

1
2
3
4
var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play);

可以看到控制台:

img

明明我只改变了s1的play属性,为什么s2也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象。

那么还有更好的方式么?

1.6.3 将前两种组合

1
2
3
4
5
6
7
8
9
10
11
12
13
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3() {
Parent3.call(this);
this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);

可以看到控制台:

img

之前的问题都得以解决。但是这里又徒增了一个新问题,那就是Parent3的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是我们不愿看到的。那么如何解决这个问题?

1.6.4 组合继承的优化1

1
2
3
4
5
6
7
8
9
function Parent4 () {
this.name = 'parent4';
this.play = [1, 2, 3];
}
function Child4() {
Parent4.call(this);
this.type = 'child4';
}
Child4.prototype = Parent4.prototype;

这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问,但是我们来测试一下:

1
2
3
var s3 = new Child4();
var s4 = new Child4();
console.log(s3)

img

子类实例的构造函数是Parent4,显然这是不对的,应该是Child4。

1.6.5 (最推荐使用): 组合继承的优化1

1
2
3
4
5
6
7
8
9
10
11
  function Parent5 () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5() {
Parent5.call(this);
this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
复制代码

这是最推荐的一种方式,接近完美的继承,它的名字也叫做寄生组合继承。

1.6 手写JS Array 原生方法

1.6.1 实现 Array.prototype.map 方法

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
Array.prototype.mymap = function(callbackFn, thisArg) {
// 处理数组类型异常
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'map' of null or undefined");
}
// 处理回调类型异常
if (Object.prototype.toString.call(callbackFn) != "[object Function]") {
throw new TypeError(callbackFn + ' is not a function')
}
// 草案中提到要先转换为对象
let O = Object(this);
let T = thisArg;

let len = O.length >>> 0;
let A = new Array(len);
for(let k = 0; k < len; k++) {
// 还记得原型链那一节提到的 in 吗?in 表示在原型链查找
// 如果用 hasOwnProperty 是有问题的,它只能找私有属性
if (k in O) {
let kValue = O[k];
// 依次传入this, 当前项,当前索引,整个数组
let mappedValue = callbackFn.call(T, kValue, k, O);
A[k] = mappedValue;
}
}
return A;
}

这里解释一下, length >>> 0, 字面意思是指”右移 0 位”,但实际上是把前面的空位用0填充,这里的作用是保证len为数字且为整数。

1.6.2 实现 Array.prototype.reduce 方法

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
Array.prototype.reduce  = function(callbackfn, initialValue) {
// 异常处理,和 map 一样
// 处理数组类型异常
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'reduce' of null or undefined");
}
// 处理回调类型异常
if (Object.prototype.toString.call(callbackfn) != "[object Function]") {
throw new TypeError(callbackfn + ' is not a function')
}
let O = Object(this);
let len = O.length >>> 0;
let k = 0;
let accumulator = initialValue;
if (accumulator === undefined) {
for(; k < len ; k++) {
// 查找原型链
if (k in O) {
accumulator = O[k];
k++;
break;
}
}
}
// 表示数组全为空
if(k === len && accumulator === undefined)
throw new Error('Each element of the array is empty');
for(;k < len; k++) {
if (k in O) {
// 注意,核心!
accumulator = callbackfn.call(undefined, accumulator, O[k], k, O);
}
}
return accumulator;
}

1.6.3 实现 Array.prototype.push pop 方法

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
Array.prototype.push = function(...items) {
let O = Object(this);
let len = this.length >>> 0;
let argCount = items.length >>> 0;
// 2 ** 53 - 1 为JS能表示的最大正整数
if (len + argCount > 2 ** 53 - 1) {
throw new TypeError("The number of array is over the max value restricted!")
}
for(let i = 0; i < argCount; i++) {
O[len + i] = items[i];
}
let newLength = len + argCount;
O.length = newLength;
return newLength;
}

Array.prototype.pop = function() {
let O = Object(this);
let len = this.length >>> 0;
if (len === 0) {
O.length = 0;
return undefined;
}
len --;
let value = O[len];
delete O[len];
O.length = len;
return value;
}

1.6.4 实现 Array.prototype.filter 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Array.prototype.filter = function(callbackfn, thisArg) {
// 处理数组类型异常
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'filter' of null or undefined");
}
// 处理回调类型异常
if (Object.prototype.toString.call(callbackfn) != "[object Function]") {
throw new TypeError(callbackfn + ' is not a function')
}
let O = Object(this);
let len = O.length >>> 0;
let resLen = 0;
let res = [];
for(let i = 0; i < len; i++) {
if (i in O) {
let element = O[i];
if (callbackfn.call(thisArg, O[i], i, O)) {
res[resLen++] = element;
}
}
}
return res;
}

1.7 手写 new, apply, call, bind 方法

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function newOperator(ctor, ...args) {
if(typeof ctor !== 'function'){
throw 'newOperator function the first param must be a function';
}
let obj = Object.create(ctor.prototype);
let res = ctor.apply(obj, args);

let isObject = typeof res === 'object' && res !== null;
let isFunction = typoof res === 'function';
return isObect || isFunction ? res : obj;
};


Function.prototype.bind = function (context, ...args) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}

var self = this;

var fbound = function () {
self.apply(this instanceof self ?
this :
context, args.concat(Array.prototype.slice.call(arguments)));
}

fbound.prototype = Object.create(self.prototype);

return fbound;
}

Function.prototype.call = function (context, ...args) {
let context = context || window;
let fn = Symbol('fn');
context.fn = this;

let result = eval('context.fn(...args)');

delete context.fn
return result;
}

Function.prototype.apply = function (context, args) {
let context = context || window;
context.fn = this;
let result = eval('context.fn(...args)');

delete context.fn
return result;
}

二: HTML, CSS 相关

三: 浏览器相关

四: 框架进阶

五: 打包造轮子

六: 计算机网络

七: 设计模式

八: 数据结构

-------------本文结束 感谢您的阅读-------------
点击查看