前端八股文基础:
一、获取所有不重复字符串集合和最长字符串
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
| function getLongestStr(str) { let s = '', arr = [], temp = ''; for (let i = 0; i < str.length; i++) { if (s.includes(str[i])) { temp = s.length > temp.length ? s : temp !arr.includes(s) && arr.push(s) s = '' } s += str[i] } if (s.trim()) { temp = s.length > temp.length ? s : temp !arr.includes(s) && arr.push(s) } return { arr, temp } }
let str = 'abctyerwsfwvsab54786c8dab6cd3457eabcd5ef7856586274255568945' let res = getLongestStr(str) console.log(res)
|
二、发布订阅模式基本模型
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
| var Event = (function() { let list = {}; let listen = function(key, fn) { (list[key] || (list[key] = [])).push(fn); }; let remove = function(key, fn) { let fns = list[key]; if (!fns) return; if (!fn) { fns && (fns.length = 0); } else { for (let i = fns.length - 1; i >= 0; i--) { let _fn = fns[i]; _fn === fn && (fns.splice(i, 1)); } } }; let trigger = function() { let keys = [].shift.call(arguments); let fns = list[keys]; if (!fns || fns.length === 0) return; for (let i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments); } };
return { listen, remove, trigger }; })();
Event.listen("name", function(name) { console.log("name:", name); }); Event.trigger("name", "Tom");
Event.listen("age", function(age) { console.log("age:", age); }); Event.trigger("age", 20);
|
三、深拷贝
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
| function deepClone (obj) { let targetObj = obj.constructor === Array ? [] : {}; for (let keys in obj) { if (obj.hasOwnProperty(keys)) { if (obj[keys] && typeof obj[keys] === "object") { targetObj[keys] = deepClone(obj[keys]); } else { targetObj[keys] = obj[keys]; } } } return targetObj; }
var obj = { a: 123, b: [12, 23, 45, 62], c: "23", d: { a: 23, b: "45" } };
var newObj = deepClone(obj); newObj.a = 678; newObj.b.push("999"); newObj.d.b = true; console.log(obj); console.log(newObj);
|
四、函数节流与防抖
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
| function throttle (fn, wait) { var timer = null; return function() { if (timer) return; var self = this; let args = arguments; timer = setTimeout(function() { fn.apply(self, args); clearTimeout(timer); timer = null; }, wait); }; }
function debounce (fn, wait) { var timer = null; return function() { var self = this; let args = arguments; clearTimeout(timer); timer = setTimeout(function() { fn.apply(self, args); clearTimeout(timer); timer = null; }, wait); }; }
function log (e) { console.log(e.clientX, e.clientY); }
var box = document.getElementById("box");
box.addEventListener('mousemove', throttle(log, 2000))
|
五、微任务派发模拟
1 2 3
| queueMicrotask(() => { console.log(new Date().toLocaleString()) })
|
六、es6新型获取key的方法
Reflect.ownKeys
解决传统方法不能正常获取 Symbol
为Key的问题。
1 2 3 4 5 6 7 8 9 10
| let obj = { a: 'yyds', b: 666, [Symbol('A')]: true, }
let oldKeys = Object.keys(obj) console.log(oldKeys) let newKeys = Reflect.ownKeys(obj) console.log(newKeys)
|
七、取数组中的最大值
1 2 3 4 5
| let arr = [12,3,5,8,95,666,147] console.log(Math.max(...arr)) console.log(Math.max.apply(null, arr)) let max = Function.prototype.apply.call(Math.max, undefined, arr) console.log(max)
|
八、Vuex核心源码简版
- 为什么用混入?use是先执行,而this指向的是vue实例,是在main.js中后创建的,使用混入才能在vue实例的指定周期里拿到store实例并做些事情
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
| let Vue; class Store { constructor(options){ this.state = new Vue({ data:options.state }) this.mutations = options.mutations; this.actions = options.actions; this.commit=this.commit.bind(this); this.dispatch=this.dispatch.bind(this); } getters(type) { return this.state[type] } commit(type,arg){ this.mutations[type](this.state,arg); } dispatch(type,arg){ console.log(this.actions[type]) return this.actions[type](this,arg) } }
function install(_vue){ Vue = _vue; Vue.mixin({ beforeCreate(){ if (this.$options.store) { Vue.prototype.$store=this.$options.store; } } }) }
export default { Store, install }
|
九、Vue-Router核心源码简版
核心API:
- 路由url监听方法:
onhashchange
/ onpopstate
- url修改替换方法:
pushState()
/ replaceState()
- hash修改:
location.replace()
/ location.href = '#gg'
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| let Vue; class VueRouter { constructor(options){ this.$options=options; this.$routerMap={}; this.app = new Vue({ data:{ current:"/" } }); } init(){ this.bindEvent(); this.createRouteMap(); this.initComponent(); } bindEvent(){ window.addEventListener('hashchange',this.onHashchange.bind(this)); } onHashchange(){ this.app.current = window.location.hash.slice(1) || "/"; } createRouteMap(){ this.$options.routes.forEach(route=>{ this.$routerMap[route.path]=route; }) } initComponent(){ Vue.component('router-link',{ props:{ to:String, }, render(h){ return h('a',{attrs:{href:'#'+this.to}},[this.$slots.default]) } }); Vue.component('router-view',{ render:(h)=>{ const Component = this.$routerMap[this.app.current].component; return h(Component) } }); } }
VueRouter.install = function(_vue){ Vue = _vue; Vue.mixin({ beforeCreate(){ if (this.$options.router) { Vue.prototype.$router=this.$options.router; this.$options.router.init(); } } }) } export default VueRouter;
|
十、循环打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var sleep = function (time, i) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(i); }, time); }) };
var start = async function () { for (let i = 0; i < 6; i++) { let result = await sleep(1000, i); console.log(result); } };
start();
|
十一、手写Promise
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
| const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';
function MyPromise(executor) { this.state = PENDING; this.value = null; this.reason = null; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = [];
const resolve = (value) => { if (this.state === PENDING) { this.state = FULFILLED; this.value = value; this.onFulfilledCallbacks.forEach(fun => { fun(); }); } }
const reject = (reason) => { if (this.state === PENDING) { this.state = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach(fun => { fun(); }); } }
try { executor(resolve, reject); } catch (reason) { reject(reason); } }
MyPromise.prototype.then = function (onFulfilled, onRejected) { if (typeof onFulfilled != 'function') { onFulfilled = function (value) { return value; } } if (typeof onRejected != 'function') { onRejected = function (reason) { throw reason; } } const promise2 = new MyPromise((resolve, reject) => { switch (this.state) { case FULFILLED: setTimeout(() => { try { const x = onFulfilled(this.value); resolve(x); } catch (reason) { reject(reason); } }, 0); break; case REJECTED: setTimeout(() => { try { const x = onRejected(this.reason); resolve(x); } catch (reason) { reject(reason); } }, 0); break; case PENDING: this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(this.value); resolve(x); } catch (reason) { reject(reason); } }, 0); }) this.onRejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(this.reason); resolve(x); } catch (reason) { reject(reason); } }, 0); }) break; } }) return promise2; }
MyPromise.prototype.catch = function(onRejected) { return this.then(null, onRejected); };
MyPromise.prototype.finally = function(fn) { return this.then(value => { fn(); return value; }, reason => { fn(); throw reason; }); };
MyPromise.resolve = function(value) { return new MyPromise((resolve, reject) => { resolve(value); }); };
MyPromise.reject = function(reason) { return new MyPromise((resolve, reject) => { reject(reason); }); };
MyPromise.all = function (promises) { return new Promise((resolve, reject) => { if (promises.length === 0) { resolve([]); } else { let result = []; let index = 0; for (let i = 0; i < promises.length; i++) { promises[i].then(data => { result[i] = data; if (++index === promises.length) { resolve(result); } }, err => { reject(err); return; }); } } }); }
MyPromise.race = function (promises) { return new Promise((resolve, reject) => { if (promises.length === 0) { resolve(); } else { let index = 0; for (let i = 0; i < promises.length; i++) { promises[i].then(data => { resolve(data); }, err => { reject(err); return; }); } } }); }
|
十二、JS内存空间
一般情况是基础
数据类型,在栈内存
中维护,引用
数据类型,在堆
内存中维护,栈内存和堆内存没有本质差别,但是栈
内存是从地址高位
开始分配,堆
内存从地址低位
开始分配,这里要结合函数调用栈来一起理解。
栈(stack):基础类型、先进后出,后进先出(类似于子弹夹中的子弹,先安装的,最后发射使用)。基础数据类型都是按值访问,我们可以直接操作保存在变量中的实际值。
堆(heap):引用类型、树状结构(类似书架与书)。引用类型的值都是按引用访问的。从变量对象中获取了该对象的地址引用(或者地址指针),然后再从堆内存中取得我们需要的数据。
队列(queue):先进先出(FIFO)的数据结构(就像排队过安检一样,排在队伍前面的人一定是先过检的人)
十三、JS执行上下文
执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括:
- 全局环境:JavaScript代码运行起来会首先进入该环境
- 函数环境:当函数被调用执行时,会进入当前函数中执行代码
- eval(不建议使用,可忽略)
在一个JavaScript程序中,必定会产生多个执行上下文,在我的上一篇文章中也有提到,JavaScript引擎会以栈的方式来处理它们,这个栈,我们称其为函数调用栈(call stack)。栈底
永远都是全局上下文
,而栈顶
就是当前正在执行的上下文
。
结论:
- 单线程
- 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈
- 函数的执行上下文的个数没有限制
- 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
十四、JS变量对象
变量对象,在新版本中,准确的说法应该是环境记录对象,而环境记录对象,又区分词法环境对象与变量环境对象,词法环境对象用于解析当前上下文中,由 const 声明的标识符引用,变量环境对象,用于解析由 var 声明的标识符引用。
一个执行上下文的生命周期可以分为如下几个阶段:
- 创建阶段: 在这个阶段中,执行上下文会分别创建
变量对象
,确定this指向,以及其他需要的状态。 - 代码执行阶段:创建完成之后,就会开始执行代码,会完成
变量赋值
,以及执行其他代码。 - 销毁阶段:可执行代码执行完毕之后,执行
上下文出栈
,对应的内存空间失去引用,等待被回收。
暂时性死区:let/const声明的变量,仍然会提前被收集到变量对象中,但和var不同的是,let/const定义的变量,不会在这个时候给他赋值undefined。
十五、JS作用域与作用域链
词法环境(Lexical Environments)是一种规范类型,一套约定好的规则,用于根据ECMAScript代码的词法嵌套结构来定义标识符与特定变量和函数的关联。词法环境,其实就是作用域。
作用域是一套规则。而作用域链,则是作用域的具体实现。
作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
作用域链,在函数声明阶段确认。如果要结合 JavaScript 引擎来理解的话,作用域链,就是在代码解析阶段确认的。
十六、JS闭包
闭包是一种特殊的对象。它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。
通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量。
十七、JS中this指向
执行上下文的创建阶段,会分别生成变量对象,建立作用域链,确定this指向。
this的指向,是在函数被调用
的时候确定的。也就是执行上下文被创建
时确定的。
在函数执行过程中,this一旦被确定,就不可更改了。
总结:如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined。非严格模式下自动指向window全局对象。
通过new操作符调用构造函数,会经历以下4个阶段:
- 创建一个新的对象;
- 将构造函数的this指向这个新对象;
- 指向构造函数的代码,为这个对象添加属性,方法等;
- 返回新对象。
十八、JS构造函数、原型与原型链
构造函数模拟
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
| var Person = function(name, age) { this.name = name; this.age = age; this.getName = function() { return this.name; } }
function New(func) {
var res = {}; if (func.prototype !== null) {
res.__proto__ = func.prototype; }
var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
if ((typeof ret === "object" || typeof ret === "function") && ret !== null) { return ret; }
return res; }
var p1 = New(Person, 'tom', 20); console.log(p1.getName());
console.log(p1 instanceof Person);
|
实例 instanceof 原型
new关键字做了什么:
声明一个中间对象;
将该中间对象的原型指向构造函数的原型;
将构造函数的this,指向该中间对象;
返回该中间对象,即返回实例对象。
原型:创建的每一个函数,都可以有一个prototype
属性,该属性指向一个对象。这个对象,就是我们这里说的原型。
每一个new出来的实例,都有一个__proto__
属性,该属性指向构造函数的原型对象,通过这个属性,让实例对象也能够访问原型对象上的方法。因此,当所有的实例都能够通过__proto__
访问到原型对象时,原型对象的方法与属性就变成了共有方法与属性。
每个函数都可以是构造函数,每个对象都可以是原型对象。
构造函数的prototype
与所有实例对象的__proto__
都指向原型对象。而原型对象的constructor
指向构造函数。
每个对象都有 __proto__
属性,但只有函数对象才有 prototype
属性。
所有函数对象__proto__
都指向 Function.prototype
,它是一个空函数(Empty function)。
所有对象的 __proto__
都指向其构造器的 prototype
。
1 2 3 4 5 6 7 8
| function Person() {}
Person.prototype = { constructor: Person, getName: function() {}, getAge: function() {}, sayHello: function() {} }
|
fn是Function对象的实例。而Function的原型对象同时又是Object的实例。这样就构成了一条原型链。原型链的访问,其实跟作用域链有很大的相似之处,他们都是一次单向的查找过程。因此实例对象能够通过原型链,访问到处于原型链上对象的所有属性与方法。
1 2 3 4 5 6 7
| var s = 'abc' s.constructor === String s.__proto__ === String.prototype s.__proto__.constructor === String.prototype.constructor String.prototype.__proto__ === Object.prototype Object.__proto__ === Function.prototype Object.prototype.__proto__ === null
|