- Vue数据双向绑定
class Observer {
constructor(obj) {
this.walk(obj)
}
walk(obj) {
for (let i in obj) {
this.defineReactive(obj, i, obj[i])
}
}
array() {
const arrayProto = Array.prototype
// 创建一个对象作为拦截器
const arrayMethods = Object.create(arrayProto)
// 改变数组自身内容的7个方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
const that = this
methodsToPatch.forEach(function (method) {
const original = arrayProto[method] // 缓存原生方法
Object.defineProperty(arrayMethods, method, {
enumerable: false,
configurable: true,
writable: true,
value: function mutator(...args) {
const result = original.apply(this, args)
console.log(method, '方法操作了数组', args);
let inserted
switch (method) {
case 'push':
case 'shift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
default:
break;
}
if (inserted) that.walk(inserted)
return result
}
})
})
return arrayMethods
}
defineReactive(obj, key, value) {
if (Array.isArray(value)) {
value.__proto__ = this.array()
}
if (value && typeof value === 'object') {
this.walk(value)
}
Object.defineProperty(obj, key, {
get() {
console.log(key, '被读取了');
return value
},
set(newVal) {
console.log(key, '被设置了');
if (newVal !== value) {
value = newVal
return value
}
}
})
}
}
new Observer(obj)
-
深拷贝
-
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
-
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
//代码实现深拷贝
function deepClone(obj, weakmap = new WeakMap()) {
//界限判断
if (obj instanceof RegExp) {
return new RegExp(obj)
}
if (obj instanceof Date) {
return new Date(obj)
}
if (typeof obj !== 'object') {
return obj
}
if (weakmap.has(obj)) {
return weakmap.get(obj)
}
//优先选择
// let target = new obj.__proto__.constructor()
//持续优化
let target = Reflect.construct(obj.__proto__.constructor, [])
weakmap.set(obj, target)
// 优于 let target = Object.prototype.toString.call(obj)==='[object Array]'?[]:{}
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
if (typeof obj[i] === 'object') {
target[i] = deepClone(obj[i], weakmap)
} else {
target[i] = obj[i]
}
}
}
return target
}
//扁平数组
let a = '哈哈哈'
let arr = [1, '2', '3', a, [4, 5, [6, [7, [8]]]]]
function flat(arr) {
let res = []
for (let i of arr) {
Array.isArray(i) ? res.push(...flat(i)) : res.push(i)
}
return res
}
function flat(arr) {
while (arr.some(i => Array.isArray(i))) {
arr = [].concat(...arr)
}
return arr
}
function flat(arr) {
return arr.reduce((i, j) => {
Array.isArray(j) ? i.push(...flat(j)) : i.push(j)
return i
}, [])
}
console.log(flat(arr));
//发布订阅模式
let eventEmitter = {
list: {},
on: function (events, fn) {
let _this = this;
(_this.list[events] || (_this.list[events] = [])).push(fn);
return _this;
},
emit: function (...args) {
let _this = this;
let event = [].shift.call(args),
fns = [..._this.list[event]];
if (!fns || fns.length === 0) {
return false;
}
fns.forEach(fn => {
fn.apply(_this, args)
})
return _this;
}
}
function user1(content) {
console.log('用户1订阅了:', content);
};
function user2(content) {
console.log('用户2订阅了:', content);
};
// 订阅
eventEmitter.on('article', user1);
eventEmitter.on('article', user2);
eventEmitter.emit('article', 'Javascript 发布-订阅模式');
//js并发请求
class Scheduler {
constructor(limit) {
this.limit = limit
this.number = 0
this.queue = []
}
addTask(timeout, str) {
this.queue.push([timeout, str])
}
start() {
if (this.number < this.limit&&this.queue.length) {
var [timeout, str] = this.queue.shift()
this.number++
setTimeout(() => {
console.log(str)
this.number--
this.start()
}, timeout * 1000);
this.start()
}
}
}
const scheduler = new Scheduler(2)
scheduler.addTask(1, '1'); // 1s后输出'1'
scheduler.addTask(2, '2'); // 2s后输出'2'
scheduler.addTask(1, '3'); // 2s后输出'3'
scheduler.addTask(1, '4'); // 3s后输出'4'
scheduler.start();
//promise手写
let PENDING = 'pending'
let FULFILLED = 'fulfilled'
let REJECTED = 'rejected'
class myPromise {
constructor(excutor) {
this.value = '';
this.reason = '';
this.status = PENDING;
this.queueRes = [];
this.queueRej = [];
excutor(this.reslove, this.reject)
}
reslove = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
while (this.queueRes.length) {
this.queueRes.shift()(this.value)
}
}
}
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
while (this.queueRej.length) {
this.queueRej.shift()(this.reason)
}
}
}
then(onFulfilled, onRejected) {
// 实现then的不传参
typeof onFulfilled === 'function' ? onFulfilled : value => value
typeof onRejected === 'function' ? onRejected : reason => reason
const p2 = new myPromise((resolve, reject) => {
if (this.status === FULFILLED) {
queueMicrotask(() => {
const x = onFulfilled(this.value);
//处理return自己的情况
if (p2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof myPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
})
} else if (this.status === REJECTED) {
onRejected(this.reason)
} else if (this.status === PENDING) {
this.queueRes.push(onFulfilled);
this.queueRej.push(onRejected);
}
})
return p2
}
}
//关于promise的一些考点:
1://用promise限制请求数
function concurrentRequest(requestFns, limit) {
// 递归函数
let i = 0
function recursion(requestFn) {
requestFn().finally(() => {
if (requestFns.length > 0) {
recursion(requestFns.pop());
}
});
}
// 限制最大并发量
while (i < limit && requestFns.length > 0) {
i++
recursion(requestFns.pop());
}
}
// ----------下面是测试用例------------
// 模拟一个异步请求函数
function createRequest(delay) {
return () =>
new Promise((resolve) => {
setTimeout(() => {
resolve("done");
}, delay);
}).then((res) => {
console.log(res);
});
}
const requestFns = [];
for (let i = 0; i < 10; i++) {
requestFns.push(createRequest(1000));
}
concurrentRequest(requestFns, 3);
2://用promise实现取消请求(请求多次,取消前面所有的,只执行最后一次)
function CancelablePromise() {
this.pendingPromise = null;
}
// 包装一个请求并取消重复请求
CancelablePromise.prototype.request = function (requestFn) {
if (this.pendingPromise) {
this.cancel("取消重复请求");
}
const _promise = new Promise((resolve, reject) => (this.reject = reject));
this.pendingPromise = Promise.race([requestFn(), _promise]);
// console.log(1111111, this.pendingPromise, this.reject);
return this.pendingPromise;
};
// 取消当前请求
CancelablePromise.prototype.cancel = function (reason) {
this.reject(new Error(reason));
// this.pendingPromise = null;
};
// ----------下面是测试用例------------
// 模拟一个异步请求函数
function createRequest(delay) {
return () =>
new Promise((resolve) => {
setTimeout(() => {
resolve("done");
}, delay);
});
}
const cancelPromise = new CancelablePromise();
// 前四个请求将被自动取消
for (let i = 0; i < 6; i++) {
cancelPromise
.request(createRequest(1000))
.then((res) => console.log(res)) // 最后一个 done
.catch((err) => console.error(err)); // 前四个 error: 取消重复请求
}
// 设置一个定时器等3s,让前面的请求都处理完再继续测试
setTimeout(() => {
// 手动取消最后一个请求
cancelPromise
.request(createRequest(1000))
.then((res) => console.log(res))
.catch((err) => console.error(err)); // error:手动取消
cancelPromise.cancel("手动取消");
}, 3000);
// 设置一个定时器等4s,让前面的请求都处理完再继续测试
setTimeout(() => {
cancelPromise
.request(createRequest(1000))
.then((res) => console.log(res)) // done
.catch((err) => console.error(err));
}, 4000);
//jsonp跨域
function jsonp(url, callback) {
const script = document.createElement('script')
script.src = url
document.body.appendChild(script)
return new Promise((reslove, reject) => {
window[callback] = function (data) {
try {
reslove(data)
} catch (error) {
reject(error)
} finally {
document.body.removeChild(script)
delete window[callback]
}
}
})
}
jsonp('./json.json', 'fn').then((res) => {
console.log(res)
})
//手写apply
Function.prototype.myApply = function (context, args) {
context = context || window
args = args ? args : []
//给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
context[key] = this
//通过隐式绑定的方式调用函数
const result = context[key](...args)
//删除添加的属性
delete context[key]
}
//手写bind
Function.prototype.myBind = function (context, ...args) {
const fn = this
args = args ? args : []
return function newFn(...newFnArgs) {
if (this instanceof newFn) {
return new fn(...args, ...newFnArgs)
}
return fn.apply(context, [...args,...newFnArgs])
}
}
//实现防抖
function debounce(fn, delay) {
let timer = null
return function (...arg) {
if (timer) {
clearTimeout(timer)
}
let context = this
timer = setTimeout(() => {
fn.apply(context, arg)
}, delay)
}
}
//实现节流
function throttle(fn, delay) {
let timer = null
return function (...arg) {
let context = this
if (!timer) {
timer = setTimeout(() => {
fn.apply(context, arg)
timer = null
}, delay)
}
}
}
//实现函数柯里化1
function add(...args) {
return args.reduce((a, b) => {
return a + b
})
}
function currying(fn) {
let args = []
return function temp(...newVal) {
if (newVal.length) {
args = [...args, ...newVal]
return temp
} else {
let val = fn.apply(null, args)
return val
}
}
}
let addCurry = currying(add)
console.log(addCurry(1, 2, 3)(2, 3, 6)())
//实现函数柯里化2(有点难理解)
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
_args.push(...arguments);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
console.log(_adder);
return _adder;
}
let a = add(1,2,3)(2)(3)
console.log(a);
// 数组转成树
let input = [
{ id: 1, val: '学校', parentId: null },
{ id: 2, val: '班级1', parentId: 1 },
{ id: 3, val: '班级2', parentId: 1 },
{ id: 4, val: '学生1', parentId: 2 },
{ id: 5, val: '学生2', parentId: 3 },
{ id: 6, val: '学生3', parentId: 7 },
{ id: 7, val: '班级3', parentId: 1 },
]
//1
function arrToTree(arr) {
let data = JSON.parse(JSON.stringify(arr))
let c = data.filter(i => {
let arr = data.filter(j => j['pid'] === i['id'])
arr.length && (i['children'] = arr)
return i.pid === 0
})
return c[0]
}
//2
function arrToTree(arr) {
let dataArr = JSON.parse(JSON.stringify(arr))
let data = new Map()
for (let i of dataArr) {
data.set(i['id'], i)
}
for (let i of dataArr) {
if (i.pid === 0) {
continue
}
let y = data.get(i['pid'])
y['children'] ? y['children'].push(i) : y['children'] = [i]
}
return data.get(1)
}
// 虚拟dom
//一气呵成,太有成就感啦!
//示例
let virtualDom = {
tag: 'div',
props: {
value: '父级div',
color: 'red'
},
children: [
{
tag: 'span',
props: {
value: '儿级span',
color: 'blue'
},
children: [
{
tag: 'div',
children: 'niubi',
},
],
},
{ tag: 'span', children: 'world' },
],
}
//咱们专业一点:结构化
class Element {
constructor(tag, props, children) {
this.tag = tag;
this.props = props;
this.children = children;
}
}
let createElement = (type, props, children) => {
return new Element(type, props, children)
}
//将示例转换
function toCdom(Vdom) {
if (typeof Vdom.children !== 'object') {
return createElement(Vdom['tag'], Vdom['props'] || {}, Vdom['children'])
} else {
let arr = []
let _c = createElement(Vdom['tag'], Vdom['props'] || {}, arr)
for (let i of Vdom['children']) {
arr.push(toCdom(i))
}
return _c
}
}
let _c = toCdom(virtualDom)
//属性判断
function setAtr(node, key, value) {
switch (key) {
case 'value':
if (node.tagName.toLowerCase() === 'input' ||
node.tagName.toLowerCase() === 'textarea'
) {
node.setAttribute(key, value)
} else {
node.setAttribute(key, value)
}
break;
case 'style':
node.style.cssText = value
break;
default:
node.setAttribute(key, value)
break;
}
}
//render函数
function render(_c) {
let ele = document.createElement(_c['tag'])
if (typeof _c['children'] !== 'object') {
ele.innerText = _c['children']
} else {
for (let i of _c['children']) {
ele.appendChild(render(i))
}
}
for (let i in _c['props']) {
setAtr(ele, i, _c['props'][i])
}
return ele
}
console.log(render(_c));
// 字节面试题 虚拟dom
let virtualDom = {
tag: 'div',
children: [
{
tag: 'span',
children: [
{
tag: 'div',
children: 'niubi',
},
],
},
{ tag: 'span', children: 'world' },
],
}
function renderTOHtml(vdom) {
let el = document.createElement(vdom.tag)
if (typeof vdom.children == 'string') {
vdom.children = [vdom.children]
}
vdom.children.forEach((child) => {
if (typeof child == 'object') {
child = renderTOHtml(child)
} else {
child = document.createTextNode(child)
}
el.appendChild(child)
})
return el
}
let realDom = renderTOHtml(virtualDom)
console.log(realDom)
// 解析url
function parseQueryString(url) {
if (typeof url !== 'string') throw new Error('invalid url')
const search = decodeURIComponent(url).split('?')[1]
if (!search) return {}
return search.split('&').reduce((pre, cur) => {
console.log(pre, cur)
const [key, value] = cur.split('=')
pre[key] = value
return pre
}, {})
}
parseQueryString('http://iauto360.cn/index.php?key0=0&key1=1&key2=2')
//数据代理(vue考点)
function proxy(vm, target, key) {
Object.defineProperty(vm, key, {
get() {
return vm[target][key]
},
set(newVal) {
vm[target][key] = newVal
},
})
}
let vm = {
data() {
return {
title: '语文新课标',
list: {
name: 'liuhuang',
age: '11',
},
}
},
}
let data = vm.data
data = vm.data = typeof data === 'function' ? data.call(vm) : data || {}
for (let key in data) {
proxy(vm, 'data', key)
}
console.log(vm.title)