1.数据类型
基础类型存储在栈内存,引用类型存储在堆内存
2.数据类型检查
- typeof
基本数据类型null会判断为Object
引用数据类型 Object,除了 function 会判断为 OK 以外,其余都是 ‘object’. - instanceof
instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型; - Object.prototype.toString
对于 Object 对象,直接调用 toString() 就能返回 [object Object];而对于其他对象,则需要通过 call 来调用,才能返回正确的类型信息。
function myInstanceof(left, right) {
// 这里先用typeof来判断基础数据类型,如果是,直接返回false
if(typeof left !== 'object' || left === null) return false;
// getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
while(proto) { //循环往下寻找,直到找到相同的原型对象
if(proto === right.prototype) return true;//找到相同原型对象,返回true
proto = Object.getPrototypeof(proto);
}
return false;
}
// 验证一下自己实现的myInstanceof是否OK
console.log(myInstanceof(new Number(123), Number)); // true
console.log(myInstanceof(123, Number)); // false
全局通用类型判断方法
function getType(obj){
let type = typeof obj;
if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
return type;
}
// 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); // 注意正则中间有个空格
}
3.深浅拷贝
浅拷贝
object.assign、扩展运算符、concat、slice
手写:
const shallowClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = target[prop];
}
}
return cloneTarget;
} else {
return target;
}
}
深拷贝
1.JSON.parse(JSON.stringfy())
缺点:
- 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
- 拷贝 Date 引用类型会变成字符串;
- 无法拷贝不可枚举的属性;
- 无法拷贝对象的原型链;
- 拷贝 RegExp 引用类型会变成空对象;
- 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
- 无法拷贝对象的循环引用,即对象成环 (obj[key] = obj)。
2.手写递归
let deepClone = (target) => {
if (typeof target === 'object' && target !== null) {
let cloneTarget = Array.isArray(target) ? [] : {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone(target[key]);
}
}
return cloneTarget;
} else {
return target;
}
}
缺点:
- 不能拷贝不可遍历属性
- 对Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
- 循环引用没有解决
3.递归进阶版
- 针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法;
- 当参数为 Date、RegExp 类型,则直接生成一个新的实例返回;
- 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链;
- 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏(你可以关注一下 Map 和 weakMap 的关键区别,这里要用 weakMap),作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
let deepClonePlus = (target, hash = new WeakMap()) => {
if (target instanceof Date) return new Date(target);
if (target instanceof RegExp) return new RegExp(target);
if (hash.has(target)) return hash.get(target);
let allDesc = Object.getOwnPropertyDescriptors(target);
let cloneTarget = Object.create(Object.getPrototypeOf(target), allDesc);
hash.set(target, cloneTarget);
for (let key of Reflect.ownKeys(target)) {
cloneTarget[key] = (isComplexDataType(target[key]) && typeof target[key] !== 'function') ? deepClonePlus(target[key], hash) : target[key];
}
return cloneTarget;
}
4. 6种继承方式
5. new、apply、call、bind
function _new(ctor, ...args) {
if(typeof ctor !== 'function'){
throw "ctor must be a function"
}
let obj = {};
obj.__proto__ = ctor.prototype;//访问构造函数原型所在原型链上属性
let res = ctor.apply(obj,[...args]);//访问私有属性并返回值
return res instanceof Object ? res:obj;
}
Function.prototype._call = function(ctx, ...args) {
ctx = ctx || window;
ctx.fn = this;
let res = ctx.fn(...args);
delete ctx.fn;
return res;
}
Function.prototype.apply = function (ctx, args) {
ctx = ctx || window;
ctx.fn = this;
let res = ctx.fn(...args);
delete ctx.fn;
return res;
}
Function.prototype.bind = function (ctx, ...args) {
if (typeof this !== 'function') {
throw new Error("this must be s function");
}
let self = this;
let fbound = function () {
//如果当前函数的this指向的是构造函数中的this 则判定为new 操作
self.apply(this instanceof self ? this : ctx, args.concat(Array.prototype.slice.call(arguments)))
}
fbound.prototype = this.prototype;
return fbound;
}
6.闭包
闭包其实就是一个可以访问其他函数内部变量的函数。即一个定义在函数内部的函数,或者直接说闭包是个内嵌函数也可以。
作用域
指变量能够被访问到的范围:全局作用域、函数作用域、块级作用域
作用域链
当访问一个变量时,代码解释器会首先在当前的作用域查找,如果没找到,就去父级作用域去查找,直到找到该变量或者不存在父级作用域中,这样的链路就是作用域链。
7.手写JSON.stringfy
额,放弃
8.数组API总结
数组构造器
ES5:Array()
ES6:Array.of 、Array.from
数组判断
ES5:
var a = [];
// 1.基于instanceof
a instanceof Array;
// 2.基于constructor
a.constructor === Array;
// 3.基于Object.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(a);
// 4.基于getPrototypeOf
Object.getPrototypeOf(a) === Array.prototype;
// 5.基于Object.prototype.toString
Object.prototype.toString.apply(a) === '[object Array]';
ES6:Array.isArray
基本方法
9.类数组
- 函数里面的参数对象 arguments;
- 用 getElementsByTagName/ClassName/Name 获得的 HTMLCollection;
- 用 querySelector 获得的 NodeList。
callee是arguments的属性
10.数组扁平化
普通递归
function flatten1(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flatten1(arr[i]));
} else {
res.push(arr[i]);
}
}
return res;
}
reduce
function flatten2(arr) {
let res = arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten2(cur) : cur);
}, [])
return res;
}
扩展运算符
function flatten3(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
toString + split
不过都变成了string ,需要转换
function flatten4(arr){
arr = arr.toString().split(",").map(item=>parseInt(item));
return arr;
}
ES6 的 Flat
function flatten5(arr){
return arr.flat(Infinity);
}
JSON + 正则
function flatten6(arr) {
let str = JSON.stringify(arr);
str = str.replace(/(\[|\])/g, '');
str = '[' + str + ']';
return JSON.parse(str);
}
11.数组排序
在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
12.sort
- 当 n<=10 时,采用插入排序;
- 当 n>10 时,采用三路快速排序;
- 10<n <=1000,采用中位数作为哨兵元素;
- n>1000,每隔 200~215 个元素挑出一个元素,放到一个新数组中,然后对它排序,找到中间位置的数,以此作为中位数。
13. 手写数组方法
数组方法 | V8 源码地址 |
---|---|
pop | V8 源码 pop 的实现 |
push |
14.JS异步方案
15. EventEmitter
设计模式:发布订阅模式
扩展:观察者模式、工厂模式、构造函数模式、单例模式
简陋版手写
class EventEmitter {
constructor() {
this._events = {};
}
on(eventName, listener) {
if (!this._events[eventName]) {
this._events[eventName] = [listener];
} else {
this._events[eventName].push(listener);
}
}
emit(eventName, ...args) {
this._events[eventName] && this._events[eventName].forEach(f => {
f.apply(this, args);
});
}
off(eventName, listener) {
if (this._events[eventName]) {
this._events[eventName] = this._events[eventName].filter(f => {
return f !== listener;
})
}
}
once(eventName, listener) {
const fn = (...args) => {
listener.apply(this, args);
this.remove(eventName, fn);
}
this.on(eventName, fn);
}
}
升级版手写
在这里插入代码片
16.Promise
手写Promise
简陋版
无法实现then的链式调用
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function Promise(executor) {
let _this = this;
this.state = PENDING;
this.value = undefined;
this.result = undefined;
this.onFulfilled = [];
this.onRejected = [];
function resolve(value) {
if (_this.state === PENDING) {
_this.state = FULFILLED;
_this.value = value;
_this.onFulfilled.forEach(fn => fn(value));
}
}
function reject(result) {
if (_this.state === PENDING) {
_this.state = REJECTED;
_this.result = result;
_this.onRejected.forEach(fn => fn(result));
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise.prototype.then = function (onFulfilled, onRejected){
if (this.state === FULFILLED) {
typeof onFulfilled === 'function' && onFulfilled(this.value);
}
if (this.state === REJECTED) {
typeof onRejected === 'function' && onRejected(this.result);
}
if(this.state === PENDING){
typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled);
typeof onRejected === 'function' && this.onRejected.push(onRejected)
}
}
var p = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve(4)
}, 1000)
})
p.then((res)=>{
//4 res
console.log(res, 'res')
})
p.then((res1)=>{
//4 res1
console.log(res1, 'res1')
})
升级标准版
略长,最后看吧
function Promise(executor) {
var self = this
self.status = 'pending'
self.onResolvedCallback = []
self.onRejectedCallback = []
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(function () { // 异步执行所有的回调函数
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
for (var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
})
}
function reject(reason) {
setTimeout(function () { // 异步执行所有的回调函数
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
for (var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason)
}
}
})
}
try {
executor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
if (x instanceof Promise) {
if (x.status === 'pending') {
x.then(function (v) {
resolvePromise(promise2, v, resolve, reject)
}, reject)
} else {
x.then(resolve, reject)
}
return
}
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
resolve(x)
}
}
Promise.prototype.then = function (onResolved, onRejected) {
var self = this
var promise2
onResolved = typeof onResolved === 'function' ? onResolved : function (v) {
return v
}
onRejected = typeof onRejected === 'function' ? onRejected : function (r) {
throw r
}
if (self.status === 'resolved') {
return promise2 = new Promise(function (resolve, reject) {
setTimeout(function () { // 异步执行onResolved
try {
var x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
}
if (self.status === 'rejected') {
return promise2 = new Promise(function (resolve, reject) {
setTimeout(function () { // 异步执行onRejected
try {
var x = onRejected(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
}
if (self.status === 'pending') {
// 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
return promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallback.push(function (value) {
try {
var x = onResolved(value)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
self.onRejectedCallback.push(function (reason) {
try {
var x = onRejected(reason)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
})
}
}
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}
手写all、any、race
all
Promise.prototype.myAll = function (array) {
if (!Array.isArray(array)) return new Error("argument must be an array!");
let result = new Array(array.length), count = 0;
return new Promise((resolve, reject) => {
array.forEach((item, index) => {
Promise.resolve(item)
.then(data => {
result[index]=data;
count++;
if (count === array.length) {
resolve(result);
}
}).catch(err => {
reject(err);
})
})
})
}
var promise1 = Promise.resolve(3);
var promise2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
var promise3 = 42;
Promise.prototype.myAll([promise1, promise2, promise3]).then(function (values) {
console.log(values);
});
race
Promise.prototype.myRace = function (array) {
if (!Array.isArray(array)) { return new Error('argument must be a Array') }
return new Promise((resolve, reject) => {
array.forEach(item => {
Promise.resolve(item).then(data => {
resolve(data)
}).catch(err => {
reject(err)
})
})
})
}
var promise1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'one');
});
var promise2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then(function(value) {
console.log(value);
});