防抖:
- 在短时间内快速的触发一件事, 每次都用新的事件替代上一次, 也就是说那么我们只会执行最后一次触发的事件 * 短时间内快速触发一件事, 只会执行最后一次
let timerID = 0
box.oninput = function (e) {
clearInterval(timerID)
timerID = setTimeout(() => {
console.log('搜索了: ', e.target.value)
}, 300)
}
// box.oninput = (() => {
// let timerID = 0
// return function (e) {
// clearInterval(timerID)
// timerID = setTimeout(() => {
// console.log('搜索了: ', e.target.value)
// }, 300)
// }
// })()
// box.oninput = ((timerID) => {
// return function (e) {
// clearInterval(timerID)
// timerID = setTimeout(() => {
// console.log('搜索了: ', e.target.value)
// }, 300)
// }
// })(0)
节流: 在短时间内快速的触发一件事, 当一事件处理函数开始执行的时候, 在此期间, 不允许重复触发 (以前的瀑布流/轮播图 都有使用节流处理)
let flag = true // 当前变量是开关变量, 默认值为 true 表示能够处理事件
box.oninput = function (e) {
if (!flag) return
flag = false
setTimeout(() => {
console.log('搜索了: ', e.target.value)
flag = true
}, 300)
}
优化1
box.oninput = (() => {
let flag = true
return function (e) {
if (!flag) return
flag = false
setTimeout(() => {
console.log('搜索了: ', e.target.value)
flag = true
}, 300)
}
})()
优化2
box.oninput = ((flag) => {
return function (e) {
if (!flag) return
flag = false
setTimeout(() => {
console.log('搜索了: ', e.target.value)
flag = true
}, 300)
}
})(true)
封装柯里化函数
- 柯里化函数, 其实还是一个函数, 只不过是将原本接收多个参数才能正常使用的函数
- 拆分为多个函数, 每个函数只接受一个参数
function test(reg) {
return function (str) {
return reg.test(str)
}
}
const res_1 = test(/^\w{4,6}$/)
console.log(res_1('QF001'))
console.log(res_1('qwe123'))
console.log(res_1('!@#$%^&*(*&^%$#@!#$%^&)'))
/**
* 函数柯里化封装
*
* fn 函数能够帮助我们拼接一个 完整的网络地址
* a --- 传输协议: http https
* b --- 域名: localhost 127.0.0.1
* c --- 端口号: 0-65535
* d --- 地址: /index.html /a/b/c/index.html
*
*
* 现在只有我们正确的传递了参数的数量才能够实现最好的拼接, 如果传递的参数数量不够也会运行函数, 但是字符串不太对
*
* 需求:
* 将当前函数处理成柯里化函数, 只有传递的参数数量足够的时候, 在执行函数内容
*/
function fn(a, b, c, d) {
return a + '://' + b + ':' + c + d
}
// console.log(fn('https', 'www.baidu.com', '8080', '/index.html'))
// console.log(fn('http', '127.0.0.1', '7777', '/a/b/c/index.html'))
/**
* 封装一个新的函数, 这个函数只有收集够指定的参数后, 再去执行指定的函数, 否则就一直收集参数
*/
// function keli(callback, ...args) {
// return function (..._args) {
// _args = [...args, ..._args]
// if (_args.length >= callback.length) {
// return callback(..._args)
// } else {
// return keli(callback, ..._args)
// }
// }
// }
// 外层函数需要收集 我们的执行函数, 以及一些基础参数
function keli(callback, ...args) {
// 内层函数负责收集参数
return function (..._args) {
// 将收集到的所有参数, 合并到一个数组中, 方便统一的维护与管理
_args = [...args, ..._args]
if (_args.length >= callback.length) {
// 判断 如果 我们收集到的参数的数量, 正好符合 callback 函数需要的参数数量, 那么我们直接执行函数即可
return callback(..._args)
} else {
// 否则说明参数数量不够, 我们应该继续收集参数
// console.log('现在参数数量不够, 我们应该继续收集参数')
// return keli()
/**
* 注意: 此时返回的不是外层函数, 而是外层函数的调用结果
*
* 所以实际上, 我们返回的是 内层函数
*/
// return keli(callback, _args) // return keli(callback, ['http', '127.0.0.1', '8080'])
return keli(callback, ..._args) // return keli(callback, 'http', '127.0.0.1', '8080')
}
}
}
/**
* 调用了 keli 函数, 传递一个 fn 函数, 以及两个 字符串, 会返回一个内层函数
*/
const res_1 = keli(fn, 'http', '127.0.0.1')
/**
* 调用了 内层函数, 并且又重新传递了一个字符串, 现在结合之前的两个字符串, 一共收集到了 3 个 字符串
*
* 所以根据内层函数里边的逻辑, 我们此时运行的 是 else 分支
*
* 内层函数中 返回了一个 keli 函数的调用结果
*
* 所以实际上我们相当于是拿到了 内层函数
*/
const res_2 = res_1('8081')
// console.log(res_2) // 内层函数
/**
* 刚才分析得出 res_2 其实就是我们的内层函数, 目前结合我现在传递的一个参数, 一共有 4 个参数了
*
* 所以此时就回调用函数, 而不是继续返回一个内层函数收集参数
*/
const str = res_2('/a.html', 10086)
console.log(str)
// const res = keli(fn, 'http', '127.0.0.1')
// const str = res('8080', '/a.html')
// console.log(str) // http://127.0.0.1:8080/a.html
// keli(fn) // []
// keli(fn, 'http') // ['http']
// keli(fn, 'http', '127.0.0.1') // ['http', '127.0.0.1']
// keli(fn, 'http', '127.0.0.1', '8989') // ['http', '127.0.0.1', '8989']
// keli(fn, 'http', '127.0.0.1', '8989', '/index.html') // ['http', '127.0.0.1', '8989', '/index.html']
数据劫持
- 于一个原本的数据, 劫持出来一个数据
- 利用这个方法, 向对象 obj 中添加一个属性
- Object.defineProperty('向那个对象中添加', '要添加的属性名是什么', '关于当前属性的一些配置项')
const obj = {}
obj.name = '张三'
obj.info = '详情'
// 数据劫持 配置项补充
// const obj = {}
// obj.name = '张三'
// obj.info = '详情'
/**
* 当前方法 添加的属性, 默认不允许被修改, 也不允许被遍历到
*
* 除非添加配置项
*/
Object.defineProperty(obj, 'age', {
// value: 18, // 你访问这个属性的时候会得到的值
// // writable: false
// writable: true, // 当前选项决定当前 属性能否被修改, 默认为 false, 不允许修改
// // enumerable: false
// enumerable: true, // 当前选项决定当前属性能否被枚举(遍历)到, 默认为 false, 不允许被枚举到
/**4
* 正常开发中我们都会选用 get 和 set 因为相比上边的我们多了一个 函数能够出发
*
* 有了这个函数之后, 我们可以做任何事情
*/
get () {
// 当你要访问这个属性的时候, 会执行, 内部的返回值就是你这个属性实际的值
console.log('你访问了 age 属性, 我就会触发')
return 'age 属性实际的值'
},
set (val) {
console.log('set触发~~~', val)
}
})
console.log(obj.age)
obj.age = 999
console.log(obj)
for (let key in obj) {
console.log(key, '==>', obj[key])
}
- 封装数据劫持
function observer (origin, cb) {
// 1. 创建一个对象用于存储数据劫持后的一些内容
const target = {}
// 2. 开始核心代码
for (let key in origin) {
// console.log(key, origin[key])
Object.defineProperty(target, key, {
get () {
return origin[key]
},
set (val) {
origin[key] = val
cb(target) // 这里到底是调用
}
})
}
// 2.5 因为当前函数页面一打开就回调用, 所以我这个位置也会在页面初次渲染的时候执行
cb(target)
// 3. 将数据劫持后的新对象返回出去
return target
}
- 02_vue花括号语法
<div id="root">
<p>使用name属性: {{name}}</p>
<p>使用age属性: {{ age}}</p>
<p>使用width属性: {{width }}</p>
<p>使用height属性: {{ height }}</p>
</div>
function createApp(options) {
// 1. 安全判断
// 1.1 options 的 el 属性必须传递
if (options.el === undefined) {
// return console.log('此时代码出现问题, 应该阻断程序运行, 并且通知开发者有问题')
throw new Error("el 选项必须传递");
}
// 1.2 options 的 data 属性必须传递, 并且只能是 对象类型
if (Object.prototype.toString.call(options.data) !== "[object Object]") {
throw new Error("data 选项必须传递, 并且只能是对象类型");
}
// 1.3 el 属性的值, 必须能够获取到 DOM 节点
const root = document.querySelector(options.el);
if (root === null) {
throw new Error(
"el 选项必须为字符串, 并且需要能够获取到当前页面的根元素"
);
}
// 2. 开始数据劫持 (核心代码)
// 创建一个对象 存储我们劫持后的数据
const _data = {};
// 开始劫持
for (let key in options.data) {
Object.defineProperty(_data, key, {
get() {
return options.data[key];
},
set(val) {
options.data[key] = val;
// 每次修改数据后, 重新渲染页面
bindHtml(root, _data, rootHtml);
},
});
}
// 存储一个最开始的标签结构
const rootHtml = root.innerHTML;
// 首次打开页面就回执行这个地方的代码, 调用渲染函数
bindHtml(root, _data, rootHtml);
// 返回劫持好的对象
return _data;
}
/**
* 三个形参的拼写不重要
*
* 第一个: 去哪个标签内修改
* 第二个: 变量对应的数据是什么
* 第三个: 原本默认的 html 结构, 防止初次选然后找不到 {{}}
*/
function bindHtml(root, _data, _str) {
// 1. 创建正则, 用于捕获出字符串中所有的 {{XXX}}
// const reg = /{{name}}/
// const reg = /{{\w+}}/
// const reg = /{{(\w+)}}/
// const reg = /{{ *(\w+) *}}/
const reg = /{{ *(\w+) *}}/g;
// 2. 利用字符串的方法, 将所有符合规则的部分捕获到一个数组中
const arr = _str.match(reg);
// console.log(arr)
// 3. 遍历数组, 拿到所有的字符串, 然后对字符串进行后续的操作
arr.forEach((item) => {
const key = /{{ *(\w+) *}}/g.exec(item)[1];
_str = _str.replace(/{{ *(\w+) *}}/, _data[key])
})
root.innerHTML = _str
}
const app = createApp({
el: '#root',
data: {
name: '张三',
age: 18,
width: 10086,
height: 10010
},
})
const inp = document.querySelector('#inp')
inp.oninput = function () {
app.age = this.value
}
- 第二种方式
- Object.defineProperties('劫持到那个对象', 属性1: {属性1的配置项},)
Object.defineProperties('劫持到那个对象', {
属性1: {属性1的配置项},
属性2: {属性2的配置项},
属性3: {属性3的配置项},
})
数据代理
- 利用 ES6 新推的一个内置构造函数 proxy
const obj = {
name: 'QF001',
age: 18
}
// console.log('源对象: ', obj)
const res = new Proxy(obj, {
get(target, property) {
/**
* 形参的拼写无所谓
*
* 第一个形参: 我们的代理对象
* 第二个形参: 我们的代理对象中的某一个属性
*/
return target[property]
},
set(target, property, val) {
target[property] = val
},
})
// 如果我们使用了 数据代理, 那么在代理完毕后, 给源对象中添加一个新的数据, 那么也会被自动代理 (由 proxy 帮助我们完成)
obj.qwe = 100
console.log(res.name)
res.age = 10086
// res.name = 'QF002'
console.log(res)
// console.log(res.age)