實(shí)現(xiàn)千位分隔符
// 保留三位小數(shù)
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'
function parseToMoney(num) {
num = parseFloat(num.toFixed(3));
let [integer, decimal] = String.prototype.split.call(num, '.');
integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
return integer + '.' + (decimal ? decimal : '');
}
正則表達(dá)式(運(yùn)用了正則的前向聲明和反前向聲明):
function parseToMoney(str){
// 僅僅對(duì)位置進(jìn)行匹配
let re = /(?=(?!\b)(\d{3})+$)/g;
return str.replace(re,',');
}
將js對(duì)象轉(zhuǎn)化為樹(shù)形結(jié)構(gòu)
// 轉(zhuǎn)換前:
source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 轉(zhuǎn)換為:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 1,
name: 'div'
}]
}
}]
代碼實(shí)現(xiàn):
function jsonToTree(data) {
// 初始化結(jié)果數(shù)組,并判斷輸入數(shù)據(jù)的格式
let result = []
if(!Array.isArray(data)) {
return result
}
// 使用map,將當(dāng)前對(duì)象的id與當(dāng)前對(duì)象對(duì)應(yīng)存儲(chǔ)起來(lái)
let map = {};
data.forEach(item => {
map[item.id] = item;
});
//
data.forEach(item => {
let parent = map[item.pid];
if(parent) {
(parent.children || (parent.children = [])).push(item);
} else {
result.push(item);
}
});
return result;
}
循環(huán)打印紅黃綠
下面來(lái)看一道比較典型的問(wèn)題,通過(guò)這個(gè)問(wèn)題來(lái)對(duì)比幾種異步編程方法:紅燈 3s 亮一次,綠燈 1s 亮一次,黃燈 2s 亮一次;如何讓三個(gè)燈不斷交替重復(fù)亮燈?
三個(gè)亮燈函數(shù):
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
這道題復(fù)雜的地方在于需要“交替重復(fù)”亮燈,而不是“亮完一次”就結(jié)束了。
(1)用 callback 實(shí)現(xiàn)
const task = (timer, light, callback) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
callback()
}, timer)
}
task(3000, 'red', () => {
task(2000, 'green', () => {
task(1000, 'yellow', Function.prototype)
})
})
這里存在一個(gè) bug:代碼只是完成了一次流程,執(zhí)行后紅黃綠燈分別只亮一次。該如何讓它交替重復(fù)進(jìn)行呢?
上面提到過(guò)遞歸,可以遞歸亮燈的一個(gè)周期:
const step = () => {
task(3000, 'red', () => {
task(2000, 'green', () => {
task(1000, 'yellow', step)
})
})
}
step()
注意看黃燈亮的回調(diào)里又再次調(diào)用了 step 方法 以完成循環(huán)亮燈。
(2)用 promise 實(shí)現(xiàn)
const task = (timer, light) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
const step = () => {
task(3000, 'red')
.then(() => task(2000, 'green'))
.then(() => task(2100, 'yellow'))
.then(step)
}
step()
這里將回調(diào)移除,在一次亮燈結(jié)束后,resolve 當(dāng)前 promise,并依然使用遞歸進(jìn)行。
(3)用 async/await 實(shí)現(xiàn)
const taskRunner = async () => {
await task(3000, 'red')
await task(2000, 'green')
await task(2100, 'yellow')
taskRunner()
}
taskRunner()
實(shí)現(xiàn)一個(gè)call
call做了什么:
- 將函數(shù)設(shè)為對(duì)象的屬性
- 執(zhí)行&刪除這個(gè)函數(shù)
- 指定this到函數(shù)并傳入給定參數(shù)執(zhí)行函數(shù)
- 如果不傳入?yún)?shù),默認(rèn)指向?yàn)?window
// 模擬 call bar.mycall(null);
//實(shí)現(xiàn)一個(gè)call方法:
Function.prototype.myCall = function(context) {
//此處沒(méi)有考慮context非object情況
context.fn = this;
let args = [];
for (let i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
context.fn(...args);
let result = context.fn(...args);
delete context.fn;
return result;
};
手寫(xiě)類(lèi)型判斷函數(shù)
function getType(value) {
// 判斷數(shù)據(jù)是 null 的情況
if (value === null) {
return value + "";
}
// 判斷數(shù)據(jù)是引用類(lèi)型的情況
if (typeof value === "object") {
let valueClass = Object.prototype.toString.call(value),
type = valueClass.split(" ")[1].split("");
type.pop();
return type.join("").toLowerCase();
} else {
// 判斷數(shù)據(jù)是基本數(shù)據(jù)類(lèi)型的情況和函數(shù)的情況
return typeof value;
}
}
實(shí)現(xiàn)JSON.parse
var json = '{"name":"cxk", "age":25}';
var obj = eval("(" + json + ")");
此方法屬于黑魔法,極易容易被xss攻擊,還有一種new Function大同小異。
判斷對(duì)象是否存在循環(huán)引用
循環(huán)引用對(duì)象本來(lái)沒(méi)有什么問(wèn)題,但是序列化的時(shí)候就會(huì)發(fā)生問(wèn)題,比如調(diào)用JSON.stringify()對(duì)該類(lèi)對(duì)象進(jìn)行序列化,就會(huì)報(bào)錯(cuò): Converting circular structure to JSON.
下面方法可以用來(lái)判斷一個(gè)對(duì)象中是否已存在循環(huán)引用:
const isCycleObject = (obj,parent) => {
const parentArr = parent || [obj];
for(let i in obj) {
if(typeof obj[i] === 'object') {
let flag = false;
parentArr.forEach((pObj) => {
if(pObj === obj[i]){
flag = true;
}
})
if(flag) return true;
flag = isCycleObject(obj[i],[...parentArr,obj[i]]);
if(flag) return true;
}
}
return false;
}
const a = 1;
const b = {a};
const c = ;
const o = {d:{a:3},c}
o.c.b.aa = a;
console.log(isCycleObject(o)
查找有序二維數(shù)組的目標(biāo)值:
var findNumberIn2DArray = function(matrix, target) {
if (matrix == null || matrix.length == 0) {
return false;
}
let row = 0;
let column = matrix[0].length - 1;
while (row < matrix.length && column >= 0) {
if (matrix[row][column] == target) {
return true;
} else if (matrix[row][column] > target) {
column--;
} else {
row++;
}
}
return false;
};
二維數(shù)組斜向打?。?/p>
function printMatrix(arr){
let m = arr.length, n = arr[0].length
let res = []
// 左上角,從0 到 n - 1 列進(jìn)行打印
for (let k = 0; k < n; k++) {
for (let i = 0, j = k; i < m && j >= 0; i++, j--) {
res.push(arr[i][j]);
}
}
// 右下角,從1 到 n - 1 行進(jìn)行打印
for (let k = 1; k < m; k++) {
for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {
res.push(arr[i][j]);
}
}
return res
}
模板引擎實(shí)現(xiàn)
let template = '我是{{name}},年齡{{age}},性別{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年齡18,性別undefined
function render(template, data) {
const reg = /\{\{(\w+)\}\}/; // 模板字符串正則
if (reg.test(template)) { // 判斷模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找當(dāng)前模板里第一個(gè)模板字符串的字段
template = template.replace(reg, data[name]); // 將第一個(gè)模板字符串渲染
return render(template, data); // 遞歸的渲染并返回渲染后的結(jié)構(gòu)
}
return template; // 如果模板沒(méi)有模板字符串直接返回
}
函數(shù)珂里化
指的是將一個(gè)接受多個(gè)參數(shù)的函數(shù) 變?yōu)?接受一個(gè)參數(shù)返回一個(gè)函數(shù)的固定形式,這樣便于再次調(diào)用,例如f(1)(2)
經(jīng)典面試題:實(shí)現(xiàn)add(1)(2)(3)(4)=10; 、 add(1)(1,2,3)(2)=9;
function add() {
const _args = [...arguments];
function fn() {
_args.push(...arguments);
return fn;
}
fn.toString = function() {
return _args.reduce((sum, cur) => sum + cur);
}
return fn;
}
實(shí)現(xiàn)深拷貝
- 淺拷貝: 淺拷貝指的是將一個(gè)對(duì)象的屬性值復(fù)制到另一個(gè)對(duì)象,如果有的屬性的值為引用類(lèi)型的話(huà),那么會(huì)將這個(gè)引用的地址復(fù)制給對(duì)象,因此兩個(gè)對(duì)象會(huì)有同一個(gè)引用類(lèi)型的引用。淺拷貝可以使用 Object.assign 和展開(kāi)運(yùn)算符來(lái)實(shí)現(xiàn)。
- 深拷貝: 深拷貝相對(duì)淺拷貝而言,如果遇到屬性值為引用類(lèi)型的時(shí)候,它新建一個(gè)引用類(lèi)型并將對(duì)應(yīng)的值復(fù)制給它,因此對(duì)象獲得的一個(gè)新的引用類(lèi)型而不是一個(gè)原有類(lèi)型的引用。深拷貝對(duì)于一些對(duì)象可以使用 JSON 的兩個(gè)函數(shù)來(lái)實(shí)現(xiàn),但是由于 JSON 的對(duì)象格式比 js 的對(duì)象格式更加嚴(yán)格,所以如果屬性值里邊出現(xiàn)函數(shù)或者 Symbol 類(lèi)型的值時(shí),會(huì)轉(zhuǎn)換失敗
(1)JSON.stringify()
-
JSON.parse(JSON.stringify(obj))是目前比較常用的深拷貝方法之一,它的原理就是利用JSON.stringify將js對(duì)象序列化(JSON字符串),再使用JSON.parse來(lái)反序列化(還原)js對(duì)象。 - 這個(gè)方法可以簡(jiǎn)單粗暴的實(shí)現(xiàn)深拷貝,但是還存在問(wèn)題,拷貝的對(duì)象中如果有函數(shù),undefined,symbol,當(dāng)使用過(guò)
JSON.stringify()進(jìn)行處理之后,都會(huì)消失。
let obj1 = { a: 0,
b: {
c: 0
}
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}
(2)函數(shù)庫(kù)lodash的_.cloneDeep方法
該函數(shù)庫(kù)也有提供_.cloneDeep用來(lái)做 Deep Copy
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
(3)手寫(xiě)實(shí)現(xiàn)深拷貝函數(shù)
// 深拷貝的實(shí)現(xiàn)
function deepCopy(object) {
if (!object || typeof object !== "object") return;
let newObject = Array.isArray(object) ? [] : {};
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] =
typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
}
}
return newObject;
}
實(shí)現(xiàn)數(shù)組的flat方法
function _flat(arr, depth) {
if(!Array.isArray(arr) || depth <= 0) {
return arr;
}
return arr.reduce((prev, cur) => {
if (Array.isArray(cur)) {
return prev.concat(_flat(cur, depth - 1))
} else {
return prev.concat(cur);
}
}, []);
}
Object.assign
Object.assign()方法用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象復(fù)制到目標(biāo)對(duì)象。它將返回目標(biāo)對(duì)象(請(qǐng)注意這個(gè)操作是淺拷貝)
Object.defineProperty(Object, 'assign', {
value: function(target, ...args) {
if (target == null) {
return new TypeError('Cannot convert undefined or null to object');
}
// 目標(biāo)對(duì)象需要統(tǒng)一是引用數(shù)據(jù)類(lèi)型,若不是會(huì)自動(dòng)轉(zhuǎn)換
const to = Object(target);
for (let i = 0; i < args.length; i++) {
// 每一個(gè)源對(duì)象
const nextSource = args[i];
if (nextSource !== null) {
// 使用for...in和hasOwnProperty雙重判斷,確保只拿到本身的屬性、方法(不包含繼承的)
for (const nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
// 不可枚舉
enumerable: false,
writable: true,
configurable: true,
})
驗(yàn)證是否是郵箱
function isEmail(email) {
var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;
return regx.test(email);
}
手寫(xiě) new 操作符
在調(diào)用 new 的過(guò)程中會(huì)發(fā)生以上四件事情:
(1)首先創(chuàng)建了一個(gè)新的空對(duì)象
(2)設(shè)置原型,將對(duì)象的原型設(shè)置為函數(shù)的 prototype 對(duì)象。
(3)讓函數(shù)的 this 指向這個(gè)對(duì)象,執(zhí)行構(gòu)造函數(shù)的代碼(為這個(gè)新對(duì)象添加屬性)
(4)判斷函數(shù)的返回值類(lèi)型,如果是值類(lèi)型,返回創(chuàng)建的對(duì)象。如果是引用類(lèi)型,就返回這個(gè)引用類(lèi)型的對(duì)象。
function objectFactory() {
let newObject = null;
let constructor = Array.prototype.shift.call(arguments);
let result = null;
// 判斷參數(shù)是否是一個(gè)函數(shù)
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一個(gè)空對(duì)象,對(duì)象的原型為構(gòu)造函數(shù)的 prototype 對(duì)象
newObject = Object.create(constructor.prototype);
// 將 this 指向新建對(duì)象,并執(zhí)行函數(shù)
result = constructor.apply(newObject, arguments);
// 判斷返回對(duì)象
let flag = result && (typeof result === "object" || typeof result === "function");
// 判斷返回結(jié)果
return flag ? result : newObject;
}
// 使用方法
objectFactory(構(gòu)造函數(shù), 初始化參數(shù));
實(shí)現(xiàn)AJAX請(qǐng)求
AJAX是 Asynchronous JavaScript and XML 的縮寫(xiě),指的是通過(guò) JavaScript 的 異步通信,從服務(wù)器獲取 XML 文檔從中提取數(shù)據(jù),再更新當(dāng)前網(wǎng)頁(yè)的對(duì)應(yīng)部分,而不用刷新整個(gè)網(wǎng)頁(yè)。
創(chuàng)建AJAX請(qǐng)求的步驟:
- 創(chuàng)建一個(gè) XMLHttpRequest 對(duì)象。
- 在這個(gè)對(duì)象上使用 open 方法創(chuàng)建一個(gè) HTTP 請(qǐng)求,open 方法所需要的參數(shù)是請(qǐng)求的方法、請(qǐng)求的地址、是否異步和用戶(hù)的認(rèn)證信息。
- 在發(fā)起請(qǐng)求前,可以為這個(gè)對(duì)象添加一些信息和監(jiān)聽(tīng)函數(shù)。比如說(shuō)可以通過(guò) setRequestHeader 方法來(lái)為請(qǐng)求添加頭信息。還可以為這個(gè)對(duì)象添加一個(gè)狀態(tài)監(jiān)聽(tīng)函數(shù)。一個(gè) XMLHttpRequest 對(duì)象一共有 5 個(gè)狀態(tài),當(dāng)它的狀態(tài)變化時(shí)會(huì)觸發(fā)onreadystatechange 事件,可以通過(guò)設(shè)置監(jiān)聽(tīng)函數(shù),來(lái)處理請(qǐng)求成功后的結(jié)果。當(dāng)對(duì)象的 readyState 變?yōu)?4 的時(shí)候,代表服務(wù)器返回的數(shù)據(jù)接收完成,這個(gè)時(shí)候可以通過(guò)判斷請(qǐng)求的狀態(tài),如果狀態(tài)是 2xx 或者 304 的話(huà)則代表返回正常。這個(gè)時(shí)候就可以通過(guò) response 中的數(shù)據(jù)來(lái)對(duì)頁(yè)面進(jìn)行更新了。
- 當(dāng)對(duì)象的屬性和監(jiān)聽(tīng)函數(shù)設(shè)置完成后,最后調(diào)用 sent 方法來(lái)向服務(wù)器發(fā)起請(qǐng)求,可以傳入?yún)?shù)作為發(fā)送的數(shù)據(jù)體。
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 創(chuàng)建 Http 請(qǐng)求
xhr.open("GET", SERVER_URL, true);
// 設(shè)置狀態(tài)監(jiān)聽(tīng)函數(shù)
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 當(dāng)請(qǐng)求成功時(shí)
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 設(shè)置請(qǐng)求失敗時(shí)的監(jiān)聽(tīng)函數(shù)
xhr.onerror = function() {
console.error(this.statusText);
};
// 設(shè)置請(qǐng)求頭信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 發(fā)送 Http 請(qǐng)求
xhr.send(null);
使用Promise封裝AJAX請(qǐng)求
// promise 封裝實(shí)現(xiàn):
function getJSON(url) {
// 創(chuàng)建一個(gè) promise 對(duì)象
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
// 新建一個(gè) http 請(qǐng)求
xhr.open("GET", url, true);
// 設(shè)置狀態(tài)的監(jiān)聽(tīng)函數(shù)
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 當(dāng)請(qǐng)求成功或失敗時(shí),改變 promise 的狀態(tài)
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
// 設(shè)置錯(cuò)誤監(jiān)聽(tīng)函數(shù)
xhr.onerror = function() {
reject(new Error(this.statusText));
};
// 設(shè)置響應(yīng)的數(shù)據(jù)類(lèi)型
xhr.responseType = "json";
// 設(shè)置請(qǐng)求頭信息
xhr.setRequestHeader("Accept", "application/json");
// 發(fā)送 http 請(qǐng)求
xhr.send(null);
});
return promise;
}
判斷括號(hào)字符串是否有效(小米)
題目描述
給定一個(gè)只包括 '(',')','{','}','[',']' 的字符串 s ,判斷字符串是否有效。
有效字符串需滿(mǎn)足:
- 左括號(hào)必須用相同類(lèi)型的右括號(hào)閉合。
- 左括號(hào)必須以正確的順序閉合。
示例 1:
輸入:s = "()"
輸出:true
示例 2:
輸入:s = "()[]{}"
輸出:true
示例 3:
輸入:s = "(]"
輸出:false
答案
const isValid = function (s) {
if (s.length % 2 === 1) {
return false;
}
const regObj = {
"{": "}",
"(": ")",
"[": "]",
};
let stack = [];
for (let i = 0; i < s.length; i++) {
if (s[i] === "{" || s[i] === "(" || s[i] === "[") {
stack.push(s[i]);
} else {
const cur = stack.pop();
if (s[i] !== regObj[cur]) {
return false;
}
}
}
if (stack.length) {
return false;
}
return true;
};
數(shù)組去重方法匯總
首先:我知道多少種去重方式
1. 雙層 for 循環(huán)
function distinct(arr) {
for (let i=0, len=arr.length; i<len; i++) {
for (let j=i+1; j<len; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
// splice 會(huì)改變數(shù)組長(zhǎng)度,所以要將數(shù)組長(zhǎng)度 len 和下標(biāo) j 減一
len--;
j--;
}
}
}
return arr;
}
思想: 雙重
for循環(huán)是比較笨拙的方法,它實(shí)現(xiàn)的原理很簡(jiǎn)單:先定義一個(gè)包含原始數(shù)組第一個(gè)元素的數(shù)組,然后遍歷原始數(shù)組,將原始數(shù)組中的每個(gè)元素與新數(shù)組中的每個(gè)元素進(jìn)行比對(duì),如果不重復(fù)則添加到新數(shù)組中,最后返回新數(shù)組;因?yàn)樗臅r(shí)間復(fù)雜度是O(n^2),如果數(shù)組長(zhǎng)度很大,效率會(huì)很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) {
let arr = a.concat(b);
return arr.filter((item, index)=> {
//return arr.indexOf(item) === index
return arr.includes(item)
})
}
思想: 利用
indexOf檢測(cè)元素在數(shù)組中第一次出現(xiàn)的位置是否和元素現(xiàn)在的位置相等,如果不等則說(shuō)明該元素是重復(fù)元素
3. ES6 中的 Set 去重
function distinct(array) {
return Array.from(new Set(array));
}
思想: ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set,Set 結(jié)構(gòu)的一個(gè)特性就是成員值都是唯一的,沒(méi)有重復(fù)的值。
4. reduce 實(shí)現(xiàn)對(duì)象數(shù)組去重復(fù)
var resources = [
{ name: "張三", age: "18" },
{ name: "張三", age: "19" },
{ name: "張三", age: "20" },
{ name: "李四", age: "19" },
{ name: "王五", age: "20" },
{ name: "趙六", age: "21" }
]
var temp = {};
resources = resources.reduce((prev, curv) => {
// 如果臨時(shí)對(duì)象中有這個(gè)名字,什么都不做
if (temp[curv.name]) {
}else {
// 如果臨時(shí)對(duì)象沒(méi)有就把這個(gè)名字加進(jìn)去,同時(shí)把當(dāng)前的這個(gè)對(duì)象加入到prev中
temp[curv.name] = true;
prev.push(curv);
}
return prev
}, []);
console.log("結(jié)果", resources);
這種方法是利用高階函數(shù)
reduce進(jìn)行去重, 這里只需要注意initialValue得放一個(gè)空數(shù)組[],不然沒(méi)法push
實(shí)現(xiàn)類(lèi)數(shù)組轉(zhuǎn)化為數(shù)組
類(lèi)數(shù)組轉(zhuǎn)換為數(shù)組的方法有這樣幾種:
- 通過(guò) call 調(diào)用數(shù)組的 slice 方法來(lái)實(shí)現(xiàn)轉(zhuǎn)換
Array.prototype.slice.call(arrayLike);
- 通過(guò) call 調(diào)用數(shù)組的 splice 方法來(lái)實(shí)現(xiàn)轉(zhuǎn)換
Array.prototype.splice.call(arrayLike, 0);
- 通過(guò) apply 調(diào)用數(shù)組的 concat 方法來(lái)實(shí)現(xiàn)轉(zhuǎn)換
Array.prototype.concat.apply([], arrayLike);
- 通過(guò) Array.from 方法來(lái)實(shí)現(xiàn)轉(zhuǎn)換
Array.from(arrayLike);
實(shí)現(xiàn)Vue reactive響應(yīng)式
// Dep module
class Dep {
static stack = []
static target = null
deps = null
constructor() {
this.deps = new Set()
}
depend() {
if (Dep.target) {
this.deps.add(Dep.target)
}
}
notify() {
this.deps.forEach(w => w.update())
}
static pushTarget(t) {
if (this.target) {
this.stack.push(this.target)
}
this.target = t
}
static popTarget() {
this.target = this.stack.pop()
}
}
// reactive
function reactive(o) {
if (o && typeof o === 'object') {
Object.keys(o).forEach(k => {
defineReactive(o, k, o[k])
})
}
return o
}
function defineReactive(obj, k, val) {
let dep = new Dep()
Object.defineProperty(obj, k, {
get() {
dep.depend()
return val
},
set(newVal) {
val = newVal
dep.notify()
}
})
if (val && typeof val === 'object') {
reactive(val)
}
}
// watcher
class Watcher {
constructor(effect) {
this.effect = effect
this.update()
}
update() {
Dep.pushTarget(this)
this.value = this.effect()
Dep.popTarget()
return this.value
}
}
// 測(cè)試代碼
const data = reactive({
msg: 'aaa'
})
new Watcher(() => {
console.log('===> effect', data.msg);
})
setTimeout(() => {
data.msg = 'hello'
}, 1000)