- 数组扁平化
- 数组去重
- 类数组转化为数组
- Array.prototype.filter()
- Array.prototype.map()
- Array.prototype.forEach()
- Array.prototype.reduce()
- Function.prototype.call
- Function.prototype.apply()
- Function.prototype.bind()
- debounce
- throttle
- 函数柯里化
- 模拟new操作
- instanceof
- Object.assign
- 深拷贝
- promise
- Promise并行限制
- JSONP(JSON with Padding)
- AJAX
- event
- 图片懒加载
- 渲染大数据
- 获取当前页面html元素种类
- 将VirtualDom转换为真实DOM
- 字符串解析
数组扁平化
//32手写
//01.数组扁平化
let arr = [1, [2, [3, [4, 5]]], 6];
// => [1, 2, 3, 4, 5, 6]
let res;
res = arr.flat(Infinity);
res = JSON.stringify(arr).replace(/\[|\]/g, "").split(",");
res = JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g, "") + "]");
const flatten = arr => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
};
res = flatten(res);
const result = [];
const f2 = arr => {
for (let i = 0; i < arr.length; i++) {
Array.isArray(arr[i]) ? f2(arr[i]) : result.push(arr[i]);
}
return result;
};
res = f2(arr);
console.log(res);
数组去重
// 02.数组去重
arr = [1, 1, "1", 17, true, true, false, false, "true", "a", {}, {}];
// => [1, '1', 17, true, false, 'true', 'a', {}, {}]
res = [...new Set(arr)];
const unique = arr => {
return arr.filter((item, idx) => {
return arr.indexOf(item) === idx;
});
};
res = unique(arr);
console.log(res);
类数组转化为数组
// 03.类数组转化为数组
// const div = document.querySelector('div')
const div = [1, 1]; //一般是 arguments 或是 dom 操作方法返回的数据
res = Array.from(div);
res = Array.prototype.slice.call(div);
res = [...div];
res = Array.prototype.concat.call(div);
console.log(res);
Array.prototype.filter()
//04.Array.prototype.filter()
Array.prototype.fakeFilter = function(callback,thisArg){
if(this == undefined)throw new TypeError('this is null or undefined');
if(typeof callback !== "function")throw new TypeError(callback+'is not a function');
const res = [];
const O = Object(this)
const len = O.length >>> 0
for(let i = 0;i<len;i++){
if(i in O){
if(callback.call(thisArg,O[i],i,O)){
res.push(O[i])
}
}
}
return res
}
const arr =[-1,-1,11,1,1]
console.log(arr.fakeFilter((item)=>{
return item > 0
}))
Array.prototype.map()
//05.Array.prototype.map()
Array.prototype.myMap = function(callback,thisArg){
if(this == undefined)throw new TypeError('this is null or undefined');
if(typeof callback !== "function")throw new TypeError(callback+'is not a function');
const res = []
const O = Object(this)
const len = O.length>>>0
for(let i =0;i<len;i++){
if( i in O){
res[i] = callback.call(thisArg,O[i],i,this)
}
}
return res
}
function arrDouble(i){
return i*2
}
res = arr.myMap(arrDouble)
console.log(res);
Array.prototype.forEach()
//06.Array.prototype.forEach()
Array.prototype.myForEach = function(callback,thisArg){
if(this == undefined)throw new TypeError('this is null or undefined');
if(typeof callback !== "function")throw new TypeError(callback+'is not a function');
const O = Object(this)
const len = O.length>>>0
let k =0
while(k < len){
if(k in O){
callback.call(thisArg,O[k],k,this)
}
k++
}
}
function clog(i){
console.log(i)
}
res.myForEach(clog)
// console.log(res)
Array.prototype.reduce()
//07.Array.prototype.reduce()
Array.prototype.myReduce = function(callback,initValue){
if (this == undefined) throw new TypeError("this is null or undefined");
if (typeof callback !== "function")
throw new TypeError(callback + "is not a function");
const O = Object(this)
const len = O.length >>> 0
let accumulator = initValue
let k = 0
if(accumulator === undefined){
while(k < len && !(k in O))k++//找到数组的第一个有效值
if(k >= len)throw new TypeError('Reduce of empty array with no initial Value')
accumulator = O[k++]
}
while(k<len){
if(k in O)accumulator = callback.call(undefined,accumulator,O[k],k,O)
k++
}
return accumulator
}
const arr = [1,1,1,,4,5]
function add(a,b){
return a+b
}
let res = arr.myReduce(add)
// console.log(res)
Function.prototype.call
//08.Function.prototype.call
Function.prototype.myCall = function (context = window, ...args) {
if (typeof this !== 'function') throw new TypeError('Type Error')
const fn = Symbol('fn')
context[fn] = this
const res = context[fn](...args)
delete context[fn]
return res
}
const a = {
name: 'aaa',
sayName: function () {
console.log(this.name)
},
}
const b = {
name: 'bbb',
}
// a.sayName.myCall(b) //bbb
function Parent(name) {
this.name = name
this.showName = function () {
console.log(this.name)
}
}
function Child(name) {
Parent.myCall(this, name)
}
const c = new Child('ccc')
// c.showName() //ccc
Function.prototype.apply()
//09.Function.prototype.apply()
//与call不同的是 函数接收的参数是数组
Function.prototype.myApply = function (context = window, args) {
if (typeof this !== 'function') throw new TypeError('Type Error')
const fn = Symbol('fn')
context[fn] = this
if (args) {
const res = context[fn](...args)
} else {
const res = context[fn]()
}
delete context[fn]
return res
}
// a.sayName.myApply(b) //bbb
function C2(name) {
Parent.myApply(this, [name])
}
const c2 = new C2('c222')
// c2.showName() //c222
Function.prototype.bind()
//10.Function.prototype.bind
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== 'function') throw new TypeError('Type Error')
const fnToBind = this
const fuBound = function () {
return fnToBind.apply(context, [...args, ...arguments])
}
return fuBound
}
const boy = {
age: 18,
getAge() {
return this.age
},
}
const getX = boy.getAge.myBind(boy)
// console.log(getX())//18
function add(a, b) {
return a + b
}
// console.log(add.myBind(null, 10)(12, 1)) //22
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。 假设B复制了A,修改A的时候,看B是否发生变化:
如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值) 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
debounce
//11 debounce
const myDebounce = (fn, time) => {
let timeout = null
return function () {
clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, arguments)
}, time)
}
}
throttle
//12. throttle
const myThrottle = (fn, time) => {
let flag = true
return function () {
if (!flag) return
flag = false
setTimeout(() => {
fn.apply(this, arguments)
flag = true
}, time)
}
}
函数柯里化
//13.函数柯里化
//实现add(1)(2)(3)(4)=10; 、 add(1)(1,2,3)(2)=9;
//直接简单add版本
function add2() {
const _args = [...arguments]
function inner() {
_args.push(...arguments)
return inner
}
inner.toString = function () {
return _args.reduce((sum, cur) => sum + cur)
}
return inner
}
const resAdd = add2(1)(2)(3)(4)
// console.log(resAdd == 10) //true
// console.log(resAdd === 10) //false
// //实际上得到的 resAdd 是一个函数
//参数定长柯里化
function myCurry(fn) {
//获取原函数的参数长度
const argLen = fn.length
//保留预置参数
const presetArgs = [].slice.call(arguments, 1) //去掉传入的第一个参数(fn)
// console.log(presetArgs)
//返回新函数
return function () {
// const restArgs = [].slice.call(arguments) //和下面的写法一样效果
const restArgs = [...arguments]
// console.log(restArgs)
const allArgs = [...presetArgs, ...restArgs]
// console.log(allArgs)
if (allArgs.length >= argLen) {
//如果参数够了,就执行原函数
// console.log(this)
// console.log(allArgs)
// console.log(fn.apply(this, [1, 2, 3]))
return fn.apply(null, allArgs)
} else {
//否则继续柯里化
return myCurry.call(null, fn, ...allArgs)
}
}
}
function add(a, b, c) {
return a + b + c
}
// 把函数传进去就可以了
const addCurry = myCurry(add)
// console.log(addCurry)
// console.log(addCurry(1)(2)(3))
// console.log(addCurry(1)(2, 3))
// console.log(addCurry(1, 2, 4))
// console.log(addCurry(1, 2)(3))
//参数不定长柯里化
function myCurry2(fn) {
const preset = [].slice.call(arguments, 1)
function curried() {
const rest = [].slice.call(arguments)
const all = [...preset, rest]
//新函数执行后仍然返回一个结果函数。
return myCurry2.call(null, fn, ...all)
}
//结果函数可以被Javascript引擎解析 ==> 重写toString
curried.toString = function () {
return fn.apply(null, preset)
}
return curried
}
function dynamicAdd() {
return [...arguments].reduce((prev, curr) => {
return prev + curr
}, 0)
}
const addPro = myCurry2(dynamicAdd) //经 curry处理,得到一个新函数,这一点不变。
console.log(typeof addPro)
// addPro(1)(2)(3)(4) // 10
// addPro(1, 2)(3, 4)(5, 6) // 21
模拟new操作
//模拟new操作
function myNew(someConstructor, ...args) {
if (typeof someConstructor !== 'function') throw new TypeError('Type Error')
//以 someConstructor 为原型创建一个对象
const obj = Object.create(someConstructor.prototype)
//执行构造函数并将this绑定到新创建的对象上
const res = someConstructor.apply(obj, args)
//判断构造函数执行返回的结果是否是引用数据类型
//若是则返回构造函数返回的结果
//若不是则返回创建的对象
const isObj = typeof res === 'object' && res !== null
const isFuc = typeof res === 'function'
return isObj || isFuc ? res : obj
}
function Otest() {
this.a = 'a'
}
const o1 = myNew(Otest)
// console.log(o1.a) //a
instanceof
//15. instanceof
//左侧是对象,右侧是函数
const myInstanceof = (left, right) => {
//基本数据类型 false
if (typeof left !== 'object' || left === null) return false
let proto = Object.getPrototypeOf(left)
while (true) {
if (proto === null) return false
if (proto === right.prototype) return true
proto = Object.getPrototypeOf(proto)
}
}
function grandpa() {}
function father() {}
father.prototype = new grandpa()
function son() {}
son.prototype = new father()
let xiaoming = new son()
// console.log(myInstanceof(xiaoming, son)) //true
// console.log(myInstanceof(xiaoming, father)) //true
// console.log(myInstanceof(xiaoming, grandpa)) //true
//16. 原型继承——寄生组合继承
function Parent() {
this.name = 'parent'
}
function Child() {
Parent.call(this)
this.type = 'children'
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
const c = new Child()
// console.log(c.name) //parent
// console.log(c.type) //children
// console.log(c instanceof Parent) //true
//17. Object.is
/* 主要解决:
1. +0 === -0 //true
2. NaN === NaN //false
*/
const myIs = (x, y) => {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
}
// console.log(myIs(+0,-0))//false
// console.log(myIs(NaN,NaN))//true
Object.assign
深拷贝
//19. 深拷贝
const myCloneDeep = (target, hash = new WeakMap()) => {
//对于传入参数进行处理
if (typeof target !== 'object' || target === null)
return target
//哈希表中存在则直接返回
if (hash.has(target)) return hash.get(target)
const cloneTarget = Array.isArray(target) ? [] : {}
hash.set(target, cloneTarget)
//针对Symbol属性
const symKeys = Object.getOwnPropertySymbols(target)
if (symKeys.length) {
symKeys.forEach((symKey) => {
if (
typeof target[symKey] === 'object' &&
target[symKey] !== null
) {
cloneTarget[symKey] = myCloneDeep(target[symKey])
} else {
cloneTarget[symKey] = target[symKey]
}
})
}
for (const i in target) {
if (Object.prototype.hasOwnProperty.call(target, i)) {
cloneTarget[i] =
typeof target[i] === 'object' && target[i] !== null
? myCloneDeep(target[i], hash)
: target[i]
}
}
return cloneTarget
}
const obj = {
a: 1,
}
const obj2 = myCloneDeep(obj)
// console.log(obj2) //{a:1}
// obj2.a = 2
// console.log(obj) //{a:1}
// console.log(obj2) //{a:2,b:2}
promise
Promise.all()会等待所有Promise完成,Promise.race()只会执行一个Promise。
//20.promise
// 模拟实现Promise
// Promise利用三大手段解决回调地狱:
// 1. 回调函数延迟绑定
// 2. 返回值穿透
// 3. 错误冒泡
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class myPromise {
constructor(exector) {
//初始化状态
this.status = PENDING
//将成功、失败结果放在 this 上,便于then、catch 访问
this.value = undefined
this.reason = undefined
//成功态回调函数队列
this.onFulfilledCallbacks = []
//失败态回调函数队列
this.onRejectedCallback = []
const resolve = (value) => {
//只有进行中状态才能更改状态
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
//成功态函数依次执行
this.onFulfilledCallbacks.forEach((fn) =>
fn(this.value),
)
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
//失败态的函数依次执行
this.onRejectedCallback.forEach((fn) =>
fn(this.reason),
)
}
}
try {
//立即执行executor
//把内部的resolve和reject 传入executor,用户可调用resolve和reject
exector(resolve, reject)
} catch (e) {
//exrcutor 执行出错,将错误内容reject抛出去
reject(e)
}
}
then(onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === 'function'
? onFulfilled
: (value) => value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw new Error(
reason instanceof Error
? reason.message
: reason,
)
}
//保存this
const self = this
return new myPromise((resolve, reject) => {
if (self.status === PENDING) {
self.onFulfilledCallbacks.push(() => {
//try捕获错误
try {
//模拟微任务
setTimeout(() => {
const result = onFulfilled(self.value)
//分两种情况:
//1.回调函数返回值是 Promise 执行then操作
//2.如果不是Promise,调用新Promise的resolve函数
result instanceof myPromise
? result.then(resolve, reject)
: resolve(result)
})
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push(() => {
try {
setTimeout(() => {
const result = onRejected(self.reason)
result instanceof myPromise
? result.then(resolve, reject)
: resolve(result)
})
} catch (e) {
reject(e)
}
})
} else if (self.status === FULFILLED) {
try {
setTimeout(() => {
const result = onFulfilled(self.value)
result instanceof myPromise
? result.then(resolve, reject)
: resolve(result)
})
} catch (e) {
reject(e)
}
} else if (self.status === REJECTED) {
try {
setTimeout(() => {
const result = onRejected(self.reason)
result instanceof myPromise
? result.then(resolve, reject)
: resolve(result)
})
} catch (e) {
reject(e)
}
}
})
}
catch(onRejected) {
return this.then(null, onRejected)
}
static resolve(value) {
if (value instanceof myPromise) {
//如果是Promise实例,则直接返回
return value
} else {
//如果不是,则返回一个新的 Promise对象,状态为FULFILLED
return new myPromise((resolve, reject) =>
resolve(value),
)
}
}
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
//Promise.all是支持链式调用的,本质上就是返回了一个Promise实例,通过resolve和reject来改变实例状态。
static all(promiseArr) {
return new myPromise((resolve, reject) => {
const len = promiseArr.length
const values = new Array(len)
//记录已经成功执行的promise个数
let count = 0
for (let i = 0; i < len; i++) {
//Promise.resolve()处理,确保每一个都是promise实例
myPromise.resolve(promiseArr[i]).then(
(val) => {
values[i] = val
count++
//如果全部执行完,返回promise状态就可以改变
if (count === len) resolve(values)
},
(err) => reject(err),
)
}
})
}
//Promise.race(iterable) 方法返回一个 promise
//一旦迭代器中的某个promise解决或拒绝
//返回的 promise就会解决或拒绝。
static race(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach((p) => {
Promise.resolve(p).then(
(val) => resolve(val),
(err) => reject(err),
)
})
})
}
}
Promise并行限制
//23. Promise并行限制
class Scheduler {
constructor() {
this.queue = []
//最大并行个数
this.maxCount = 2
this.runCount = 0
}
//往队列中插入Promise Creator函数
add(promiseCreator) {
this.queue.push(promiseCreator)
}
//每次从队列中取出Promise Creator并执行
//此Promise执行完成之后
//应该递归调用request函数来执行下一个Promise
request() {
if (
!this.queue ||
!this.queue.length ||
this.runCount >= this.maxCount
) {
return
}
this.runCount++
this.queue
.shift()()
.then(() => {
this.runCount--
this.request()
})
}
//启动函数,将队列中的两个Promise启动起来
taskStart() {
for (let i = 0; i < this.maxCount; i++) {
this.request()
}
}
}
//test
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTask = (time, order) => {
scheduler.add(() =>
timeout(time).then(() => console.log(order)),
)
}
addTask(800, '1')
addTask(1100, '2')
addTask(600, '3')
addTask(300, '4')
scheduler.taskStart()
JSONP(JSON with Padding)
//24. JSONP(JSON with Padding)
//jsonp的核心原理是利用script标签没有同源限制的方式,可以发送跨域的get请求(只能发送get请求)。
const jsonp = ({ url, params, callbackName }) => {
//script标签中的src属性将请求参数和当前请求的回调函数名拼接在链接上。最终由服务端接收到请求之后拼接成前端可执行的字符串的形式返回。这个结果字符串最终会在前端的script标签中解析并执行。
const generateUrl = () => {
let dataSrc = ''
for (let key in params) {
if (
Object.prototype.hasOwnProperty.call(params, key)
) {
dataSrc += `${key}=${params[key]}&`
}
}
dataSrc += `callback=${callbackName}`
return `${url}?${dataSrc}`
}
return new Promise((resolve, reject) => {
const scriptEle = document.createElement('script')
scriptEle.src = generateUrl()
document.body.appendChild(scriptEle)
window[callbackName] = (data) => {
resolve(data)
document.removeChild(scriptEle)
}
})
}
/* btn.addEventListener('click', () => {
jsonp({
url: 'http://localhost:3000/xxx',
params: {
name: 'xx',
age: 18,
},
callback: 'show',
}).then((data) => {
console.log(data)
})
}) */
AJAX
//25.AJAX
// xhr 具有一个 open 方法,这个方法的作用类似于初始化,并不会发起真正的请求
// open 方法具有 5 个参数,但是常用的是前 3 个
// method: 请求方式 —— get / post
// url:请求的地址
// async:是否异步请求,默认为 true(异步)
//xhr.open(method, url, async)
// send 方法发送请求,并接受一个可选参数
// 当请求方式为 post 时,可以将请求体的参数传入
// 当请求方式为 get 时,可以不传或传入 null
// 不管是 get 还是 post,参数都需要通过 encodeURIComponent 编码后拼接
//xhr.send(data)
//注意:XMLHttpRequest不是node内置的,需要安装
//npm install xmlhttprequest
// const XMLHttpRequest =
// require('xmlhttprequest').XMLHttpRequest
//或者安装quokka browser插件jsdom-quokka-plugin可以在vscode中调试
const ajax = function (options) {
const url = options.url
const method = options.method.toLowerCase() || 'get'
const async = options.async != false //默认为true
const data = options.data
//兼容IE
const xhr = XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject('Mscrosoft.XMLHttp')
if (options.timeout && options.timeout > 0) {
xhr.timeout = options.timeout
}
return new Promise((resolve, reject) => {
//处理超时
xhr.onTimeout = () => reject && reject('请求超时')
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return
// HTTP 状态在 200-300 之间表示请求成功
// HTTP 状态为 304 表示请求内容未发生改变,可直接从缓存中读取
if (
(xhr.status >= 200 && xhr.status < 300) ||
xhr.status === 304
) {
resolve && resolve(xhr.responseText)
} else {
reject && reject(new Error(xhr.responseText))
}
}
xhr.onerror = (err) => reject && reject(err)
let paramArr = []
let encodeData
if (data instanceof Object) {
for (let key in data) {
//通过encodeURIComponent编码后再拼接参数
paramArr.push(
`${encodeURIComponent(key)}=${encodeURIComponent(
data[key],
)}`,
)
}
encodeData = paramArr.join('&')
}
//看要不要处理url
if (method === 'get') {
//检测url中是否存在?以及其下标
const index = url.indexOf('?')
if (index === -1) url += '?'
else if (index !== url.length - 1) url += '&'
//拼接
url += encodeData
}
//初始化请求
xhr.open(method, url, async)
if (method === 'get') xhr.send(null)
else {
//post方法,设置请求头
xhr.setRequestHeader(
'Content-Type',
'application/x-www-form-urlencoded;charset=UTF-8',
)
xhr.send(encodeData)
}
})
}
//test
ajax({
url: 'http://xxx',
method: 'get',
async: true,
timeout: 1000,
data: {
k1: 111,
k2: 211,
},
}).then(
(res) => console.log('请求成功: ' + res),
(err) => console.log('请求失败: ' + err),
)
event
- on(eventName, func): 监听 eventName事件, 事件触发的时候调用 func函数
- emit(eventName, arg1, arg2, arg3,arg4…) : 触发eventName 事件, 并且把参数 arg1, arg2, arg3,arg4…传给事件处理函数
- off(eventName, func) : 停止监听某个事件
addListener是emitter.on(eventName, listener) 的别名。
//26.event模块
//events只对外暴露一个对象,就是EventEmitter,EventEmitter作用只有2个,分别是:事件的发射和事件的监听。
class Event {
constructor() {
//储存事件的数据结构——字典
this.cache = new Map()
}
//绑定
on(type, func) {
//出于查找方便和节省空间将同一类型事件放到一个模拟队列的数组中
let fns = (this.cache[type] = this.cache[type] || [])
if (fns.indexOf(func) === -1) {
fns.push(func)
}
return this
}
//触发
emit(type, data) {
let fns = this.cache[type]
if (Array.isArray(fns)) {
fns.forEach((fn) => {
fn(data)
})
}
return this
}
//解绑
off(type, func) {
let fns = this.cache[type]
if (Array.isArray(fns)) {
if (func) {
let index = fns.indexOf(func)
if (index !== -1) {
fns.splice(index, 1)
}
} else {
//没指定就清空
fns.length = 0
}
}
return this
}
}
//test
const events = new Event()
events.on('test', (val) => {
console.log(val)
})
events.on('test2', (val) => {
console.log('@2' + val)
})
events.emit('test', 'runnnnn')
events.emit('test2', 'runnnnn')
events.off('test')
events.emit('test', 'none')
events.emit('test2', 'runnnnn')
图片懒加载
//27. 图片懒加载
//先设置一个临时的 data-src 属性进行src的占位
const lazyLoad = () => {
const imgs = document.querySelectorAll('img')
const len = imgs.length
//窗口高度
const viewH = document.documentElement.clientHeight
//滚动条高度
const scrollH =
//这两者有个特点:同时只有一个值生效,另一个始终为0
document.documentElement.scrollTop +
document.body.scrollTop
for (let i = 0; i < len; i++) {
const offsetH = imgs[i].offsetTop
if (offsetH < viewH + scrollH) {
const src = imgs[i].dataset.src
imgs[i].src = src
}
}
}
//使用
lazyLoad() //首屏加载
window.addEventListener('scroll', lazyLoad)
//还可以配合节流一起使用
//28. 滚动加载
window.addEventListener('scroll', () => {
const { clientHeight, scrollTop, scrollHeight } =
document.documentElement
//检测到滚动页面接近页面底部
if (clientHeight + scrollTop >= scrollHeight + 50) {
//加载后面的部分窗口
}
})
渲染大数据
//29.较大的数据量需要渲染时
//合理使用createDocumentFragment和requestAnimationFrame,将操作切分为一小段一小段执行。
setTimeout(() => {
//插入十万条数据
const total = 100000
//一次插入的数据
const once = 20
//插入数据需要的次数
const loopCount = Math.ceil(total / once)
let countOfRender = 0
const ul = document.querySelector('ul')
//添加数据
const add = () => {
//在虚拟节点对象上操作
const fragment = document.createDocumentFragment()
for (let i = 0; i < once; i++) {
const li = document.createElement('li')
li.innerText = Math.floor(Math.random * total)
fragment.appendChild(li)
}
//插入的实际操作
ul.appendChild(fragment)
countOfRender++
loop()
}
//刷新
const loop = () => {
if (countOfRender < loopCount) {
//window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
window.requestAnimationFrame(add)
}
}
loop()
}, 0)
获取当前页面html元素种类
//30 当前页面html元素种类
const printSpeciesOfHtml = () => {
return [
...new Set(),
[...document.querySelectorAll('*')].map(
(el) => el.tagName,
),
].length
}
将VirtualDom转换为真实DOM
//31.将VirtualDom 转化为真实DOM结构
// vnode结构:
// {
// tag,
// attrs,
// children,
// }
//Virtual DOM => DOM
const render = (vnode, container) => {
container.appendChild(_render(vnode))
}
const _render = (vnode) => {
//如果是数字类型就转化为字符串
if (typeof vnode === 'number ') {
vnode = String(vnode)
}
//字符串类型则直接作为文本节点
if (typeof vnode == 'string') {
return document.createTextNode(vnode)
}
//else就是
//普通DOM
const dom = document.createElement(vnode.tag)
if (vnode.attrs) {
//遍历属性
Object.keys(vnode.attrs).forEach((key) => {
const value = vnode.attrs[key]
dom.setAttribute(key, value)
})
}
//子数组进行递归操作
vnode.children.forEach((child) => render(child, dom))
return dom
}
字符串解析
//32.字符串解析问题
const a = {
z: 123,
q: '123',
}
//实现函数使得将str字符串中的{}内的变量替换,如果属性不存在保持原样(比如{a.d})
//类似于模版字符串,但有一点出入,实际上原理大差不差
const parseStr = (str, obj) => {
let res = ''
//标志位,标志前面是否有{
let flag = false
let start
for (let i = 0; i < str.length; i++) {
if (str[i] === '{') {
flag = true
start = i + 1
continue
}
if (!flag) res += str[i]
else {
if (str[i] === '}') {
flag = false
res += match(str.slice(start, i), obj)
}
}
}
return res
}
//对象匹配操作
const match = (str, obj) => {
const keys = str.split('.').slice(1)
let index = 0
let o = obj
while (index < keys.length) {
const key = keys[index]
if (!o[jey]) return `{${str}}`
else o = o[key]
index++
}
return o
}