1つ:Vueカスタム命令の遅延読み込みを実装します。
Intersection Observer APIは、ターゲット要素と祖先要素またはトップレベルドキュメントのビューポートとの交差の変化を非同期的に監視する方法を提供します。
IntersectionObserverオブジェクトを作成し、対応するパラメーターを渡して関数をコールバックします。コールバック関数は、ターゲット要素とルート要素の交差のサイズがしきい値を超えたときに実行されます。
var observer = new IntersectionObserver(callback, options);
IntersectionObserverは、ブラウザーによって提供されるコンストラクターであり、2つのパラメーターを受け入れます。callbackは、可視性が変化したときのコールバック関数です(つまり、コールバック関数は、ターゲット要素がルートオプションで指定された要素に表示されたときに実行されます)。は構成オブジェクトです(このパラメーターはオプションです)。
返されるの observer
はオブザーバーインスタンスです。インスタンスのobserveメソッドは、監視するDOMノードを指定できます。
Vueカスタム手順
次のAPIは、公式Webサイトのカスタム手順に基づいています。
フック機能
-
bind:命令が初めて要素にバインドされるときに、1回だけ呼び出されます。ここでは、1回限りの初期化設定を実行できます。
-
挿入:バインドされた要素が親ノードに挿入されたときに呼び出されます(親ノードのみが存在することが保証されていますが、ドキュメントに挿入されていない可能性があります)。
-
update:コンポーネントのVNodeが更新されたときに呼び出されますが、子VNodeが更新される前に発生する可能性があります。命令の値は変更されている場合と変更されていない場合があります。ただし、更新の前後の値を比較することで、不要なテンプレートの更新を無視できます
-
componentUpdated:命令が配置されているコンポーネントのVNodeとそのサブVNodeがすべて更新された後に呼び出されます。
-
unbind:命令が要素からバインド解除されたときに、1回だけ呼び出されます。
フック機能パラメータ
命令フック関数には、次のパラメータが渡されます。
-
el:命令によってバインドされた要素を使用して、DOMを直接操作できます。
-
バインディング:次のプロパティを含むオブジェクト:
-
name:v-プレフィックスを除くコマンドの名前。
-
value:命令のバインディング値。例:v-my-directive = "1 + 1"、バインディング値は2です。
-
oldValue:命令によってバインドされた以前の値で、updateフックとcomponentUpdatedフックでのみ使用できます。値が変更されているかどうかに関係なく使用できます。
-
式:文字列形式のコマンド式。たとえば、v-my-directive = "1 + 1"では、式は "1 +1"です。
-
arg:命令に渡されるパラメーター。オプション。たとえば、v-my-directive:fooでは、パラメーターは「foo」です。
-
モディファイア:モディファイアを含むオブジェクト。例:v-my-directive.foo.barでは、修飾子オブジェクトは{foo:true、bar:true}です。
-
-
vnode:Vueコンパイルによって生成された仮想ノード。
-
oldVnode:最後の仮想ノード。updateフックとcomponentUpdatedフックでのみ使用できます。
v-lazyload命令を実装します
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
img {
width: 100%;
height: 300px;
}
</style>
</head>
<body>
<div id="app">
<p v-for="item in imgs" :key="item">
<img v-lazyload="item">
</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
Vue.directive("lazyload", {
// 指令的定义
bind: function(el, binding) {
let lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
let lazyImage = entry.target;
// 相交率,默认是相对于浏览器视窗
if(entry.intersectionRatio > 0) {
lazyImage.src = binding.value;
// 当前图片加载完之后需要去掉监听
lazyImageObserver.unobserve(lazyImage);
}
})
})
lazyImageObserver.observe(el);
},
});
var app = new Vue({
el: "#app",
data: {
imgs: [
'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657907683.jpeg',
'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657913523.jpeg',
'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657925550.jpeg',
'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657930289.jpeg',
'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657934750.jpeg',
'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657918315.jpeg',
]
},
});
</script>
</html>
2:HTMLタグで最も頻繁に発生するタグを数えます
const tags = document.getElementsByTagName('*');
let map = new Map();
let maxStr = '';
let max = 0;
// 只是使用下标来获取,没有使用数组的方法,所以不需要将类数组转为数组
for(let i = 0; i < tags.length; i++) {
let value = map.get(tags[i].tagName)
if(value) {
map.set(tags[i].tagName, ++value)
} else {
map.set(tags[i].tagName, 1);
}
if(value > max) {
maxStr = tags[i].tagName;
max = value;
}
}
console.log(`当前最多的标签为 ${maxStr},个数为 ${max}` );
3:単にPromiseを実装する
class Promise {
constructor(fn) {
/**
* 三种状态
* pending:进行中
* fulfilled:已成功
* rejected: 已失败
*/
this.status = 'pending';
this.resoveList = []; // 成功后回调函数
this.rejectList = []; // 失败后的回调函数
fn(this.resolve.bind(this), this.reject.bind(this));
}
then(scb, fcb) {
if (scb) {
this.resoveList.push(scb);
}
if(fcb) {
this.rejectList.push(fcb);
}
return this;
}
catch(cb) {
if (cb) {
this.rejectList.push(cb);
}
return this;
}
resolve(data) {
if (this.status !== 'pending') return;
this.status = 'fulfilled';
setTimeout(() => {
this.resoveList.forEach(s => {
data = s(data);
})
})
}
reject(err) {
if (this.status !== 'pending') return;
this.status = 'rejected';
setTimeout(() => {
this.rejectList.forEach(f => {
err = f(err);
})
})
}
/**
* 实现Promise.resolve
* 1.参数是一个 Promise 实例, 那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
* 2.如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
*/
static resolve(data) {
if (data instanceof Promise) {
return data;
} else {
return new Promise((resolve, reject) => {
resolve(data);
})
}
}
// 实现Promise.reject
static reject(err) {
if (err instanceof Promise) {
return err;
} else {
return new Promise((resolve, reject) => {
reject(err);
})
}
}
/**
* 实现Promise.all
* 1. Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
* 2. 返回值组成一个数组
*/
static all(promises) {
return new Promise((resolve, reject) => {
let promiseCount = 0;
let promisesLength = promises.length;
let result = [];
for(let i = 0; i < promises.length; i++) {
// promises[i]可能不是Promise类型,可能不存在then方法,中间如果出错,直接返回错误
Promise.resolve(promises[i])
.then(res => {
promiseCount++;
// 注意这是赋值应该用下标去赋值而不是用push,因为毕竟是异步的,哪个promise先完成还不一定
result[i] = res;
if(promiseCount === promisesLength) {
return resolve(result);
}
},(err) => {
return reject(err);
}
)
}
})
}
/**
* 实现Promise.race
* 1. Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
* 2. 返回那个率先改变的 Promise 实例的返回值
*/
static race(promises) {
return new Promise((resolve, reject) => {
for(let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i])
.then(res => {
return resolve(res);
},(err) =>{
return reject(err);
}
)
}
})
}
}
テストケース
1. Promise.then
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('resolve');
resolve(222);
}, 1000)
})
p.then(data => {
setTimeout(() => {
console.log('data', data);
})
return 3333;
}).then(data2 => {
console.log('data2', data2);
}).catch(err => {
console.error('err', err);
});
2. Promise.reject
const p1 = Promise.reject('出错了');
p1.then(null, function (s) {
console.log(s); // 出错了
});
3. Promise.all && Promise.race
const q1 = new Promise((resolve, reject) => {
resolve('hello')
});
const q2 = new Promise((resolve, reject) => {
resolve('world')
});
Promise.all([q1, q2]).then(res => {
console.log(res); // [ 'hello', 'world' ]
});
Promise.race([q1, q2]).then(res => {
console.log(res); // hello
});
4:防振とスロットル機能
関数デバウンス(デバウンス)
アンチシェイク:イベントトリガーの頻度がいくら高くても、イベントがトリガーされてからn秒後に実行する必要があります。イベントの実行からn秒以内にイベントがトリガーされた場合、新しいイベントの時間が優先されます。そして、n秒後に実行されます。つまり、イベントは、イベントがトリガーされてからn秒以内にトリガーされず、n秒後に再度実行されます。
アイデア:
-
関数を返します。
-
イベントがトリガーされるたびに前のタイマーをキャンセルします
問題に注意を払う必要があります:
-
これは
-
パラメータの受け渡し
-
すぐに電話しますか
function debounce(fn, wait, immediate) {
let timer = null;
// 返回一个函数
return function(...args) {
// 每次触发事件时都取消之前的定时器
clearTimeout(timer);
// 判断是否要立即执行一次
if(immediate && !timer) {
fn.apply(this, args);
}
// setTimeout中使用箭头函数,就是让 this指向 返回的该闭包函数,而不是 debounce函数的调用者
timer = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
クロージャーを介してマーカー(タイマー)を保存し、setTimeoutによって返される値を保存します。関数をトリガーする場合は、最初に前のsetTimeoutをクリアしてから、新しいsetTimeoutを作成する必要があります。これにより、関数が実行されます。間隔中に関数がトリガーされる場合、fnは実行されません。
使用するシーン
-
サイズ変更またはスクロールを監視し、いくつかのビジネス処理ロジックを実行します
window.addEventListener('resize', debounce(handleResize, 200));
window.addEventListener('scroll', debounce(handleScroll, 200));
いくつかの高周波トリガー機能が使用されており、手ぶれ防止を考慮する必要があります
-
ウィンドウのサイズ変更、スクロール
-
マウスダウン、mousemove
-
キーアップ、キーダウン..。
-
入力ボックスを検索し、入力後200ミリ秒で検索します
debounce(fetchSearchData, 200);
メモリは次のように解釈できます。アンチシェイク機能は 、スクロールイベントとサイズ変更イベントをリッスンしているときにトリガーイベントのn秒後にのみ実行され、実行時にトリガーscroll
や resize
時間を必要としないたびに、n秒後 に実行されます。 n秒以内の実行は無意味です(ユーザーはそれを感じないかもしれず、フリーズを引き起こしやすいです)。
機能スロットル(スロットル)
機能調整:イベントトリガーの頻度がいくら高くても、単位時間に1回だけ実行されます。
それを実現する2つの方法があります:タイムスタンプとタイマーを使用する
タイムスタンプを使用する
function throttle(fn, wait) {
// 记录上一次执行的时间戳
let previous = 0;
return function(...args) {
// 当前的时间戳,然后减去之前的时间戳,大于设置的时间间隔,就执行函数,否则不执行
if(Date.now() - previous > wait) {
// 更新上一次的时间戳为当前时间戳
previous = Date.now();
fn.apply(this, args);
}
}
}
最初のイベントをトリガーする必要があり、最後にトリガーされない場合(たとえば、マウスの移動が停止すると、トリガーイベントはすぐに停止します)
タイマーを使う
function throttle(fn, wait) {
// 设置一个定时器
let timer = null;
return function(...args) {
// 判断如果定时器不存在就执行,存在则不执行
if(!timer) {
// 设置下一个定时器
timer = setTimeout(() => {
// 然后执行函数,清空定时器
timer = null;
fn.apply(this, args)
}, wait)
}
}
}
最初のイベントはトリガーされません(fnはsetTimeoutで実行されるため、最初のトリガーイベントは、実行される前に少なくとも数ミリ秒待機します)、最後にトリガーする必要があります
タイマーとタイムスタンプの組み合わせ
2つの組み合わせを実現でき、最初のイベントがトリガーされ、最後のイベントもトリガーされます
function throttle(fn, wait) {
// 记录上一次执行的时间戳
let previous = 0;
// 设置一个定时器
let timer = null;
return function(...args) {
// 当前的时间戳,然后减去之前的时间戳,大于设置的时间间隔
if(Date.now() - previous > wait) {
clearTimeout(timer);
timer = null
// 更新上一次的时间戳为当前时间戳
previous = Date.now();
fn.apply(this, args);
} else if(!timer) {
// 设置下一个定时器
timer = setTimeout(() => {
timer = null;
fn.apply(this, args)
}, wait)
}
}
}
5:vueの双方向バインディングを実現する
// vue 2.x双向绑定
var obj = {};
var demo = document.getElementById('aa');
var inp = document.getElementById('input');
Object.defineProperty(obj,'txt',{
get:function(val){
return val;
},
set:function(newVal){
inp.value = newVal;
demo.innerHTML = newVal;
}
})
document.addEventListener('input', function(e){
obj.txt = e.target.value;
})
// vue 3.x双向绑定
var obj = {};
var obj1 = new Proxy(obj, {
// target就是第一个参数obj, receive就是返回的obj(返回的proxy对象)
get: function (target, key, receive) {
// 返回该属性值
return target[key];
},
set: function (target, key, newVal, receive) {
// 执行赋值操作
target[key] = newVal;
document.getElementById('text').innerHTML = target[key];
}
})
document.addEventListener('keyup', function (e) {
obj1[0] = e.target.value;
});