写此篇的目的主要是记录平时的学习笔记,方便随时查阅。
发布订阅和观察者模式
let eventObj = {
listeners: [],
// 订阅
subscribe: function (fn) {
this.listeners.push(fn);
// 取消订阅
return () => {
let index = this.listeners.indexOf(fn);
this.listeners.splice(index, 1);
};
},
// 发布
emit: function () {
this.listeners.forEach((fn) => {
fn();
});
},
};
let unsubscribe = eventObj.subscribe(() => {
console.log("update1");
});
unsubscribe();
eventObj.subscribe(() => {
console.log("update2");
});
eventObj.emit();
// 观察者
class Observer {
constructor(type) {
this.type = type;
}
update(o) {
console.log(this.type + "监听被观察者状态变化", o.update);
}
}
// 被观察者
class Subject {
constructor(type) {
// 观察集合
this.observers = [];
this.type = type;
}
// 收集观察者
attach(o) {
this.observers.push(o);
}
setState(update) {
console.log("update", update);
this.update = update;
this.observers.forEach((o) => {
o.update(this);
});
}
}
let baby = new Subject("baby");
let o1 = new Observer("爸爸");
let o2 = new Observer("妈妈");
baby.attach(o1);
baby.attach(o2);
baby.setState("我在玩呢");
发布订阅模式升级版
//题目描述:实现一个发布订阅模式拥有 on emit once off 方法
class EventEmitter {
constructor() {
this.events = {}
}
// 订阅
on(type, callback) {
if (!this.events[type]) {
this.events[type] = [callback]
} else {
this.events[type].push(callback)
}
}
// 取消订阅
off(type, callback) {
if (this.events[type]) {
this.events[type] = this.events[type].filter((fn) => fn !== callback);
}
}
// 只执行一次订阅事件,触发一次后,就会取消订阅
once(type, callback) {
function fn(args) {
callback(args);
this.off(type, fn);// 这里是删除订阅 fn函数
}
this.on(type, fn)
}
// 发布(事件触发)
emit(type, ...rest) {
if (this.events[type]) {
this.events[type].forEach((fn) => {
fn.call(this, ...rest)
})
}
}
}
let event = new EventEmitter()
const handle1 = (arg) => {
console.log('click', arg);
}
const handle2 = (arg) => {
console.log('change', arg);
}
const handle3 = (arg) => {
console.log('mousemove', arg);
}
// event.on('click', handle1);
// event.on('change', handle2);
// event.emit('click', 'haha')
// event.off('click', handle1);
// event.emit('click', 'haha')
event.once('mousemove', handle3);
event.emit('mousemove', '触发once了111');
单例模式
// 方法1:
class SingleFn{
static _instance = null
static getInstance(){
if(!SingleFn._instance){
SingleFn._instance = new SingleFn();
}
return SingleFn._instance;
}
constructor(name,age){
this.type = '单例模式'
this.name = name;
this.age = age;
}
say(){
return 'hello'
}
}
let u1 = SingleFn.getInstance();
let u2 = SingleFn.getInstance();
console.log('u1===u2',u1 === u2); // true
// 方法2: 可传入 一个构造函数扩展 + 闭包
function SingleFn(con){
// 排除非函数与箭头函数,箭头函数的prototype是undefined
if(!(con instanceof Function)|| !(con.prototype)){
throw Error('请出入合法的构造函数')
}
let instance; // 闭包存入instance实例
return function(){
if(!instance){
instance = new con();
}
return instance;
}
}
function Person(){
this.name = '单例'
}
let Single = SingleFn(Person);
let u1 = new Single();
let u2 = new Single();
let u3 = Single();
let u4 = Single();
console.log('u1===u2',u1 === u2);// true
console.log('u3===u4',u3 === u4);// true
// 方法2: constructor函数里判断单例,返回实例
class Single{
static _instance;
constructor(){
if(!Single._instance){
Single._instance = this;
}
return Single._instance;
}
}
let u1 = new Single();
let u2 = new Single();
console.log('u1===u2',u1===u2);// true
// 方法4:使用包装对象结合闭包的形式实现
const Single = (function(){
function user(){
this.name = '单例'
}
return function(){
if(!user._instance){
user._instance = new user()
}
return user._instance;
}
})();
let u1 = new Single();
let u2 = new Single();
console.log('u1===u2',u1 === u2);// true
节流防抖
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>节流防抖</title>
<style>
#content {
height: 150px;
line-height: 150px;
text-align: center;
color: #fff;
background-color: #ccc;
font-size: 80px;
}
</style>
</head>
<body>
<!-- https://juejin.im/post/6844903651278848014 -->
<h1>节流防抖(https://juejin.im/post/6844903651278848014)</h1>
<div id="content"></div>
<script>
let num = 1;
const content = document.getElementById("content");
function count() {
content.innerHTML = num++;
}
content.onmousemove = throttle(count, 1000, 1);
// immediate=false 非立即执行,immediate=true 立即执行;
// 防抖-debounce-原理:通过clearTimeout不断清除timeId,终止定时器执行,从而达到鼠标一直滑动会一直清除timeId,定时器函数就不会执行
function debounce(fn, wait, immediate) {
let timeId;
return function () {
const context = this;
const args = arguments;
// clearTimeout方法会终止 定时器执行
// 所以在一直滑动时,timeId不断的被赋值,又不断的被清除,终止定时器的执行,知道停下来等wait时间段才会执行
if (timeId) clearTimeout(timeId);
// setTimeout是宏任务会放到任务队列里,会等当前宏任务栈里执行完了,在执行任务队列里的setTimeout回调函数
if (!immediate) {
timeId = setTimeout(() => {
fn.call(context, args);
}, wait);
} else {
let callNow = !timeId;
timeId = setTimeout(() => {
timeId = null;
}, wait);
if (callNow) fn.call(this, args);
}
};
}
// 节流-throttle-不清除定时器Id,所以不管鼠标是否一直在动,超过wait时间就会执行,
function throttle(fn, wait, type) {
if (type === 1) {
let previous = 0;
} else if (type === 2) {
let timeout = null;
}
return function () {
const context = this;
const args = arguments;
if (type === 1) {
// 时间戳版
let now = Date.now();
if (now - previous > wait) {
fn.call(this, args);
previous = now;
}
} else if (type === 2) {
// 定时器版
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
fn.call(this, args);
}, wait);
}
}
};
}
</script>
</body>
</html>
节流高级篇
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>截流防抖</title>
</head>
<body>
<div>
<h1>防抖截流</h1>
<div style="width:100%;height:100%;border:1px solid #000;">
<div id="content" style="width:100%;height:200px;font-size: 20px;"></div>
</div>
<button id="button">点击</button>
</div>
<script>
/**
leading 第一次是否立即执行,默认true,会立即执行一次,leading为false,第一次不会立即执行
trailing 最后一次点击后是否执行 默认true,最后会执行一次
*/
// 截流
function throttleFn(func, wait, options = { leading: true, trailing: true }) {
let timeout, args, context
let previous = 0
const later = function () {
previous = options.leading === false ? 0 : Date.now()
func.apply(context, args)
timeout = null
}
const throttled = function() {
console.log('点击');
context = this
args = arguments
console.log('context',context);
const now = Date.now()
// leading=false,表示禁用第一次立即执行
if (!previous && options.leading === false) previous = now
const remaining = wait - (now - previous)
// 点击一下停顿wait时间以上 -> remaining <= 0 ,第一次点击 或者 大于或等于触发事件的间隔时间(wait),事件达到触发时机了
if (remaining <= 0) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
func.apply(context, args)
previous = now // 每次记录上次点击的时间戳
//快速连续点击-> remaining > 0,最后一次点击 或者 小于触发事件的间隔时间(wait)
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining)
}
}
throttled.cancel = function () {
clearTimeout(timeout)
timeout = null
previous = 0
}
return throttled
}
let count = 0;
function handleUpdate(event) {
console.log('event',event);
document.getElementById('content').innerText = count++;
}
button.addEventListener('click', throttleFn(handleUpdate, 2000, { leading: true, trailing: true }))
</script>
</body>
</html>
科里化函数
function add(num1, num2, num3, num4) {
return num1 + num2 + num3 + num4;
}
//1.科里化函数
function handleCury(fn, arr = []) {
function curying(...args) {
let context = this;
let len = fn.length;
arr.push(...args);
console.log("arr", arr);
if (arr.length < len) {
return handleCury(fn, arr);
} else {
return fn.call(context, ...arr);
}
}
return curying;
}
//2.科里化函数
function add(num1, num2, num3, num4) {
return num1 + num2 + num3 + num4;
}
function handleCury2(fn) {
let len = fn.length;
function curying(...args) {
let context = this;
if (args.length < len) {
return function () {
return curying.apply(null, args.concat([].slice.call(arguments)));
};
} else {
return fn.call(context, ...args);
}
}
return curying;
}
let cury = handleCury2(add);
console.log(cury(2)(3, 5)(6));
3.科里化 判断数据类型
function isType1(type) { //Array object string
return function (value) {
return Object.prototype.toString.call(value) === `[object ${type}]`;
}
}
const isType = (type) => (value) => Object.prototype.toString.call(value) === `[object ${type}]`;
const isArray = isType('Array');
const isObject = isType('Object');
4.判断数据类型
let con1 = [].constructor; // Array
let con2 = {}.constructor; // Object
const fn = function(){};
let con3 = fn.constructor; // Function
console.log('con1-con2-con3',{con1,con2,con3});
console.log([] instanceof Array); // true
快速排序
//快速排序
/**
1.先从数列中取出一个数作为基准数
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边
3.再对左右区间重复第二步,直到各区间只有一个数
*/
function qiuckSort(arr) {
// 递归,
// 一直找中间值,然后左右划分,直到划分成 数组长度小于2(数组长度是1)为止;
if (arr.length < 2) return arr;
let middleIndex = Math.floor(arr.length / 2);
let left = [];
let right = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < arr[middleIndex] && i !== middleIndex) {
//排除中间值
left.push(arr[i]);
}
if (arr[i] > arr[middleIndex] && i !== middleIndex) {
right.push(arr[i]);
}
}
//左中右-->小中大的顺序
return [...qiuckSort(left), arr[middleIndex], ...qiuckSort(right)];
}
function qiuckSort2(arr) {
// 递归,
// 一直找中间值,然后左右划分,直到划分成 数组长度小于2(数组长度是1)为止;
if (arr.length < 2) return arr;
let middleIndex = Math.floor(arr.length / 2);
let middleValue = arr.splice(middleIndex, 1);
let left = [];
let right = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] <= middleValue) {
//排除中间值
left.push(arr[i]);
}
if (arr[i] > middleValue) {
right.push(arr[i]);
}
}
//左中右--》小中大的顺序
return qiuckSort2(left).concat(middleValue, ...qiuckSort2(right));
}
let arr = [2, 4, 1, 5, 7, 8];
console.log(qiuckSort2(arr));
冒泡排序
方法一
// 冒泡排序:双重循环 i和j都从0开始
function sort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
let temp;
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
let arr = [3, 11, 6, 7, 21, 4, 9, 12, 56];
console.log(sort(arr));
// 0<=i<4;
// i=0;0<=j<4; 1,3,6,2,7
// i=1;0<=j<3; 1,3,2,6,7
// i=2;0<=j<2; 1,2,3,6,7
// i=3;0<=j<1; 1,2,3,6,7
选择排序
// 方法二 减少了跟i自身的比较,效率更快
/** 选择排序/打擂台法:
规律:通过比较首先选出最小的数放在第一个位置上,然后在其余的数中选出次小数放在第二个位置上,依此类推,直到所有的数成为有序序列。
*/
function BubbleSort2(arr){
//比较的轮数
for(let i=0;i<arr.length-1;i++){
//每轮比较的次数
for(let j=i+1;j<arr.length;j++){
if(arr[i]>arr[j]){
let temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
请实现 DOM2JSON 一个函数,可以把一个 DOM 节点输出 JSON 的格式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>请实现 DOM2JSON 一个函数,可以把一个 DOM 节点输出 JSON 的格式</title>
</head>
<body>
<h3>DOM-to-JSON</h3>
<div id='root'>
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
<script>
/*
把上面dom结构转成下面的JSON格式
{
tag: 'DIV',
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
}
*/
let rootNode = document.getElementById('root');
function domToJson(rootNode) {
let obj = {};
obj.tag = rootNode.tagName;
obj.children = [];
rootNode.childNodes.forEach(child => {
if(child.tagName){
obj.children.push(domToJson(child));
}
});
return obj;
}
console.log(JSON.stringify(domToJson(rootNode),null,2));;
</script>
</body>
</html>
比较两个版本号的大小
/*
* @Description: 比较两个版本号的大小
* 题目描述:有一组版本号如下 v1 = '0.1.1' 和 v2= '2.3.3'。
*/
function compareVersion(v1, v2) {
const len1 = v1.split('.')
const len2 = v2.split('.')
const maxLen = Math.max(len1.length, len2.length)
if (len1.length < maxLen) {
len1.push('0')
}
if (len2.length < maxLen) {
len2.push('0')
}
for (let i = 0; i < maxLen; i++) {
const s1 = len1[i]
const s2 = len2[i]
if (s1 > s2) {
return 1;
} else if (s1 < s2) {
return -1;
}
}
return 0;
}
console.log('compareVersion',compareVersion('0.1.1.1','2.3.3')); //-1
比较版本号排序
/*
* @Description: 写版本号排序的方法
* 题目描述:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。
* 现在需要对其进行排序,排序的结果为 [ '0.1.1', '0.302.1', '2.3.3', '4.2', '4.3.4.5', '4.3.5' ]
*/
let arr = [2, 5, 7, 3];
arr.sort((a, b) => {
return a - b; // return a-b 升序; return b-a 降序
})
console.log('arr', arr);// arr [ 2, 3, 5, 7 ]
// 比较版本号
const arr2 = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'];
arr2.sort((a, b) => {
let i = 0;
const arr1 = a.split('.');
const arr2 = b.split('.');
while (true) {
const s1 = arr1[i];
const s2 = arr2[i];
i++
if(s1===undefined || s2 === undefined){
return s1.length - s2.length;
}
if (s1 === s2) continue;
return s1 - s2;
}
});
console.log(arr2);// [ '0.1.1', '0.302.1', '2.3.3', '4.2', '4.3.4.5', '4.3.5' ]
二分查找
function binarySearch(arr, target) {
if (!Array.isArray(arr)) return;
arr.sort((a, b) => a - b);// 排序
let start = 0;
let end = arr.length - 1;
while (start <= end) {
let midIndex = Math.floor((start + end) / 2);
let mid = arr[midIndex];
if (mid === target) {
// return mid;
return midIndex;
} else if (mid < target) {
start = mid + 1;
} else {
end = mid - 1;
}
}
return -1;
}
let arr = [2, 4, 1, 7, 8, , 9]; // [1, 2, 4, 7, 8, 9]
let target = 7;
console.log(binarySearch(arr, target));
实现call,apply,bind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
//1.call
function fn(...str) {
console.log("this", this);
console.log("str", str);
}
Function.prototype.callFn = function (context, ...args) {
let fn = this;
context = Object(context) || window;
let key = Symbol(); //随便定义一个key
context[key] = fn; // context={ key:fn };此时fn内部的this就是context了
let result = context[key](...args); //context.key 谁调用this就是谁,
delete context[key]; // 给上下文添加了 key属性,为了不污染 上下文context,调用后删掉key属性
return result;
};
// console.log(fn.callFn(null, "hello", "world"));
// let obj = {
// key: function () {
// console.log("this就是obj对象", this);// obj
// },
// fn: () => {
// console.log("如果是箭头函数,this就是window对象", this);// window
// },
// };
// console.log(obj.key());
// 2.apply
Function.prototype.applyFn = function (context, args) {
let fn = this;
context = Object(context) || window; //上下文必须是 object对象
let key = Symbol();
context[key] = fn;
let result = context[key](...args);
delete context[key];
return result;
};
// console.log(fn.applyFn(null, ["hello", "world"]));
// 3.bind
Function.prototype.myBind = function (context, args) {
let fn = this; // 这个this是myBind函数的实例->Animal
args = args ? args : [];
context = Object(context) || window;
function fn1() {}
fn1.prototype = fn.prototype;
return function newFn(...newArgs) {
let _this = this; // 这个this是newFn函数实例->instance
let newContext = _this instanceof newFn ? _this : context;
let result = fn.apply(newContext, [args, ...newArgs]);
return result;
};
newFn.prototype = new fn1();
};
function Animal(name, age) {
this.name = name;
this.age = age;
console.log("Animal内部的this-3", this);
// return {list:[],data:{name:'Mary'}}
}
let obj = { score: 0, id: 1 };
Animal.prototype.flag = "哺乳类";
let bindFn = Animal.myBind(obj, "lc");
let instance = new bindFn(222);
console.log("instance", instance);//{name: "lc", age: 222}
</script>
</body>
</html>
实现 Object.create()
//4.实现 Object.create()
Object.create = function (obj) {
function fn() {}
fn.prototype = obj;
return new fn();
};
Object.create = function (obj) {
let o = {};
o.__proto__ = obj;
return o;
};
Object.create = function (obj) {
let o = {};
Object.setPrototypeOf(o, obj);
return o;
};
模拟new
function Animal(type, name) {
this.type = type;
this.name = name;
}
// let cat = new Animal("animal", "cat");
Animal.prototype.say = function () {
console.log("say");
};
// let instance = new Animal("animal", "cat");
// console.log("instance", instance);
// new的核心原理:
// 1.let obj={},创建新对象obj;
// 2.obj.__proto__=constructor.prototype,obj的原型对象继承构造函数的原型对象,
// 3.constructor.call(obj,...args),obj调用构造函数,使得构造函数里面的this指向obj,obj继承了构造函数的私有属性
// 4.let result=constructor.call(obj,...args),判断result返回值的类型,如果result是引用类型就返回result,如果是基本数据类型还是会返回obj,默认也是返回obj(this)
function mockNew() {
let constructor = [].shift.call(arguments);
let args = arguments;
//创建新的空对象
let obj = {};
//obj继承构造函数的原型对象
obj.__proto__ = constructor.prototype;
//执行构造函数 并且将obj调用构造函数,指向构造函数内部的this
//obj既拥有构造函数的私有属性又继承了构造函数的原型属性
let result = constructor.call(obj, ...args);
//对构造函数的返回值做判断,如果构造函数返回的是引用类型就返回该引用值,如果不是引用,会默认返回this(实例对象)
return typeof result === "object" ? result : obj;
}
let instance = mockNew(Animal, "animal", "cat");
console.log("返回实例", instance);
数组扁平化
//1.递归扁平化
function handleFlat(arr) {
if (!Array.isArray(arr)) {
throw Error("need array");
}
let newArr = [];
function flat(arr) {
arr.forEach((item) => {
if (Array.isArray(item)) {
flat(item);
} else {
newArr.push(item);
}
});
}
flat(arr);
return newArr;
}
let arr = [12, [35, 5], [56, 23, [67, 9]]];
// console.log(handleFlat(arr));
// 2.reduce
function flat(arr, init = []) {
return arr.reduce((pre, cur) => {
if (Array.isArray(cur)) {
flat(cur, pre);
} else {
pre.push(cur);
}
return pre;
}, init);
}
// reduce
function flat2(arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat2(cur) : cur);
}, []);
}
let arr = [12, [35, 5], [56, 23, [67, 9]]];
console.log(flat2(arr));
// 3.迭代
function flaten(arr){
while((arr.some((item)=>Array.isArray(item)))){
arr = [].concat(...arr);
}
return arr;
}
let arr = [[1, 2, 3], [4, 5, [6]], [7]];
console.log(flaten(arr));
let arr = [[1, 2, 3], [4, 5, [6]], [7]];
console.log('...arr',...arr);// 扁平最外层 [1,2,3] [4,5,[6]] [7]
arr = [].concat([1,2,3],[4,5,[6]],[7]);
console.log('arr',arr); // [1,2,3,4,5,[6],7]
//4.数组 栈的思想
function flatStack(arr) {
let result = [];
let stack = [];
stack = arr.slice();
while (stack.length !== 0) {
let val = stack.pop();
if (Array.isArray(val)) {
stack.push(...val); //分解数组层级,此时stack.length!==0,继续循环
} else {
result.unshift(val);
}
}
return result;
}
let arr = [12, [35, 5], [56, 23, [67, 9]]];
console.log(flatStack(arr));
compose函数(koa中间件,redux)
//1.compose 洋葱模型 中间件koa,express,react-redux用到
async function add(ctx, next) {
console.log("1");
await next();
console.log("2");
}
async function minus(ctx, next) {
console.log("3");
await next();
console.log("4");
}
async function say(ctx, next) {
console.log("5");
await next();
console.log("6");
}
let obj = {
ctx: { a: 1, b: 2, c: 3, d: 4 },
middles: [],
use: function (fn) {
this.middles.push(fn);
},
compose: function (ctx) {
return function () {
let middles = this.middles;
let index = -1;
function dispatch(i) {
if (index === i) return Promise.reject(`next called multiples`);
if (i === middles.length) return Promise.resolve();
index = i;
let middleware = middles[i];
return Promise.resolve(middleware(ctx, () => dispatch(i + 1)));
}
return dispatch(0);
};
},
listen: function () {
const composeFn = this.compose(this.ctx);
composeFn().then(() => {
console.log("listen");
});
},
};
//订阅中间件
obj.use(add);
obj.use(minus);
obj.use(say);
//执行所有中间件
obj.listen();
//koa compose+异步迭代用函数递归
async function a(ctx, next) {
console.log("1");
await next();
console.log("2");
}
async function b(ctx, next) {
console.log("3");
await next();
console.log("4");
}
async function c(ctx, next) {
console.log("5");
await next();
console.log("6");
}
function compose(middles) {
let index = -1;
return function (ctx) {
function dispatch(i) {
if (index === i) return Promise.reject(`next called multiples`);
if (i === middles.length) return Promise.resolve();
index = i;
let middleware = middles[i];
return Promise.resolve(middleware(ctx, () => dispatch(i + 1)));
}
return dispatch(0);
};
}
let composeFn = compose([a, b, c]);
let ctx = {};
composeFn(ctx).then(() => {
console.log("composeFn");
});
//compose +reducer
// async function a(ctx, next) {
// console.log("1");
// await async function b(ctx, next) {
// console.log("3");
// await async function c(ctx, next) {
// console.log("5");
// console.log("6");
// };
// console.log("4");
// };
// console.log("2");
// }
// 2.react-redux compose+reduce
function a1(str1) {
console.log("1");
return "a1" + str1;
}
function b1(str2) {
console.log("2");
return "b1" + str2;
}
function c1(str3) {
console.log("3");
return "c1" + str3;
}
function composeReducer(composes) {
return composes.reduce((pre, cur) => {
return function (...args) {
return pre(cur(...args));
};
});
}
let str = composeReducer([a1, b1, c1])("hello");
console.log(str);
// 3.compose+reduceRight
function a2(str1) {
console.log("1");
return "a2" + str1;
}
function b2(str2) {
console.log("2");
return "b2" + str2;
}
function c2(str3) {
console.log("3");
return "c2" + str3;
}
function composeRightReducer(composes) {
let lastMiddle = composes.pop();
return function (...args) {
return composes.reduceRight((pre, cur) => {
return cur(pre);
}, lastMiddle(...args));
};
}
let str = composeRightReducer([a2, b2, c2])("hello");
console.log(str);
实现reduce函数
//实现reduce函数
Array.prototype.reduceFn = function (callback, pre) {
for (let i = 0; i < this.length; i++) {
if (typeof pre === "undefined") {
pre = callback(this[i], this[i + 1], i + 1, this);
i++;
} else {
pre = callback(pre, this[i], i, this);
}
}
return pre;
};
let arr = [1, 3, 5];
let total = arr.reduceFn((pre, cur, index, arr) => {
return pre + cur;
}, 1);
console.log("total", total);
快照沙箱
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
//快照沙箱:激活状态和失活状态
// 1)一年前给 window拍照记录
// 2)一年后(现在)又给window拍照
// 3)失活状态时,将一年前保存的window和现在的window做对比,将新增不同的属性保存起来;还原到一年前的window
// 4)再次激活时,恢复到上次保存的window状态
class SnapShotSandBox {
constructor() {
this.proxy = window;
this.modifyPropsMap = {};
this.snapShotWindow = {};
this.active();
}
active() {
for (let p in window) {
//初始化时的window
if (window.hasOwnProperty(p)) {
this.snapShotWindow[p] = window[p]; //拍照
}
}
Object.keys(this.modifyPropsMap).forEach((p) => {
window[p] = this.modifyPropsMap[p];
});
}
inactive() {
//现在最新的window
for (let p in window) {
if (window.hasOwnProperty(p)) {
if (window[p] !== this.snapShotWindow[p]) {
this.modifyPropsMap[p] = window[p]; //将现在window新增的属性保存起来
window[p] = this.snapShotWindow[p]; //还原到初始化前window的状态
}
}
}
}
}
let sandBox = new SnapShotSandBox(); // 1.初始化激活状态,记录了window
(function (window) {
window.name = "aa";
window.age = 2;
console.log(window.name, window.age); //aa 2 //此时是最新的window,添加了a,b属性
sandBox.inactive(); // 失活状态
console.log(window.name, window.age); //undefined undefined //2.失活状态,先记录新增修改的属性,再还原到之前的window状态
sandBox.active(); //3.再次激活,还原之前新添加了a,b属性的状态
console.log(window.name, window.age); //aa 2
})(sandBox.proxy);
//2.Proxy代理沙箱
class ProxySandbox {
constructor() {
const rawWindow = window;
const fakeWindow = {};
const proxy = new Proxy(fakeWindow, {
set(target, p, value) {
target[p] = value;
return true;
},
get(target, p) {
return target[p] || rawWindow[p];
},
});
this.proxy = proxy;
}
}
let sandbox1 = new ProxySandbox();
let sandbox2 = new ProxySandbox();
window.a = 1;
((window) => {
window.a = "hello";
window.b = "111";
console.log("window.b", window.b);
console.log("window.a", window.a);
})(sandbox1.proxy);
((window) => {
window.a = "world";
console.log("window.b", window.b);
console.log("window.a", window.a);
})(sandbox2.proxy);
</script>
</body>
</html>
对象扁平化
// 原始数据
var entryObj = {
a: {
b: {
c: {
dd: 'abcdd'
}
},
d: {
xx: 'adxx'
},
e: 'ae'
}
};
// 要求的结果
// let obj = {
// "a.b.c.dd":'abcdd',
// "a.d.xx":'adxx',
// "a.e":'ae'
// }
var obj = {
"obj1": {
"obj11": 1,
"obj12": 2
},
"obj2": {
"obj21": 3,
"obj22": {
"obj33": 4,
},
}
};
//使用 Object.keys() + 递归
function flattenObj(obj, prekey = '', resobj = {}) {
prekey = prekey ? prekey + '.' : '';
Object.keys(obj).forEach((item) => {
let temp = obj[item];
if (temp && typeof temp === 'object') {
flattenObj(temp, prekey + item, resobj);
} else {
resobj[`${prekey}${item}`] = temp;
}
})
return resobj
}
// 使用 Object.entries() + 递归
function flat(obj, key = "", res = {}) {
for (let [k, v] of Object.entries(obj)) {
if (typeof v === "object") {
let tmp = key + k + "."
flat(v, tmp, res)
} else {
let tmp = key + k
res[tmp] = v
}
}
return res
}
let result = flattenObj(entryObj);
console.log(result);
// 原始数据
var entryObj = {
a: {
b: {
c: {
dd: 'abcdd'
}
},
d: {
xx: 'adxx'
},
e: 'ae'
}
};
let entries = Object.entries(entryObj);
console.log('entries',entries); // [ [ 'a', { b: [Object], d: [Object], e: 'ae' } ] ]
for(let [k,v] of entries){
console.log('[k,v]',[k,v])
}
计算n以内的质数
// n = 7 2,3,5,7
function countPrimes(n) {
let arr = [];
for (let i = 2; i <= n; i++) {
if (isPrimes(i)) {
arr.push(i);
}
}
return arr;
}
// 判断是否是质数,被1和本身整除的数才是质数
function isPrimes(num) {
let isPrimes = true;
// 这里排除了 1和本身
for (let i = 2; i < num; i++) {
// 如果 [2,n-1] 之间,有一个数能整除,说明就不是质数
if (num % i === 0) {
isPrimes = false;
break;
}
}
return isPrimes;
}
console.log('countPrimes(20)', countPrimes(20));
vue模板编译原理 new Function + with()语法
let obj = { age: 11, name: 'hello' }
let code = 'return name + age'
let str = `with(this) ${code}`; // this就是传进来的obj
function compileFn() {
let fn = new Function(str);
let res = fn.call(obj) // fn里的this就是obj
return res;
}
let result = compileFn();
console.log('result', result); // 'hello11'
深拷贝
let obj = {
name: 'Colin',
age: 20,
intresting: {
name: 'eating',
title: '吃饭'
},
list: [
{ id: 1, title: 'hello' },
{ id: 2, title: 'world' },
{
id: 3,
title: '对象',
hobby: {
name: 'play',
id: 4,
title: '玩'
}
}
]
}
let arr = ['name', 'age', { id: 1, title: 'haha', hobby: { name: '旅游' } }];
function isObject(obj) {
return typeof obj === 'object';
}
/**
* WeakMap:
* 1.只接受对象作为键名(null除外),不接受其他类型的值作为键名,它的key的引用是弱引用
* 2.一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。
*/
function deepClone(obj, hash = new WeakMap()) {
if (typeof obj !== 'object' || obj === null) return obj; // 判断是否是 object,这里和第47行 判断只需要其一就行了
let Constructor = obj.constructor;
let result;
switch (Constructor) {
case RegExp:
result = new Constructor(obj);
return result;
case Date:
result = new Constructor(obj.getTime());
return result;
default:
break;
}
if (hash.has(obj)){
return hash.get(obj);
}
result = new Constructor(); // 引用
hash.set(obj, result);
for (let key in obj) {
// hasOwnProperty只枚举对象自身的属性,不会遍历原型上的属性
if (obj.hasOwnProperty(key)) {
// result[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key]; // 判断是否是 object
result[key] = deepClone(obj[key], hash); // result引用赋值
}
}
return result;
}
obj.obj = obj; // 循环引用
let o1 = deepClone(obj);
o1.list[0].title = 'lc';
console.log('deepClone-o1', o1);
console.log('obj', obj);
树形结构扁平化转成一维数组
let data = [
{
id:1,
value:'a',
children:[
{
id:11,
value:'a-a',
children:[
{
id:111,
value:'a-a-a',
children:[]
},
{
id:112,
value:'a-a-b',
children:[]
}
]
},
{
id:12,
value:'a-b',
children:[]
}
]
},
{
id:2,
value:'b-b',
children:[]
}
]
function treeToList (data) {
let res = [];
const dfs = tree => {
tree.forEach(item => {
if(item.children) {
dfs(item.children)
delete item.children
}
res.push(item)
})
}
dfs(data);
return res
}
/**
* 将树形结构转化成一维数组,并给数组每项标记 level层级字段
* @param {*} data 数组
* @param {*} level 层级,最外层是0,越往内层,依次加1
*/
function treeToList2(data) {
const arr = [];
const flat = (data, level = 0) => {
data.forEach((item) => {
if (item.children.length) {
flat(item.children, level + 1);// 这里仅仅是level+1,并没有将level+1后的值赋值给level
}
arr.push({ ...item, level, children: [] });
})
}
flat(data)
return arr;
}
console.log('treeToList2', JSON.stringify(treeToList2(data), null, 2));
数组等份截取
const list = [{id:1},{id:2},{id:3},{id:4},{id:5},{id:6},{id:7}];
function handleSlice(list, num) {
// let index = 0;
const arr = [];
for (let i = 0; i < list.length; i += num) {
arr.push(list.slice(i, i + num))
}
// while (index<list.length){
// arr.push(list.slice(index,index+=num))
// }
return arr;
}
let newList = handleSlice(list, 5);
console.log('newList', JSON.stringify(newList, null, 2));
数组去重
let arr = [
{ key: "04-28", count: 1 },
{ key: "10-12", count: 2 },
{ key: "09-04", count: 3 },
{ key: "09-04", count: 4 },
{ key: "06-01", count: 5 },
{ key: "01-17", count: 6 }
];
function handleUnique(arr) {
let newArr = [];
let newArrKeys = new Set();
arr.forEach((item) => {
if (!newArrKeys.has(item.key)) {
newArrKeys.add(item.key);
newArr.push(item);
}
})
return newArr;
}
console.log(handleUnique(arr));
一维数组转化为tree树形结构
let arr = [
{id: 1,name: '1',pid: 0},
{id: 2,name: '1-1',pid: 1},
{id: 3,name: '1-1-1',pid: 2},
{id: 4,name: '1-2',pid: 1},
{id: 5,name: '1-2-2',pid: 4},
{id: 6,name: '1-1-1-1',pid: 3},
{id: 7,name: '2'}
]
// 转化后的结果
// const res = [
// {
// "id": 1,
// "name": "1",
// "pid": 0,
// "children": [
// {
// "id": 2,
// "name": "1-1",
// "pid": 1,
// "children": [
// {
// "id": 3,
// "name": "1-1-1",
// "pid": 2,
// "children": [
// {
// "id": 6,
// "name": "1-1-1-1",
// "pid": 3,
// "children": []
// }
// ]
// }
// ]
// },
// {
// "id": 4,
// "name": "1-2",
// "pid": 1,
// "children": [
// {
// "id": 5,
// "name": "1-2-2",
// "pid": 4,
// "children": []
// }
// ]
// }
// ]
// }
// ]
方法1:递归
function handleTree(arr, parentId = 0) {
let tree = [];
arr.forEach((item)=>{
if(item.pid === parentId){
let vnode = {
...item,
children:handleTree(arr,item.id)
}
tree.push(vnode)
}
})
return tree;
}
console.log('handleTree(arr)',JSON.stringify(handleTree(arr),null,2));
//方法2:不用递归
//主要思路是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储
function handleTreeToArray(data){
const arr = [];
const map = {};
for(let i of data){
map[i.id] = {...i,children:[]};
}
for(let item of data){
// map[item.id] 当前项-子级
const childItem = map[item.id];
// 父级
const parentItem = map[item.pid];// parentItem没有值会被过滤掉
if(item.pid===0){
arr.push(childItem);
}else{
// 通过给parentItem对象的children添加子项,并且引用不变
parentItem?.children.push(childItem)
}
}
return arr;
}
找出最长回文字符串
function longestPalindrome(str) {
let palindromeStr = ""; //记录最长回文串
let tempPalindrome = ""; //记录当前回文串
for (let i = 0; i < str.length; i++) { //i记录当前遍历字符串的开始位置,循环依次向后遍历
tempPalindrome = ""; //每次新的一轮开始时,将临时记录回文串的变量清空
for (let j = i; j < str.length; j++) { //每次开始循环是以当前i所在的下标位置为开始遍历字符串的起始位置,直到遍历到结束位置
tempPalindrome += str.charAt(j); //逐个增加字符串的长度
if (isPalindrome(tempPalindrome) && tempPalindrome.length > palindromeStr.length) {
//将当前的字符串传入isPalindrome进行回文判断,如果是回文串,则判断当前回文串长度是否大于之前记录的最长回文串的长度,
// 如果大于之前的回文串,则更新之前的记录即可
palindromeStr = tempPalindrome; //更新回文串
}
}
}
return palindromeStr; //返回最终的最长的回文串
}
function isPalindrome(s) { //判断是否为回文串
let rev = s.split('').reverse().join(''); //字符串逆转操作
return rev === s;
}
//测试
console.log(longestPalindrome("ddabbade"));//输出dabbad
每3位分割数字字符串(兼容有小数位和没有小数位的数)
// 每3位分割数字字符串
const formatMoney=(nStr)=> {
nStr += '';
let x = nStr.split('.');
console.log('x',x);
let x1 = x[0];
// x.length>1,说明有小数位
let x2 = x.length > 1 ? '.' + x[1] : '';
let rgx = /(\d+)(\d{3})/; // 整数位最少4(1+3)位数
while (rgx.test(x1)) {
console.log('x1-1',x1);
x1 = x1.replace(rgx, '$1' + ',' + '$2');
console.log('x1-2',x1);
}
return x1 + x2;
}
let val = formatMoney('1122123456.12');
console.log('val',val);
兼容多种格式,金额每3位分割展示格式化的函数formatAmount
/**
* 2.实现一个金额展示格式化的函数formatAmount ,金额展示规则为整数部分每三位用逗号分隔,小数部分展示00
* 示例1:formatAmount(2688);=>"2,688.00"
* 示例2:formatAmount('2e6');=>"2,000,000.00"
* 示例3:formatAmount(-2.3333333);=>"-2.33"
* 示例2:formatAmount('Alibaba');=>"-"
*/
function formatAmount(num) {
if (isNaN(Number(num))) {
return '-'
}
if (typeof num === 'string') {
num = Number(num);
}
num = num.toString();
const arr = num.split(".");
let n1 = arr[0];
let n2 = arr[1];
const reg = /(\d+)(\d{3})/;
let str = "";
while (reg.test(n1)) {
n1 = n1.replace(reg, "$1" + "," + "$2");
}
if (n2) {
str = n1 + "." + n2.slice(0, 2);// 有小数点时保留两位小数
} else {
str = n1 + '.00';
}
return str;
}
// console.log(formatAmount(2688.12));// 2,688.12
// console.log(formatAmount(2688));// 2,688.00
// console.log(formatAmount('2e6'));// 2,000,000.00
// console.log(formatAmount(-2.3333333));// -2.33
// console.log(formatAmount('Alibaba'))// -
// console.log(Number('Alibaba'));// NaN
// console.log(typeof NaN);// number
实现一个normalize函数,能将输入的特定字符串转化为特定结构化数据
/*
* 实现一个normalize函数,能将输入的特定字符串转化为特定结构化数据
* 注:字符串仅由小写字母和[ ]组成,且字符串不会包含多余空格
* 示例一:'abc' -> {value:'abc'}
* 示例二:'[abc[bcd[def]]]' ->{value:'abc',children:{value:'bcd',children:{value:'def'}}}
*/
// {value:'abc'},
// {value:'abc',children:{value:'bcd'}},
// {value:'abc',children:{value:'bcd',children:{value:'def'}}},
function normalize(str) {
const reg = /\w+/g;
const arr = str.match(reg);
console.log('arr',arr);
const obj = {}
arr.reduce((pre, cur, index, arr) => {
pre.value = cur;
if (index < arr.length - 1) {
pre.children = {}
}
return pre.children;
}, obj)
return obj;
}
console.log('normalize()', normalize('abc'));// {value:'abc'}
console.log('normalize()', normalize('[abc[bcd[def]]]'));//{value:'abc',children:{value:'bcd',children:{value:'def'}}}
instanceof 原理
function _instanceof(left,right){
let l = left.__proto__;
let r = right.prototype;
while(true){
if(l === null){
return false;
}
if(l === r){
return true;
}
l = l.__proto__;
}
}
function Animal(name){
this.name = name
}
let cat = new Animal('tom');
console.log('cat.__proto__ === Animal.prototype',cat.__proto__ === Animal.prototype); // true
console.log(_instanceof(cat,Animal));// true
Map和Set
let map1 = new Map([['a',1],['b','lc'],['age',12]]);
console.log('map1', map1.get('a')); //1
// 利用set求交集,并集,差集
let arr1=[1,2,3];
let arr2=[2,3,4,5,6];
//交集
function intersection(arr1,arr2){
let set1 = new Set(arr1);
let set2 = new Set(arr2);
let inter = [...set1].filter((item)=>set2.has(item));
return inter;
}
console.log('交集',intersection(arr1,arr2)); //[2,3]
//并集
function union(arr1,arr2){
let set1 = new Set([...arr1,...arr2]);
return [...set1];
}
console.log('并集',union(arr1,arr2)); // [1,2,3,4,5,6]
// 求差集
function deleteFn(arr1,arr2){
let set1=new Set(arr1);
let set2=new Set(arr2);
return [...set1].filter((item)=>!set2.has(item));
}
console.log('差集',deleteFn(arr1,arr2));// [1]
递归匹配过滤 动态路由(数组对象)
let roles = [
"Home","Dashbord","Permission","PageUser",
"PageAdmin","Roles","Github"
];
// 筛选出 roles数组里有的对象
const asyncRoutes = [
{
path: '/permission',
name: 'Permission',
// component: Layout,
redirect: '/permission/page-use',
meta: {
title: '权限许可',
icon: 'el-icon-lock'
},
children: [
{
path: 'page-user',
name: 'PageUser',
component: () => import('@/views/permission/page-user'),
meta: { title: '用户页面', icon: 'el-icon-user' }
},
{
path: 'page-admin',
name: 'PageAdmin',
component: () => import('@/views/permission/page-admin'),
meta: {
title: '管理员页面',
icon: 'el-icon-user-solid'
}
},
]
},
{
path: 'https://github.com/gcddblue/vue-admin-webapp',
name: 'Github',
meta: { icon: 'el-icon-link', title: '项目链接' }
},
{
path: '*',
name: '404',
redirect: '/404',
hidden: true
}
]
// 递归过滤(遍历asyncRoutes动态路由)
function forSearchArr(route, roles) {
let arrNew = []
for (let item of route) {
let itemNew = { ...item } //浅拷贝
if (roles.includes(itemNew.name)) {
if (itemNew.children) {
itemNew.children = forSearchArr(itemNew.children, roles)
}
arrNew.push(itemNew)
}
}
return arrNew
}
let newRouteArrs = forSearchArr(asyncRoutes,roles);
console.log('newRouteArrs',JSON.stringify(newRouteArrs,null,2));
Object.defineProperty,defineReactive实现
// 递归深度观察对象,使其成响应式对象,
// defineReactive = 递归 + Object.defineProperty
let obj = {
name: 'tom',
age: 1,
hobby:{
play:'打球'
}
}
function observer(obj) {
if (typeof obj !== 'object') return obj;
Object.keys(obj).forEach((key) => {
defineReactive(obj, key, obj[key]);
})
}
function defineReactive(obj, key, value) {
// 如果value属性值是object,继续代理观测
observer(value);
Object.defineProperty(obj, key, {
get() {
console.log('get',key, value);
return value;
},
set(newVal) {
console.log('set更新',key, newVal);
value = newVal;
}
})
}
observer(obj);
obj.hobby.play = 'hh1'; // 会触发 hobby的getter,同时会触发 play的setter
Promise错误捕获
//promise的reject只能是promise().catch()捕获
async function main() {
console.log("1");
try {
console.log("2");
await f1();
} catch (error) { // try catch 无法捕获 promise的reject
console.log("6");
console.log("error----", error);
}
}
function f1() {
new Promise((resolve, reject) => {
resolve("1");
console.log("3");
}).then(() => {
console.log("4");
f2();
});
}
function f2() {
new Promise((resolve, reject) => {
console.log("5");
reject("error: f2"); // triggerUncaughtException(err, true /* fromPromise */);
}).catch((err)=>{
console.log('7');
console.log('err',err);
})
// .catch((err)=>{
// console.log('7');
// console.log('err',err);
// })
}
main(); // 分别打印: 1 2 3 4 5 7 err 'error: f2'
// try catch 捕获 throw new Error
function tryFn() {
try {
console.log('try');
throw new Error('error-报错');
} catch (err) {
console.log('err', err);
}
}
tryFn(); // 分别打印:try -> err Error: error-报错
实现有并行限制的 Promise 调度器
/*
* @Description: 实现有并行限制的 Promise 调度器
* 题目描述:JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个
*
addTask(1000,"1");
addTask(500,"2");
addTask(300,"3");
addTask(400,"4");
的输出顺序是:2 3 1 4
整个的完整执行流程:
一开始1、2两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4
*/
class Scheduler {
constructor(limit) {
this.queue = [];
this.maxRun = limit;
this.runningCounts = 0;
}
add(time, order) {
const promiseFn = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(order);
}, time)
})
}
this.queue.push(promiseFn)
}
start() {
for (let i = 0; i < this.maxRun; i++) {
this.request()
}
}
request() {
if (!this.queue || !this.queue.length || this.runningCounts >= this.maxRun) {
return;
}
this.runningCounts++
const firstFn = this.queue.shift();
firstFn().then((res) => {
this.runningCounts--;
console.log(res);
this.request();
})
}
}
let schedulerInstance = new Scheduler(2);
const addTask = (time, order) => {
schedulerInstance.add(time, order);
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
schedulerInstance.start(); // 2,3,1,4
每次调用返回的函数的时候,只会输出最后一个promsie的结果
/*
* @Description:
* 编写一个高阶函数,这个高阶函数接收一个返回promise的函数作为参数,然后返回一个函数。
* 每次调用返回的函数的时候,只会输出最后一个promsie的结果。
* // 示例
let count = 1;
// 这是一个函数,返回promise
let promiseFunction = () =>
new Promise(resolve =>
setTimeout(() => {
resolve(count++);
})
);
let lastFn = lastPromise(promiseFunction);
lastFn().then(console.log); // 无输出
lastFn().then(console.log); // 无输出
lastFn().then(console.log); // 3
*/
let count = 1;
// 这是一个函数,返回promise
let promiseFunction = () => {
return new Promise(resolve =>
setTimeout(() => {
resolve(count++);
})
);
}
function lastPromise(promiseFunction) {
let time1 = 0;
let time2 = 0;
return function(){
// lastPromise.time1优先加1
time1++;
return new Promise((resolve,reject)=>{
// 等promiseFunction函数里的setTimeout,resolve之后,time2加1
promiseFunction().then((res)=>{
time2++;
if(time1===time2){
resolve(res)
}
})
})
}
}
let lastFn = lastPromise(promiseFunction)
lastFn().then((res)=>{
console.log(res)
});
lastFn().then((res)=>{
console.log(res)
});
lastFn().then((res)=>{
console.log(res)
});
this指向
/*
* 1.this 的指向并不是在创建的时候就可以确定的,在 es5 中,this 永远指向最后调用它的那个对象。
* 2.众所周知,ES6 的箭头函数是可以避免 ES5 中使用 this 的坑的。箭头函数的 this 始终指向函数定义时的 this,而非执行时。
* 箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,
* 则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。
*/
// es5 this是调用时或者执行时(运行时)决定的
// 栗1
var name = "windowsName";
var a = {
name: "Cherry",
fn: function () {
console.log(this.name); // Cherry
}
}
a.fn();
// 栗2
var name = "windowsName";
var a = {
name: "Cherry",
fn: function () {
console.log(this.name);// windowsName
}
}
var f = a.fn;
f();
// 栗3
var name = "windowsName";
var a = {
name: "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout(function () {
console.log('this', this); // window
this.func1(); //打印: this.func1 is not a function
}, 100);
// 优化如下:
// setTimeout(function () {
// console.log('this', this); // window
// this.func1(); // 打印:Cherry
// }.bind(a), 100); // bind改变this指向a
}
};
a.func2() // this.func1 is not a function
// 栗4 => es6 箭头函数的this是定义时或创建时 决定的
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
console.log('this',this); // a对象
setTimeout( () => { // 箭头函数的this是由定义时的非箭头函数的块级作用域决定的
this.func1()
},100);
}
};
a.func2() // Cherry
保留几位小数
/**
*
* @param {*} num 数值
* @param {*} decimal 保留位数
*/
const formatDecimal = (num, decimal) => {
num = num.toString()
const index = num.indexOf('.')
if (index !== -1) {// 浮点型小数
num = num.substring(0, decimal + index + 1)
console.log('num', num);
return parseFloat(num).toFixed(decimal);// 可以过滤掉小数点后面都是0的小数,如 parseFloat(123.0) === 123
} else {// 整数即没有小数点时
num = num.substring(0)
return num
}
}
console.log(formatDecimal(123.0, 2));// 特殊0小数位的:123
console.log(formatDecimal(123.456, 2));// 保留两位小数: 123.45
console.log(formatDecimal(123.456, 3));// 保留三位小数: 123.456
console.log('parseFloat(123.0)', parseFloat(123.0));// 123
console.log('123.12'.substring(0, 3 + 1));// 123. substring(startIndex,endIndex) // [) 左闭又开
console.log('123.12345'.substring(0)); // 123.12345
大数相加
/*
* @Description: 题目描述:实现一个add方法完成两个大数相加
*
let a = "9007199254740991";
let b = "1234567899999999999";
function add(a ,b){
//...
}
*/
function add(a, b) {
let maxLen = Math.max(a.length, b.length);
a = a.padStart(maxLen, 0);
b = b.padStart(maxLen, 0);
console.log(a,b);
let cur = 0;// 当前值
let carry = 0;// 进位值
let sum = '';// 相加的值
for (let i = maxLen - 1; i >= 0; i--) {
cur = parseInt(a[i]) + parseInt(b[i]) + carry;
carry = Math.floor(cur / 10);
sum = cur % 10 + sum;
}
console.log('carry',carry);
console.log('sum',sum);
if (carry > 0) {
sum = '' + carry + sum;
}
return sum;
}
let a = "9000";
let b = "1234";
console.log('111',add(a, b));
// console.log(a[a.length - 1]);// 1
LeetCode 二叉树 前中后序遍历,层序遍历
root
left-1 right-1
left-2 right-2
const root = {
val:'root',
left:{
val:'left-1',
left:{
val:'left-2',
left:null,
right:null
},
right:{
val:'right-2',
left:null,
right:null
}
},
right:{
val:'right-1',
left:null,
right:null
}
}
// 递归
function BineryTree(root){
let res = [];
const preOrderTraverseNode=(node)=>{
if(node===null) return;
// 如果node.left === null,preOrderTraverseNode(node.left)这个函数就会被 return终止函数,
// 然后接着往下执行preOrderTraverseNode(node.right)
res.push(node.val);
preOrderTraverseNode(node.left);
preOrderTraverseNode(node.right);
}
preOrderTraverseNode(root);
return res;
}
console.log('BineryTree(root)',BineryTree(root));
// 栈 ->先进后出栈,前序遍历的顺序是:根->左->右,所以先把右入栈再把左入栈,然后左先出栈,右后出栈,[].pop每次取出最后一格
function preOrderTraverseNode1(root){
let stack = [];
let res = [];
if(root ===null) return res;
stack.push(root);
while(stack.length>0){
let currentNode = stack.pop(); // 第一个是root节点
res.push(currentNode.val);// 先根,再左,最后右
if(currentNode.right!==null){
stack.push(currentNode.right);// 先把右边的入栈,先进后出栈
}
if(currentNode.left!==null){
stack.push(currentNode.left);// 再把左边的入栈,后进先出栈
}
}
return res;
}
console.log('preOrderTraverseNode1-stack',preOrderTraverseNode1(root));
// 层序遍历-先进先出
function levelOrderTraverseNode(root){
let stack = [];
let res = [];
stack.push(root);
while(stack.length>0){
let currentNode = stack.shift();
res.push(currentNode.val);
if(currentNode.left!==null){
stack.push(currentNode.left);
}
if(currentNode.right!==null){
stack.push(currentNode.right);
}
}
return res;
}
console.log('levelOrderTraverseNode(root)-层序遍历',levelOrderTraverseNode(root));
LeetCode 最长无重复字符子串的长度
// 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
// 示例 1:
// 输入: "abcabcbb"
// 输出: 3
// 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
// 示例 2:
// 输入: "bbbbb"
// 输出: 1
// 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
// 示例 3:
// 输入: "pwwkew"
// 输出: 3
// 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
// 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
const lengthOfLongestSubstring = function (s) {
console.log('s',s.charAt(0));
let arr = [], max = 0
for (let i = 0; i < s.length; i++) {
let index = arr.indexOf(s[i])
if (index !== -1) {
arr.splice(0, index + 1);
}
arr.push(s.charAt(i)); //arr.push(s[i])
max = Math.max(arr.length, max)
}
console.log('arr',arr);// ['k','e','w']
return max
};
console.log(lengthOfLongestSubstring("pwwkew"));
let arr = ['a','b','c','b']
let index = arr.indexOf('b');
console.log('index',index);
console.log(arr.splice(0,index+1)); //['a','b'] splice(startIndex,deleCount)
console.log('arr',arr); // ['c','b']
LeetCode 求root节点总数量
二叉树节点:
root
left-1 right-1
left-2 right-2
const root = {
val: 'root',
left: {
val: 'left-1',
left: {
val: 'left-2',
left: null,
right: null
},
right: {
val: 'right-2',
left: null,
right: null
}
},
right: {
val: 'right-1',
left: null,
right: null
}
}
// 求root的节点数量
function getNodeNum(node) {
if (node === null) return 0;
return getNodeNum(node.left) + getNodeNum(node.right) + 1;
}
console.log(getNodeNum(root)); // 5
//求root节点的最大深度(最大层数)
function getMaxDepth(node) {
if (node === null) return 0;
let left = getMaxDepth(node.left);
let right = getMaxDepth(node.right);
return Math.max(left, right) + 1;
}
console.log(getMaxDepth(root));// 3
//求root节点的最小深度
function getMinDepth(node){
if(node===null) return 0;
let left = getMinDepth(node.left);
let right = getMinDepth(node.right);
return Math.min(left,right) + 1;
}
console.log(getMinDepth(root));
LeetCode 给出一个区间的集合,请合并所有重叠的区间
/*
* https://blog.csdn.net/qq_41096610/article/details/107531031
* @Description: 给出一个区间的集合,请合并所有重叠的区间。
*
示例 1:
输入: [[1,3],[2,6],[8,10],[15,18]] 输出: [[1,6],[8,10],[15,18]]
解释: 区间[1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入: [[1,4],[4,5]] 输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
*/
// 方法1;
// splice方法 改变原数组
var merge = function(intervals) {
if(intervals.length <= 1) { return intervals }
intervals.sort((a,b) => a[0]-b[0]) // 行进行升序排序
let arr = []
for(var i=0;i<intervals.length-1;i++){ //边界intervals.length-1,因为删除元素变短了,此时,i--
const a2 = intervals[i][1] //取[[1,4],[4,5]]中第一个数组元素4
const b1 = intervals[i+1][0] //取[[1,4],[4,5]]中第二个数组元素4
const b2 = intervals[i+1][1] //取[[1,4],[4,5]]中第二个元素5
if(a2 >= b1){
intervals[i][1] = a2 > b2 ? a2 : b2
intervals.splice(i+1,1) //删除后面多余的元素,删除数量为1
arr.push(a2,b1)
i-- //根据此例子,当i=0时,因为删除arr[1]项之后,数组的长度发生了变化,需减1-1,0-1=-1,然后i++,此时又从-1+1 = 0开始遍历
}
console.log('i++之后',i);
}
return intervals
};
let arr = [[1,3],[2,6],[8,10],[9,11],[15,18]];
console.log(merge(arr));
// 方法2:
/**i和j双指针,不改变原数组,返回新数组
* @param {number[][]} intervals
* @return {number[][]}
*/
var merge2 = function(intervals) {
if(intervals.length <= 1) { return intervals }
intervals.sort((a,b) => {
return a[0] - b[0]
})
let res = [intervals[0]], j = 0;
for(let i = 1; i < intervals.length; i++) {
if(res[j][1] >= intervals[i][1]){
res[j] = res[j]
}else if(res[j][1] >= intervals[i][0] && res[j][1] < intervals[i][1]){
res[j][1] = intervals[i][1]
}else{
res.push(intervals[i])
j++
}
}
return res;
};
let arr = [[1,3],[8,10],[2,6],[15,18]];
console.log(merge2(arr));
LeetCode 验证括号是否都闭合
示例 1:
输入:(){}[]
输出:true
示例 2:
输入:()[{
输出:false
示例 2:
输入:([{}])
输出:true
*/
// 方法1;
function valid(s) {
if (s.length < 2) return false;
let memo = new Map();
let result = []
memo.set("(", ")")
memo.set("{", "}")
memo.set("[", "]")
for (let i = 0; i < s.length; i++) {
if (memo.has(s[i])) {
result.push(memo.get(s[i]))
} else {
if (result.pop() !== s[i]) {
return false;
}
}
}
if(result.length>0) return false;
return true;
}
console.log(valid('([{}])'));// true
LeetCode m*n网格里找两点之间总共有多少种路径
/*
* https://leetcode-cn.com/problems/unique-paths/
*
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
*/
// m=3,n=7 =>28(3行7列)
function uniquePath2(m, n) { // 3*7
const memo = [];
for (let i = 0; i < n; i++) {
memo.push([])
}
for (let col = 0; col < n; col++) { // 第一行都是1 二维数组
memo[col][0] = 1;
}
for (let row = 0; row < m; row++) { // 第一列都是1 二维数组
memo[0][row] = 1;
}
for (let col = 1; col < n; col++) { // 第2列开始
for (let row = 1; row < m; row++) { // 第2行开始
memo[col][row] = memo[col][row-1] + memo[col-1][row]
}
}
return memo[n-1][m-1]
}
console.log(uniquePath2(3, 7));// 28
LeetCode 反转链表
/**
* 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
* 输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
*/
let root = {
val: '1',
next: {
val: '2',
next: {
val: '3',
next: {
val: '4',
next: {
val: '5',
next: null
}
}
}
}
}
// [1->2->3->4->5] -> [1<-2<-3<-4<-5]
function reverseList(head) {
if (head === null) return null;
let pre = null;
let cur = head;
while (cur !== null) {
let nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
console.log(JSON.stringify(reverseList(root), null, 2));
// 打印结果:
/**
{
"val": "5",
"next": {
"val": "4",
"next": {
"val": "3",
"next": {
"val": "2",
"next": {
"val": "1",
"next": null
}
}
}
}
}
*/
LeetCode 合并两个有序数组
/*
* https://leetcode-cn.com/problems/merge-sorted-array
* @Description: 合并两个有序数组
* 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
*/
// 方法1 :先合并在sort排序
function merge(num1, num2) {
num1 = num1.filter((i) => i);
num2 = num2.filter((j) => j);
num1 = num1.concat(num2).sort((a, b) => a - b);
return num1;
}
let nums1 = [1, 2, 3, 0, 0, 0], m = 3;
let nums2 = [2, 5, 6], n = 3;
console.log(merge(nums1, nums2));
// 方法2 :我们为两个数组分别设置一个指针p1和p2来作为队列的头部指针
const merge2 = function (nums1, m, nums2, n) {
let p1 = 0, p2 = 0;
const sorted = new Array(m + n).fill(0);
let cur;
while (p1 < m || p2 < n) {
if (p1 === m) {
cur = nums2[p2++];
} else if (p2 === n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur; //p1=3,p2=3,cur=6 [1,2,2,3,5,6]
}
for (let i = 0; i < m + n; ++i) {
nums1[i] = sorted[i];
}
};
let nums1 = [1, 2, 3, 0, 0, 0], m = 3;
let nums2 = [2, 5, 6], n = 3;
merge2(nums1, m, nums2, n);
console.log('nums1', nums1); // [1,2,2,3,5,6]
let nums1 = [1, 2, 3], p1 = 0;
let cur;
cur = nums1[p1++];
// 等价
// cur = nums1[p1];
// p1 = p1 + 1
console.log({ cur, p1 }); // { cur: 1, p1: 1 }