执行上下文
创建阶段
当调用函数时,会进入创建上下文阶段,此阶段会绑定this、生成作用域链、生成变量。
1、this
var b = 20;
var obj = {
b:40,
c: this.b + 20,
a: function {
return this.b;
}
上述c的值为40,this.b为全局的b,因为obj在全局环境,没有单独生成一个作用域,this指向window。
function f () {
function g () {};
}
f和g的this都为window。因为g没有赋给一个对象,因此都是全局调用。
在函数回调过程中,可能发生this的变化,如setTimeout等调用,this会变为window。解决办法:可以将this暂存起来。
call、apply、bind实现
要点:arguments时类数组,不是真实数组,不能直接用
bind返回值是一个待执行的闭包函数,执行时内部调用了apply方法,将function绑定到新的对象上。
2.作用域链
作用域链主要用于闭包,闭包会导致内存泄漏。
闭包形成条件:A函数内部有函数B引用了A的内部变量,那么A函数即为闭包函数。
执行阶段
执行阶段,变量会从栈中弹出。一些函数执行结果相同,但是入栈出栈顺序不一定相同。
原型
- protoType只有函数有,Function既是函数也是对象,Function.proto = Function.protoType;
- instanceof用于判断是否在某个函数的原型对象上
- new做了什么?new创建了一个对象的实例。创建一个新对象,并让该对象指向构造函数的原型,使得该对象成为构造函数的实例,并通过apply方法执行构造函数的方法并返回结果。
function myNew(constructor, args) {
let obj = {};
obj.__proto__ = constructor.protoType;
let res = constructor.apply(obj, args);
return res instanceof Object ? res : constructor;
}
- 组合寄生式继承:该继承方法既可以继承父类,又可以将子类和父类的原型分开
function Parant(name) {
console.log(this.name);
}
function Children(name, age) {
Parant.call(this, name); // 构造函数式继承
this.age = age;
}
Children.__proto__ = Object.create(Parent.protoType); // 原型继承,Children的原型指向父类的原型实例
Children.protoType.constructor = Children; // 修改原型的构造函数,使得children和Parent分离
深拷贝
深拷贝方法:JSON.Parse(JSON.Stringify(obj)),递归。
JSON.Stringify无法拷贝function,null,undefined等值。
首层深拷贝方法有:slice,concat,assign,这些方法只会拷贝第一层,即简单类型的值。引用类型依然是浅拷贝。
// 递归实现深拷贝
var deepClone = (obj) => {
if (typeof obj !== 'object' || obj === null) return obj;
let hash = new Map(); // 使用hash解决循环引用的问题
let newObj = Array.isArray(obj) ? [] : {};
// 递归过程中发现有循环,终止递归,返回hash表存的值
// 如果该对象已经被拷贝过,则直接返回引用
if (hash.has(obj)) {
return hash.get(obj);
}
hash.set(obj, newObj); // 存储遍历过的值
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
hash.set(key, obj[key]);
deepClone(obj[key]);
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
事件循环
浏览器事件循环和node.js事件循环不同。
浏览器事件循环:JavaScript是单线程模式,一个时段只能执行一个任务。同步任务是主线程直接执行,异步任务是放入宏任务、微任务队列,等待同步任务执行完后再调用执行。
再每一次任务循环中,会先执行所有的同步代码,然后清空所有的微任务,执行下一次的宏任务,然后清空所有微任务…
async await是Promise的语法糖。其相当于如下写法: Promise.resolve(new Promise()).then(callback);resolve会返回 一个promise对象,直到其状态变为resolve或rejected,then回调才会调用,注册到微任务队列,因此then的微任务在下一次的循环中。
微任务有:promise,async await,then,proccess.nextTick。
宏任务:setInterval,setTimeout,setImmidiate
函数式编程
函数式编程区别于命令式编程。函数式编程注重结果,不关心实现的过程。而命令式编程注重过程。
函数式编程一个输入得到一个确切的输出,且该输出可作为下一个的输入。因此,函数式编程可理解为对中间的过程进行细粒度封装,主函数再调用封装函数,最后的结果就是由封装出的各个中间函数组合调用而得到。这种写法可增加代码的可读性。
纯函数:无状态:无论调用多少次,每次的输入对应的输出都相同。数据不可变:不能改变输入参数的值。
柯里化函数可实现纯函数。
柯里化封装
// 柯里化通用方法
webworker
可通过webWorker实现多线程调用。JavaScript主线程任务繁重时,通过webWorker可避免浏览器阻塞。
let worker = new Worker(url); // 将代码的地址给worker,即可开启线程执行相应的代码
worker通过postMessage,onMessage进行通信。不过传递的是值的副本,而不是引用。
es6、es7、es8
1、var、let、const
var存在变量提升,且创建时会为其赋值undefined;let也有变量提升,只是创建时不会为其进行值的初始化(没有将变量绑定在词法环境),因此无法访问。这无法访问的阶段就是“暂时性死区”。
var的变量提升是声明提升,赋值不会提升
var val = 10;
function f() {
console.log(val);
var val = 20;
console.log(val);
this.val = 30;
}
f();
console.log(val);
// 输出依次是undefined、20、30;
// 第一个val打印的是函数内部的val,声明提升了,但是赋值未提升,所以是undefined,this.val改变的是全局环境的val。
const声明的变量必须初始化参数。如果是引用类型,可以修改其属性,只是地址不变。
function函数和var一样,会变量提升。
2、export导出
export {} 导出的是对象的引用,而export default导出的是值。
3、去重方法
let arr = [1, 3, 2, 1, 3, 2, 4, 6];
//1、 使用set
const newArr = Array.from(new Set(arr));
// 2、使用indexOf: 只会返回匹配到的第一个字符的下标
const newArr = arr.filter((i, index) => arr.indexOf(i) === index);
// 3、使用forEach + includes
let newArr = [];
arr.forEach(i => {
if (!newArr.includes(i)) {
newArr.push(i);
}
})
// 4、使用reduce + includes
const newArr = arr.reduce((pre, curr) => pre.includes(curr) ? pre : [...pre, curr], [])
// 5、考虑时间复杂度,使用hash表存储遍历过的值,单次循环搞定
function duplicateRemove(arr) {
let hash = new Map();
let newArr = [];
for(let i of arr) {
if (!hash.has(i)) {
newArr.push(i);
hash.set(i, true);
}
}
return newArr;
}
Promise
关于promise的题:https://juejin.cn/post/6844903509934997511
// 手写promise
// 定义状态
const PENDING = 'pending';
const FULLFILLED = 'fullfilled';
const REJECTED = 'rejected';
class Promise {
// 当我们写promise时,有resolve,reject两个参数。这两个参数是promise内部实现的。
// executor是外部传入的整个函数
constructor(executor) {
this.state = PENDING; // 默认pending状态
this.successCallbacks = []; // pending时,then中执行的回调存储在数组中,等状态resolve再执行
this.failCallbacks = [];
this.value = undefined; // resolve的值
this.reason = undefined; // rejected的值
let resolve = (value) => {
if (this.state === PENDING) {
this.state = FULLFILLED;
this.value = value;
this.successCallbacks.forEach(fn => fn());
}
}
let reject = (value) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
this,failCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject);
} catch(e) {
reject(e);
}
// then方法返回的也是promise
then(onFullFilled, onRejected) {
// 传入的应为函数,如果不是,则返回当前resolve的值
onFullFilled = typeof onFullFilled === 'function' ? onFullFilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => throw reason;
;
let promise = new Promise((resolve, reject) => {
// 规范要求then里的回调必须异步调用
if (this.state === PENDING) {
// 将回调函数存储起来
this.successCallbacks.push(
setTimeout(() => {
try {
const v = onFullFilled(this.value);
// 将结果传入下一个then回调
resolvePromise(promise, v, resolve, reject)
}catch(e){ reject(e) };
})
)
this.failCallbacks.push(
setTimeout(()=> {
try {
const v = onRejected(this.reason);
resolvePromise(promise, v, resolve, reject);
}catch(e){ reject(e) };
})
)
}
if (this.state === FULLFILLED) {
setTimeout(() => {
// 执行onFullFilled,将结果传入下一个回调
}, 0);
}
if(this.state === REJECTED) {
setTimeout(() => {
// 执行onRejected,将结果传入下一个回调
}, 0);
}
})
}
}
function resolvePromise(promise, value, resolve, reject) {
if (promise === value) {
return reject(new Error(“循环引用”));
}
if (value instanceof Promise) {
try{
const v = value.then;
// 保证只执行一次
let isCalled =false;
// 再次执行then回调
then.call(v, y => {
if (isCalled) return;
isCalled = true;
resolvePromise(promise, y, resolve, reject);
}, err => {
if (isCalled) return;
isCalled = true;
reject(err);
}
}catch(e){
reject(e);
}
} else {
resolve(value);
}
}