我像麋鹿一样在林荫中走着,为着自己的香气而发狂;夜晚是五月正中的夜晚,清风是南国的清风; 我迷路了,我游荡着,我寻求那得不到的东西,我得到了我所没有寻求的东西。 ------ 泰戈尔
原型链
原型链 = 原型 + 链式连接
先说原型,比如 const arr = new Array()
,这样一串代码,在这个 arr
上有个 __proto__
属性和 Array
中的 prototype
只想同一个位置,所以关系为 arr.__proto__ === Array.prototype
,所以可以说 arr 的原型是 Array.prototype
。
接下来说原型链,也就是将多个原型链式连接。每个对象的原型都是 Object.prototype
,因为 Array.prototype
也是对象,所以关系为 Array.prototype.__proto__ === Object.prototype
,所以可以说 Array.prototype
的原型是 Object.prototype
。
这样他们之间就形成了一个链式的关系,所以叫原型链。
Javascript
const arr = new Array()
Object.getPrototypeOf(arr) === Array.prototype // true
Object.getPrototypeOf(Array.prototype) === Object.prototype // true
Object.getPrototypeOf(Object.prototype) === null // true
将上述代码图形化就是:
细致了解:继承与原型链 - JavaScript | MDN
继承
背代码就行,举例的方法,说代码实现。
1.原型继承
JavaScript
function Animal(color){
this.color = color
}
Animal.prototype.move = function(){}
function Dog(color, name){
Animal.call(this, color)
this.name = name
}
function temp(){}
temp.prototype = Animal.prototype
Dog.prototype = new temp()
Dog.prototype.constructor = Dog
let dog = new Dog("白黄黑", "小短腿")
2.使用class
JavaScript
class Animal{
constructor(leg){
this.leg = leg
}
run(){}
}
class Dog extends Animal{
constructor(name){
super(4)
this.name = name
}
say(){
console.log("xxx")
}
}
闭包
闭包
是由 函数 + 自由变量
组成的。比如:
JavaScript
var a = 1;
function foo() {
console.log(a);
}
foo();
foo
函数能访问变量 a
,但 a
既不是函数的参数,也不是局部变量,所以该变量就是自由变量,那么 函数foo
和 foo访问的自由变量a
构成了闭包
。
细致了解:JavaScript深入之闭包
this
全局执行上下文中的 this
指向是 window
对象,函数执行上下文中的 this
分多种情况:
- 在函数调用中,
this
是call、apply、bind
的第一个参数 - 使用对象调用函数中的方法,
this
指向该对象本身 - 在全局环境中调用一个函数,函数内部
this
指向全局window
细致了解:this 的值到底是什么?一次说清楚
instanceof 和 typeof
- typeof
typeof
一般用来判断一个变量的类型,主要判断 number、string、boolean、symbol、undefined、object、function
,但是这里比较特殊的就是 object
,它只能判断出是 object
而不能知道是哪种,这时候就需要使用 instanceof
来做细致判断了。有一个特殊的变量值就是 null
,它使用 typeof
也会返回一个 object
,这是因为 js底层在存储变量的时候,会在机器码低位1-3位储存类型信息
,对象类型信息是 000
,而 null
这个特殊值的存储形式均为0
,所以就被当成了对象。
- instanceof
instanceof
的作用就是用来判断某个实例是否属于某种类型
,使用方式是 a instanceof b
,实现原理是 遍历左边变量a的原型链,查找找到右边变量b的prototype
,查找失败就 return false
,也就是说一直在遍历左边变量的 __proto__
值,判断是否和右边变量的 prototype
全等。
细致了解:浅谈 instanceof 和 typeof 的实现原理
call、apply 和 bind
先看使用方式:
Javascript
fn.call(this, ...args)
fn.apply(this, [...args])
fn.bind(this, ...args)()
通过使用方式就可以一目了然的知道这三个函数之间的区别了:
call
函数的传参,从第二个形参开始,使用逗号分隔;apply
函数的传参,也是第二个形参,但是它第二形参的类型是数组,数组里面的元素是参数;bind
函数的传参和call
的一样;call & apply
都是直接调用函数;bind
是返回一个改变了执行上下文的新函数;
三个函数之间的相同点:
- 主要作用都是用来改变
this指向
也就是改变函数执行时上下文
细致了解:
「干货」细说 call、apply 以及 bind 的区别和用法
柯里化
柯里化
是一种将使用多个参数的函数转化成一系列使用单个参数的函数的技术
JavaScript
function curry(fn, length) {
length = length || fn.length;
var slice = Array.prototype.slice;
return function () {
// 调用这个返回函数的参数的长度是否小于转化前的函数的参数总个数
if (arguments.length < length) {
// 在传参没有完成的情况下,继续返回函数,等待传参
var combined = [fn].concat(slice.call(arguments));
return curry(sub_curry.apply(this, combined), length - arguments.length);
} else {
// 参数都传递完毕,执行
return fn.apply(this, arguments);
}
};
}
// 这里的 fn 是 apply(this, [..args]) ...args 中的第一个参数
function sub_curry(fn) {
var args = [].slice.call(arguments, 1);
return function () {
// 把之前的参数和当前传入的参数,合并成新数组
return fn.apply(this, args.concat([].slice.call(arguments)));
};
}
测试:
JavaScript
var fn = curry(function(a, b, c) {
return [a, b, c];
});
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
细致了解:JavaScript专题之函数柯里化
new 操作符
使用 new
操作符时,会发生以下步骤:
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行 [[ 原型 ]] 连接。
- 这个新对象会绑定到函数调用的 this。
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
实现:
JavaScript
function objectFactory() {
// 创建一个全新对象
var obj = new Object(),
Constructor = [].shift.call(arguments);
// 这个新对象执行 原型连接,Constructor 是传入的对象,也就是相当于 new A() 中的 A函数
obj.__proto__ = Constructor.prototype;
// 将新对象绑定到函数调用的 this
var ret = Constructor.apply(obj, arguments);
// 如果函数没有返回其他对象,那么返回这个新对象
return typeof ret === 'object' ? ret : obj;
};
事件循环机制
事件循环就是 Event Loop
,首先我们知道 JavaScript 是一门单线程非阻塞的脚本语言
,这个 非阻塞
的特点就是由 Event Loop
来实现的。js引擎在执行js代码时,分为两种情况,一种是 同步代码
,另一种是 异步代码
;它会将同步代码按顺序加入到 执行栈
中执行,执行的过程就是一个 压栈和弹栈
的过程,直到将栈中执行完毕结束。但是往往代码不完全是同步的,遇到异步时,js并不会一直等待它的返回结果,而是将这个事件挂起,继续执行其他任务,当异步返回结果后,并不会立即执行,而是将它放入 事件队列
中,等待执行栈中的任务完毕,主线程处于闲置状态,就会去查找 事件队列
中有没有可执行事件,如果有,将该事件推入到执行栈,然后执行同步代码,以此反复,形成一个循环,称为事件循环
。
细致了解:详解JavaScript中的Event Loop(事件循环)机制
Promise 原理
promise
有三种状态,Peding
(等待),Fulfilled
(执行),Rejected
(拒绝)。(⚠️:promise
状态是不可逆的,且只有两种变化:P -> F, P -> R)。promise
可以进行链式调用promise.then().then()
,可以知道在调用then
方法后返回一个新的promise
实例,上一个回调函数返回的结果会被返回给下一个promise
,这样实现了链式调用。
javascript
const p = new Promise((resolve, reject) => {
return resolve(1)
})
p.then((res) => {
console.log("one then res: ", res);
return 2
}).then((res) => {
console.log("two then res: ", res);
})
/*
one then res: 1
two then res: 2
*/
细致了解:这一次,彻底弄懂Promise原理
generator 原理
首先,看下generator
基础用法
javascript
function* example() {
yield 1;
yield 2;
yield 3;
}
var iter=example();
iter.next();//{value:1,done:false}
iter.next();//{value:2,done:false}
iter.next();//{value:3,done:false}
iter.next();//{value:undefined,done:true}
在分析这段代码执行原理前,先了解下线程
和协程
,协程比线程更加轻量级,可以看作是协程是跑在线程上的任务,一个线程可以有多个协程,但是每次只能同时执行一个协程,比如执行协程A,需要执行B了,要先停止A的执行,控制权转交给B,B开始执行。
好了,然后请记住上面的话,将它带入到这段代码中,你可以把generator
的执行看作就是协程之间的转换
- 启动 example,将
1
返回给其它代码,同时停止 example 的执行,恢复其他代码的执行 - 执行第一个 next 函数,执行后,此时就会重新执行 example ,停止其它代码执行
- 此刻和第 1 步一样,只有返回值不同,也就是 yield 2 了 ...依此类推,知道没有结果 done为true
async & await 原理
async/await 技术原理就是promise
和generator
的应用,底层讲就是微任务和协程应用,它改变了生成器(generator
)的缺点,提供了在不阻塞主线程的情况下,使用同步代码访问异步资源的能力。
async
函数在执行时也是一个单独的协程,await
可以用来暂停这个协程,等待一个promise
对象,当await
等待这个promise
状态为Fulfilled
resolve后,V8就会恢复这个协程的执行。
使用下面例子可以清晰看到:
javascript
function HaveResolvePromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100)
})
})
}
async function getResult() {
console.log(1);
const res = await HaveResolvePromise();
console.log(res);
console.log(2);
}
console.log(0);
getResult();
console.log(3);
/*
0
1
3
100
2
*/