拨开一切的迷雾,最终一定能看到事物的本质!
一: 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 | typeof 1 // 'number' |
但对于引用数据类型,除了函数之外,都会显示”object”。
1 | typeof [] // 'object' |
因此采用typeof判断对象数据类型是不合适的,采用instanceof会更好,instanceof的原理是基于原型链的查询,只要处于原型链中,判断永远为true
1.2.2 instanceof
因此采用typeof判断对象数据类型是不合适的,采用instanceof会更好,instanceof的原理是基于原型链的查询,只要处于原型链中,判断永远为true
1 | const Person = function() {} |
1.2.3 实现 instanceof 的功能
核心: 原型链的向上查找。
1 | function myInstanceof(left, right) { |
测试:
1 | console.log(myInstanceof("111", String)); //false |
1.3 类型转换
1.3.1 类型转换
转换为布尔值
转换为数字
转换为字符串
1.3.2 [] == ![]结果是什么?为什么?
== 中,左右两边都需要转换为数字然后进行比较。
[]转换为数字为0。
![] 首先是转换为布尔值,由于[]作为一个引用类型转换为布尔值为true。
因此![]为false,进而在转换成数字,变为0。
0 == 0 , 结果为true。
1.4 闭包
红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一个函数作用域中的变量的函数.
1.4.1 闭包产生的原因?
首先要明白作用域链的概念,其实很简单,在ES5中只存在两种作用域————全局作用域和函数作用域,当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链
,值得注意的是,每一个子函数都会拷贝上级的作用域,形成一个作用域的链条。
1 | function f1() { |
1.4.2 闭包有哪些表现形式?
明白了本质之后,我们就来看看,在真实的场景中,究竟在哪些地方能体现闭包的存在?
返回一个函数。刚刚已经举例。
作为函数参数传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14var a = 1;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
// 这就是闭包
fn();
}
// 输出2,而不是1
foo();
在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。
IIFE(立即执行函数表达式)创建闭包, 保存了
全局作用域window
和当前函数的作用域
,因此可以全局的变量。
1 | var a = 2; |
1.5 原型链相关
1.5.1 .原型对象和构造函数有何关系?
在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个prototype属性,这个属性指向函数的原型对象。
当函数经过new调用时,这个函数就成为了构造函数,返回一个全新的实例对象,这个实例对象有一个proto属性,指向构造函数的原型对象。
1.5.2 描述一下 原型链
JavaScript对象通过proto 指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条, 即原型链。
- 对象的 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 | function Parent1(){ |
这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类原型对象中一旦存在方法那么子类无法继承。那么引出下面的方法。
1.6.2 借助原型链
1 | function Parent2() { |
看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:
1 | var s1 = new Child2(); |
可以看到控制台:
明明我只改变了s1的play属性,为什么s2也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象。
那么还有更好的方式么?
1.6.3 将前两种组合
1 | function Parent3 () { |
可以看到控制台:
之前的问题都得以解决。但是这里又徒增了一个新问题,那就是Parent3的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是我们不愿看到的。那么如何解决这个问题?
1.6.4 组合继承的优化1
1 | function Parent4 () { |
这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问,但是我们来测试一下:
1 | var s3 = new Child4(); |
子类实例的构造函数是Parent4,显然这是不对的,应该是Child4。
1.6.5 (最推荐使用): 组合继承的优化1
1 | function Parent5 () { |
这是最推荐的一种方式,接近完美的继承,它的名字也叫做寄生组合继承。
1.6 手写JS Array 原生方法
1.6.1 实现 Array.prototype.map 方法
1 | Array.prototype.mymap = function(callbackFn, thisArg) { |
这里解释一下, length >>> 0, 字面意思是指”右移 0 位”,但实际上是把前面的空位用0填充,这里的作用是保证len为数字且为整数。
1.6.2 实现 Array.prototype.reduce 方法
1 | Array.prototype.reduce = function(callbackfn, initialValue) { |
1.6.3 实现 Array.prototype.push pop 方法
1 | Array.prototype.push = function(...items) { |
1.6.4 实现 Array.prototype.filter 方法
1 | Array.prototype.filter = function(callbackfn, thisArg) { |
1.7 手写 new, apply, call, bind 方法
1 | function newOperator(ctor, ...args) { |