日常的笔记总结


前端八股文基础:

一、获取所有不重复字符串集合和最长字符串

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)
/*
* 输出
{
arr: ['abctyerwsf', 'wvsab54786c', '8dab6c', 'd3457eabc', 'd5ef78', '56', '586274', '25', '5', '56894'],
temp: "wvsab54786c"
}
* */

二、发布订阅模式基本模型

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);

/*
* 输出
name: Tom
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))
// 防抖
// box.addEventListener("mousemove", debounce(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) // ['a', 'b']
let newKeys = Reflect.ownKeys(obj)
console.log(newKeys) // ['a', 'b', Symbol(A)]

七、取数组中的最大值

1
2
3
4
5
let arr = [12,3,5,8,95,666,147]
console.log(Math.max(...arr)) // 666
console.log(Math.max.apply(null, arr)) // 666
let max = Function.prototype.apply.call(Math.max, undefined, arr)
console.log(max) // 666

八、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 {
// 持有state,并使其响应化
constructor(options){
this.state = new Vue({
data:options.state
})
this.mutations = options.mutations;// mutations 是对象
this.actions = options.actions;// mutations 是对象
// 绑定this
this.commit=this.commit.bind(this);
this.dispatch=this.dispatch.bind(this);
}
getters(type) {
return this.state[type]
}
// 实现commit和dispatch方法
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:

  1. 路由url监听方法:onhashchange / onpopstate
  2. url修改替换方法:pushState() / replaceState()
  3. 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={};//{"/":{component:...}}
// url 响应式,当值变化时引用的地方都会刷新
this.app = new Vue({
data:{
current:"/"
}
});
}
// 初始化
init(){
// 监听事件
this.bindEvent();
// 解析路由
this.createRouteMap();
// 声明组件
this.initComponent();
}
bindEvent(){
// hash 模式
window.addEventListener('hashchange',this.onHashchange.bind(this));
// history 模式
// window.addEventListener('popstate',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)
}
});
}
}

// 参数是vue构造函数,Vue.use(router)时,执行router的install方法并把Vue作为参数传入
VueRouter.install = function(_vue){
Vue = _vue;
//全局混入
Vue.mixin({
beforeCreate(){//拿到router的示例,挂载到vue的原型上
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内存空间

一般情况是基础数据类型,在栈内存中维护,引用数据类型,在内存中维护,栈内存和堆内存没有本质差别,但是内存是从地址高位开始分配,内存从地址低位开始分配,这里要结合函数调用栈来一起理解。

  1. 栈(stack):基础类型、先进后出,后进先出(类似于子弹夹中的子弹,先安装的,最后发射使用)。基础数据类型都是按值访问,我们可以直接操作保存在变量中的实际值。

  2. 堆(heap):引用类型、树状结构(类似书架与书)。引用类型的值都是按引用访问的。从变量对象中获取了该对象的地址引用(或者地址指针),然后再从堆内存中取得我们需要的数据。

  3. 队列(queue):先进先出(FIFO)的数据结构(就像排队过安检一样,排在队伍前面的人一定是先过检的人)

十三、JS执行上下文

执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括:

  • 全局环境:JavaScript代码运行起来会首先进入该环境
  • 函数环境:当函数被调用执行时,会进入当前函数中执行代码
  • eval(不建议使用,可忽略)

在一个JavaScript程序中,必定会产生多个执行上下文,在我的上一篇文章中也有提到,JavaScript引擎会以栈的方式来处理它们,这个栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文

结论:

  1. 单线程
  2. 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
  3. 全局上下文只有唯一的一个,它在浏览器关闭时出栈
  4. 函数的执行上下文的个数没有限制
  5. 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

十四、JS变量对象

变量对象,在新版本中,准确的说法应该是环境记录对象,而环境记录对象,又区分词法环境对象与变量环境对象,词法环境对象用于解析当前上下文中,由 const 声明的标识符引用,变量环境对象,用于解析由 var 声明的标识符引用。

一个执行上下文的生命周期可以分为如下几个阶段:

  1. 创建阶段: 在这个阶段中,执行上下文会分别创建变量对象,确定this指向,以及其他需要的状态。
  2. 代码执行阶段:创建完成之后,就会开始执行代码,会完成变量赋值,以及执行其他代码。
  3. 销毁阶段:可执行代码执行完毕之后,执行上下文出栈,对应的内存空间失去引用,等待被回收。

暂时性死区: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;
}

// ret为构造函数执行的结果,这里通过apply,将构造函数内部的this指向修改为指向res,即为实例对象
var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));

// 当我们在构造函数中明确指定了返回对象时,那么new的执行结果就是该返回对象
if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
return ret;
}

// 如果没有明确指定返回对象,则默认返回res,这个res就是实例对象
return res;
}

// 通过new声明创建实例,这里的p1,实际接收的正是new中返回的res
var p1 = New(Person, 'tom', 20);
console.log(p1.getName());

// 当然,这里也可以判断出实例的类型了
console.log(p1 instanceof Person); // true

实例 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 // true
s.__proto__ === String.prototype // true
s.__proto__.constructor === String.prototype.constructor // true
String.prototype.__proto__ === Object.prototype // true
Object.__proto__ === Function.prototype // true
Object.prototype.__proto__ === null // true

十九、一行命令生成所有平台需要的图标

1
pnpm tauri icon <your-logo-path>

二十、使用 electron-vite 快速创建一个 electron 应用

1
pnpm create @quick-start/electron
欢迎留言,提问 ^_^
个人邮箱: tw.email@qq.com
notification icon
博客有更新,将会发送通知给您!