思路:將傳入的對(duì)象作為原型
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
instanceof 運(yùn)算符用于判斷構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在對(duì)象的原型鏈中的任何位置。
實(shí)現(xiàn)步驟:
具體實(shí)現(xiàn):
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 獲取對(duì)象的原型
prototype = right.prototype; // 獲取構(gòu)造函數(shù)的 prototype 對(duì)象
// 判斷構(gòu)造函數(shù)的 prototype 對(duì)象是否在對(duì)象的原型鏈上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
在調(diào)用 new
的過程中會(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ù)的返回值類型,如果是值類型,返回創(chuàng)建的對(duì)象。如果是引用類型,就返回這個(gè)引用類型的對(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ù));
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
// 保存初始化狀態(tài)
var self = this;
// 初始化狀態(tài)
this.state = PENDING;
// 用于保存 resolve 或者 rejected 傳入的值
this.value = null;
// 用于保存 resolve 的回調(diào)函數(shù)
this.resolvedCallbacks = [];
// 用于保存 reject 的回調(diào)函數(shù)
this.rejectedCallbacks = [];
// 狀態(tài)轉(zhuǎn)變?yōu)?resolved 方法
function resolve(value) {
// 判斷傳入元素是否為 Promise 值,如果是,則狀態(tài)改變必須等待前一個(gè)狀態(tài)改變后再進(jìn)行改變
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
// 保證代碼的執(zhí)行順序?yàn)楸据喪录h(huán)的末尾
setTimeout(() => {
// 只有狀態(tài)為 pending 時(shí)才能轉(zhuǎn)變,
if (self.state === PENDING) {
// 修改狀態(tài)
self.state = RESOLVED;
// 設(shè)置傳入的值
self.value = value;
// 執(zhí)行回調(diào)函數(shù)
self.resolvedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 狀態(tài)轉(zhuǎn)變?yōu)?rejected 方法
function reject(value) {
// 保證代碼的執(zhí)行順序?yàn)楸据喪录h(huán)的末尾
setTimeout(() => {
// 只有狀態(tài)為 pending 時(shí)才能轉(zhuǎn)變
if (self.state === PENDING) {
// 修改狀態(tài)
self.state = REJECTED;
// 設(shè)置傳入的值
self.value = value;
// 執(zhí)行回調(diào)函數(shù)
self.rejectedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 將兩個(gè)方法傳入函數(shù)執(zhí)行
try {
fn(resolve, reject);
} catch (e) {
// 遇到錯(cuò)誤時(shí),捕獲錯(cuò)誤,執(zhí)行 reject 函數(shù)
reject(e);
}
}
MyPromise.prototype.then = function(onResolved, onRejected) {
// 首先判斷兩個(gè)參數(shù)是否為函數(shù)類型,因?yàn)檫@兩個(gè)參數(shù)是可選參數(shù)
onResolved =
typeof onResolved === "function"
? onResolved
: function(value) {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function(error) {
throw error;
};
return new MyPromise((resolve,reject)=> {
// 如果是等待狀態(tài),則將函數(shù)加入對(duì)應(yīng)列表中
if (this.state === PENDING) {
this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
// 如果狀態(tài)已經(jīng)凝固,則直接執(zhí)行對(duì)應(yīng)狀態(tài)的函數(shù)
if (this.state === RESOLVED) {
try{
let res = onResolved(this.val)
if(res instanceof MyPromsie){
res.then(v => {
resolve(v)
},error => {
reject(error)
})
}
}catch(error){
reject(error)
}
}
if (this.state === REJECTED) {
onRejected(this.value);
}
};
})
then
方法返回一個(gè)新的 promise
實(shí)例,為了在 promise
狀態(tài)發(fā)生變化時(shí)(resolve
/ reject
被調(diào)用時(shí))再執(zhí)行 then
里的函數(shù),我們使用一個(gè) callbacks
數(shù)組先把傳給then的函數(shù)暫存起來,等狀態(tài)改變時(shí)再調(diào)用。
那么,怎么保證后一個(gè) then
里的方法在前一個(gè) then
(可能是異步)結(jié)束之后再執(zhí)行呢?
我們可以將傳給 then
的函數(shù)和新 promise
的 resolve
一起 push
到前一個(gè) promise
的 callbacks
數(shù)組中,達(dá)到承前啟后的效果:
promise
?完成后,調(diào)用其 ?resolve
?變更狀態(tài),在這個(gè) ?resolve
?里會(huì)依次調(diào)用 ?callbacks
?里的回調(diào),這樣就執(zhí)行了 ?then
?里的方法了then
?里的方法執(zhí)行完成后,返回一個(gè)結(jié)果,如果這個(gè)結(jié)果是個(gè)簡(jiǎn)單的值,就直接調(diào)用新 ?promise
?的 ?resolve
?,讓其狀態(tài)變更,這又會(huì)依次調(diào)用新 ?promise
?的 ?callbacks
?數(shù)組里的方法,循環(huán)往復(fù)。如果返回的結(jié)果是個(gè) ?promise
?,則需要等它完成之后再觸發(fā)新 ?promise
?的 ?resolve
?,所以可以在其結(jié)果的 ?then
?里調(diào)用新 ?promise
?的 ?resolve
?then(onFulfilled, onReject){
// 保存前一個(gè)promise的this
const self = this;
return new MyPromise((resolve, reject) => {
// 封裝前一個(gè)promise成功時(shí)執(zhí)行的函數(shù)
let fulfilled = () => {
try{
const result = onFulfilled(self.value); // 承前
return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //啟后
}catch(err){
reject(err)
}
}
// 封裝前一個(gè)promise失敗時(shí)執(zhí)行的函數(shù)
let rejected = () => {
try{
const result = onReject(self.reason);
return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
}catch(err){
reject(err)
}
}
switch(self.status){
case PENDING:
self.onFulfilledCallbacks.push(fulfilled);
self.onRejectedCallbacks.push(rejected);
break;
case FULFILLED:
fulfilled();
break;
case REJECT:
rejected();
break;
}
})
}
注意:
then
?里的回調(diào)方法是同步注冊(cè)的,但注冊(cè)到了不同的 ?callbacks
?數(shù)組中,因?yàn)槊看?nbsp;?then
?都返回新的 ?promise
?實(shí)例(參考上面的例子和圖)callbacks
?數(shù)組中提前注冊(cè)的回調(diào)1) 核心思路
2)實(shí)現(xiàn)代碼
一般來說,Promise.all 用來處理多個(gè)并發(fā)請(qǐng)求,也是為了頁(yè)面數(shù)據(jù)構(gòu)造的方便,將一個(gè)頁(yè)面所用到的在不同接口的數(shù)據(jù)一起請(qǐng)求過來,不過,如果其中一個(gè)接口失敗了,多個(gè)請(qǐng)求也就失敗了,頁(yè)面可能啥也出不來,這就看當(dāng)前頁(yè)面的耦合程度了
function promiseAll(promises) {
return new Promise(function(resolve, reject) {
if(!Array.isArray(promises)){
throw new TypeError(`argument must be a array`)
}
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedResult = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promises[i]).then(value=>{
resolvedCounter++;
resolvedResult[i] = value;
if (resolvedCounter == promiseNum) {
return resolve(resolvedResult)
}
},error=>{
return reject(error)
})
}
})
}
// test
let p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(1)
}, 1000)
})
let p2 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(2)
}, 2000)
})
let p3 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(3)
}, 3000)
})
promiseAll([p3, p1, p2]).then(res => {
console.log(res) // [3, 1, 2]
})
該方法的參數(shù)是 Promise 實(shí)例數(shù)組, 然后其 then 注冊(cè)的回調(diào)方法是數(shù)組中的某一個(gè) Promise 的狀態(tài)變?yōu)?fulfilled 的時(shí)候就執(zhí)行. 因?yàn)?Promise 的狀態(tài)只能改變一次, 那么我們只需要把 Promise.race 中產(chǎn)生的 Promise 對(duì)象的 resolve 方法, 注入到數(shù)組中的每一個(gè) Promise 實(shí)例中的回調(diào)函數(shù)中即可.
Promise.race = function (args) {
return new Promise((resolve, reject)=>{
for(let i = 0; i < args.length; i++){
Promise.resolve(args[i]).then(resolve,reject)
}
})
}
函數(shù)防抖是指在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā),則重新計(jì)時(shí)。這可以使用在一些點(diǎn)擊請(qǐng)求的事件上,避免因?yàn)橛脩舻亩啻吸c(diǎn)擊向后端發(fā)送多次請(qǐng)求。
// 函數(shù)防抖的實(shí)現(xiàn)
function debounce(fn, wait) {
let timer = null;
return function() {
let context = this,
args = arguments;
// 如果此時(shí)存在定時(shí)器的話,則取消之前的定時(shí)器重新記時(shí)
if (timer) {
clearTimeout(timer);
timer = null;
}
// 設(shè)置定時(shí)器,使事件間隔指定事件后執(zhí)行
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
函數(shù)節(jié)流是指規(guī)定一個(gè)單位時(shí)間,在這個(gè)單位時(shí)間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行,如果在同一個(gè)單位時(shí)間內(nèi)某事件被觸發(fā)多次,只有一次能生效。節(jié)流可以使用在 scroll 函數(shù)的事件監(jiān)聽上,通過事件節(jié)流來降低事件調(diào)用的頻率。
// 函數(shù)節(jié)流的實(shí)現(xiàn);
function throttle(fn, delay) {
let curTime = Date.now();
return function() {
let context = this,
args = arguments,
nowTime = Date.now();
// 如果兩次時(shí)間間隔超過了指定時(shí)間,則執(zhí)行函數(shù)。
if (nowTime - curTime >= delay) {
curTime = Date.now();
return fn.apply(context, args);
}
};
}
function throttle(fn,wait){
let timer = null
return function(){
let context = this, args = arguments
if(!timer){
timer = setTimeOut(()=>{
fn.apply(context,args)
timer = null
},wait)
}
}
}
function getType(value) {
// 判斷數(shù)據(jù)是 null 的情況
if (value === null) {
return value + "";
}
// 判斷數(shù)據(jù)是引用類型的情況
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ù)類型的情況和函數(shù)的情況
return typeof value;
}
}
call 函數(shù)的實(shí)現(xiàn)步驟:
// call函數(shù)實(shí)現(xiàn)
Function.prototype.myCall = function(context) {
// 判斷調(diào)用對(duì)象
if (typeof this !== "function") {
console.error("type error");
}
// 獲取參數(shù)
let args = [...arguments].slice(1),
result = null;
// 判斷 context 是否傳入,如果未傳入則設(shè)置為 window
context = context || window;
// 將調(diào)用函數(shù)設(shè)為對(duì)象的方法
context.fn = this;
// 調(diào)用函數(shù)
result = context.fn(...args);
// 將屬性刪除
delete context.fn;
return result;
};
apply 函數(shù)的實(shí)現(xiàn)步驟:
// apply 函數(shù)實(shí)現(xiàn)
Function.prototype.myApply = function(context) {
// 判斷調(diào)用對(duì)象是否為函數(shù)
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判斷 context 是否存在,如果未傳入則為 window
context = context || window;
// 將函數(shù)設(shè)為對(duì)象的方法
context.fn = this;
// 調(diào)用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 將屬性刪除
delete context.fn;
return result;
};
bind 函數(shù)的實(shí)現(xiàn)步驟:
// bind 函數(shù)實(shí)現(xiàn)
Function.prototype.myBind = function(context) {
// 判斷調(diào)用對(duì)象是否為函數(shù)
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 獲取參數(shù)
var args = [...arguments].slice(1),
fn = this;
var bound = function() {
// 根據(jù)調(diào)用方式,傳入不同綁定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
f = Function() {}
f.prototype = fn.prototype
bound.prototype = new f();
return bound;
};
函數(shù)柯里化指的是一種將使用多個(gè)參數(shù)的一個(gè)函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)的技術(shù)。
function curry(fn, args) {
// 獲取函數(shù)需要的參數(shù)長(zhǎng)度
let length = fn.length;
args = args || [];
return function() {
let subArgs = args.slice(0);
// 拼接得到現(xiàn)有的所有參數(shù)
for (let i = 0; i < arguments.length; i++) {
subArgs.push(arguments[i]);
}
// 判斷參數(shù)的長(zhǎng)度是否已經(jīng)滿足函數(shù)所需參數(shù)的長(zhǎng)度
if (subArgs.length >= length) {
// 如果滿足,執(zhí)行函數(shù)
return fn.apply(this, subArgs);
} else {
// 如果不滿足,遞歸返回科里化的函數(shù),等待參數(shù)的傳入
return curry.call(this, fn, subArgs);
}
};
}
// es6 實(shí)現(xiàn)
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
AJAX是 Asynchronous JavaScript and XML 的縮寫,指的是通過 JavaScript 的 異步通信,從服務(wù)器獲取 XML 文檔從中提取數(shù)據(jù),再更新當(dāng)前網(wǎng)頁(yè)的對(duì)應(yīng)部分,而不用刷新整個(gè)網(wǎng)頁(yè)。
創(chuàng)建AJAX請(qǐng)求的步驟:
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 創(chuàng)建 Http 請(qǐng)求
xhr.open("GET", SERVER_URL, true);
// 設(shè)置狀態(tài)監(jiān)聽函數(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)聽函數(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 封裝實(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)聽函數(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)聽函數(shù)
xhr.onerror = function() {
reject(new Error(this.statusText));
};
// 設(shè)置響應(yīng)的數(shù)據(jù)類型
xhr.responseType = "json";
// 設(shè)置請(qǐng)求頭信息
xhr.setRequestHeader("Accept", "application/json");
// 發(fā)送 http 請(qǐng)求
xhr.send(null);
});
return promise;
}
淺拷貝是指,一個(gè)新的對(duì)象對(duì)原始對(duì)象的屬性值進(jìn)行精確地拷貝,如果拷貝的是基本數(shù)據(jù)類型,拷貝的就是基本數(shù)據(jù)類型的值,如果是引用數(shù)據(jù)類型,拷貝的就是內(nèi)存地址。如果其中一個(gè)對(duì)象的引用內(nèi)存地址發(fā)生改變,另一個(gè)對(duì)象也會(huì)發(fā)生變化。
Object.assign()
是ES6中對(duì)象的拷貝方法,接受的第一個(gè)參數(shù)是目標(biāo)對(duì)象,其余參數(shù)是源對(duì)象,用法:Object.assign(target, source_1, ···)
,該方法可以實(shí)現(xiàn)淺拷貝,也可以實(shí)現(xiàn)一維對(duì)象的深拷貝。
注意:
null
?和 ?undefined
? 不能轉(zhuǎn)化為對(duì)象,所以第一個(gè)參數(shù)不能為 ?null
?或 ?undefined
?,會(huì)報(bào)錯(cuò)。let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);
console.log(target); // {a: 1, b: 2, c: 3}
使用擴(kuò)展運(yùn)算符可以在構(gòu)造字面量對(duì)象的時(shí)候,進(jìn)行屬性的拷貝。語(yǔ)法:let cloneObj = { ...obj };
let obj1 = {a:1,b:{c:1}}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}
1)Array.prototype.slice
slice()
?方法是JavaScript數(shù)組的一個(gè)方法,這個(gè)方法可以從已有數(shù)組中返回選定的元素:用法:?array.slice(start, end)
?,該方法不會(huì)改變?cè)紨?shù)組。let arr = [1,2,3,4];
console.log(arr.slice()); // [1,2,3,4]
console.log(arr.slice() === arr); //false
2)Array.prototype.concat
concat()
? 方法用于合并兩個(gè)或多個(gè)數(shù)組。此方法不會(huì)更改現(xiàn)有數(shù)組,而是返回一個(gè)新數(shù)組。let arr = [1,2,3,4];
console.log(arr.concat()); // [1,2,3,4]
console.log(arr.concat() === arr); //false
// 淺拷貝的實(shí)現(xiàn);
function shallowCopy(object) {
// 只拷貝對(duì)象
if (!object || typeof object !== "object") return;
// 根據(jù) object 的類型判斷是新建一個(gè)數(shù)組還是對(duì)象
let newObject = Array.isArray(object) ? [] : {};
// 遍歷 object,并且判斷是 object 的屬性才拷貝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
Object.keys(object).forEach(key => {
if(object.hasOwnProperty(key)){
newObject[key] = object[key]
}
})
return newObject;
}// 淺拷貝的實(shí)現(xiàn);
function shallowCopy(object) {
// 只拷貝對(duì)象
if (!object || typeof object !== "object") return;
// 根據(jù) object 的類型判斷是新建一個(gè)數(shù)組還是對(duì)象
let newObject = Array.isArray(object) ? [] : {};
// 遍歷 object,并且判斷是 object 的屬性才拷貝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
//newObject[key] = typeof object[key] === 'object' ? deepCopy(object[key]) : object[key]
}
}
return newObject;
}// 淺拷貝的實(shí)現(xiàn);
function shallowCopy(object) {
// 只拷貝對(duì)象
if (!object || typeof object !== "object") return;
// 根據(jù) object 的類型判斷是新建一個(gè)數(shù)組還是對(duì)象
let newObject = Array.isArray(object) ? [] : {};
// 遍歷 object,并且判斷是 object 的屬性才拷貝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}
JSON.parse(JSON.stringify(obj))
?是目前比較常用的深拷貝方法之一,它的原理就是利用 ?JSON.stringify
? 將 ?js
?對(duì)象序列化(JSON字符串),再使用 ?JSON.parse
?來反序列化(還原)js對(duì)象。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}}
該函數(shù)庫(kù)也有提供_.cloneDeep用來做 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
// 深拷貝的實(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];
}
}
Object.keys(object).forEach(key => {
if(object.hasOwnProperty(key)){
newObject[key] = typeof object[key] === 'object' ? deepCopy(object[key]) : object[key]
}
})
return newObject;
}
輸入:
dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{
var day = dateInput.getDate()
var month = dateInput.getMonth() + 1
var year = dateInput.getFullYear()
format = format.replace(/yyyy/, year)
format = format.replace(/MM/,month)
format = format.replace(/dd/,day)
return format
}
巧妙的利用兩個(gè)數(shù)的和、差:
a = a + b
b = a - b
a = a - b
主要的實(shí)現(xiàn)思路就是:
var arr = [1,2,3,4,5,6,7,8,9,10];
for (var i = 0; i < arr.length; i++) {
const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
}
console.log(arr)
還有一方法就是倒序遍歷:
var arr = [1,2,3,4,5,6,7,8,9,10];
let length = arr.length,
randomIndex,
temp;
while (length) {
randomIndex = Math.floor(Math.random() * length--);
temp = arr[length];
arr[length] = arr[randomIndex];
arr[randomIndex] = temp;
}
console.log(arr)
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce( (total,i) => total += i,0);
console.log(sum);
var = arr=[1,2,3,[[4,5],6],7,8,9]
let arr= arr.toString().split(',').reduce( (total,i) => total += Number(i),0);
console.log(arr);
遞歸實(shí)現(xiàn):
let arr = [1, 2, 3, 4, 5, 6]
function add(arr) {
if (arr.length == 1) return arr[0]
return arr[0] + add(arr.slice(1))
}
console.log(add(arr)) // 21
(1)遞歸實(shí)現(xiàn)
普通的遞歸思路很容易理解,就是通過循環(huán)遞歸的方式,一項(xiàng)一項(xiàng)地去遍歷,如果每一項(xiàng)還是一個(gè)數(shù)組,那么就繼續(xù)往下遍歷,利用遞歸程序的方法,來實(shí)現(xiàn)數(shù)組的每一項(xiàng)的連接:
let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(arr); // [1, 2, 3, 4,5]
(2)reduce 函數(shù)迭代
從上面普通的遞歸函數(shù)中可以看出,其實(shí)就是對(duì)數(shù)組的每一項(xiàng)進(jìn)行處理,那么其實(shí)也可以用reduce 來實(shí)現(xiàn)數(shù)組的拼接,從而簡(jiǎn)化第一種方法的代碼,改造后的代碼如下所示:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.reduce(function(prev, next){
return prev.concat(Array.isArray(next) ? flatten(next) : next)
}, [])
}
console.log(flatten(arr));// [1, 2, 3, 4,5]
(3)擴(kuò)展運(yùn)算符實(shí)現(xiàn)
這個(gè)方法的實(shí)現(xiàn),采用了擴(kuò)展運(yùn)算符和 some 的方法,兩者共同使用,達(dá)到數(shù)組扁平化的目的:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
(4)split 和 toString
可以通過 split 和 toString 兩個(gè)方法來共同實(shí)現(xiàn)數(shù)組扁平化,由于數(shù)組會(huì)默認(rèn)帶一個(gè) toString 的方法,所以可以把數(shù)組直接轉(zhuǎn)換成逗號(hào)分隔的字符串,然后再用 split 方法把字符串重新轉(zhuǎn)換為數(shù)組,如下面的代碼所示:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(',');
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
通過這兩個(gè)方法可以將多維數(shù)組直接轉(zhuǎn)換成逗號(hào)連接的字符串,然后再重新分隔成數(shù)組。
(5)ES6 中的 flat
我們還可以直接調(diào)用 ES6 中的 flat 方法來實(shí)現(xiàn)數(shù)組扁平化。flat 方法的語(yǔ)法:arr.flat([depth])
其中 depth 是 flat 的參數(shù),depth 是可以傳遞數(shù)組的展開深度(默認(rèn)不填、數(shù)值是 1),即展開一層數(shù)組。如果層數(shù)不確定,參數(shù)可以傳進(jìn) Infinity,代表不論多少層都要展開:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.flat(Infinity);
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
可以看出,一個(gè)嵌套了兩層的數(shù)組,通過將 flat 方法的參數(shù)設(shè)置為 Infinity,達(dá)到了我們預(yù)期的效果。其實(shí)同樣也可以設(shè)置成 2,也能實(shí)現(xiàn)這樣的效果。在編程過程中,如果數(shù)組的嵌套層數(shù)不確定,最好直接使用 Infinity,可以達(dá)到扁平化。
(6)正則和 JSON 方法
在第4種方法中已經(jīng)使用 toString 方法,其中仍然采用了將 JSON.stringify 的方法先轉(zhuǎn)換為字符串,然后通過正則表達(dá)式過濾掉字符串中的數(shù)組的方括號(hào),最后再利用 JSON.parse 把它轉(zhuǎn)換成數(shù)組:
let arr = [1, [2, [3, [4, 5]]], 6];
function flatten(arr) {
let str = JSON.stringify(arr);
str = str.replace(/(\[|\])/g, '');
str = '[' + str + ']';
return JSON.parse(str);
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
給定某無序數(shù)組,要求去除數(shù)組中的重復(fù)數(shù)字并且返回新的無重復(fù)數(shù)組。
ES6方法(使用數(shù)據(jù)結(jié)構(gòu)集合):
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
ES5方法:使用map存儲(chǔ)不重復(fù)的數(shù)字
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
uniqueArray(array); // [1, 2, 3, 5, 9, 8]
function uniqueArray(array) {
let map = {};
let res = [];
for(var i = 0; i < array.length; i++) {
if(!map.hasOwnProperty([array[i]])) {
map[array[i]] = 1;
res.push(array[i]);
}
}
return res;
}
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);
}
}, []);
}
let arr = [];
Array.prototype.push = function() {
for( let i = 0 ; i < arguments.length ; i++){
this[this.length] = arguments[i] ;
}
return this.length;
}
Array.prototype._filter = function(fn) {
if (typeof fn !== "function") {
throw Error('參數(shù)必須是一個(gè)函數(shù)');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {
fn(this[i]) && res.push(this[i]);
}
return res;
}
Array.prototype._map = function(fn) {
if (typeof fn !== "function") {
throw Error('參數(shù)必須是一個(gè)函數(shù)');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {
res.push(fn(this[i]));
}
return res;
}
輸入字符串s,以及其重復(fù)的次數(shù),輸出重復(fù)的結(jié)果,例如輸入abc,2,輸出abcabc。
function repeat(s, n) {
return (new Array(n + 1)).join(s);
}
遞歸:
function repeat(s, n) {
return (n > 0) ? s.concat(repeat(s, --n)) : "";
}
在字符串的原型鏈上添加一個(gè)方法,實(shí)現(xiàn)字符串翻轉(zhuǎn):
String.prototype._reverse = function(a){
return a.split("").reverse().join("");
}
var obj = new String();
var res = obj._reverse ('hello');
console.log(res); // olleh
需要注意的是,必須通過實(shí)例化對(duì)象之后再去調(diào)用定義的方法,不然找不到該方法。
數(shù)字有小數(shù)版本:
let format = n => {
let num = n.toString() // 轉(zhuǎn)成字符串
let decimals = ''
// 判斷是否有小數(shù)
num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
let len = num.length
if (len <= 3) {
return num
} else {
let temp = ''
let remainder = len % 3
decimals ? temp = '.' + decimals : temp
if (remainder > 0) { // 不是3的整數(shù)倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
} else { // 是3的整數(shù)倍
return num.slice(0, len).match(/\d{3}/g).join(',') + temp
}
}
}
format(12323.33) // '12,323.33'
數(shù)字無小數(shù)版本:
let format = n => {
let num = n.toString()
let len = num.length
if (len <= 3) {
return num
} else {
let remainder = len % 3
if (remainder > 0) { // 不是3的整數(shù)倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')
} else { // 是3的整數(shù)倍
return num.slice(0, len).match(/\d{3}/g).join(',')
}
}
}
format(1232323) // '1,232,323'
JavaScript對(duì)數(shù)值有范圍的限制,限制如下:
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_VALUE // 5e-324
Number.MIN_SAFE_INTEGER // -9007199254740991
如果想要對(duì)一個(gè)超大的整數(shù)(> Number.MAX_SAFE_INTEGER
)進(jìn)行加法運(yùn)算,但是又想輸出一般形式,那么使用 + 是無法達(dá)到的,一旦數(shù)字超過 Number.MAX_SAFE_INTEGER
數(shù)字會(huì)被立即轉(zhuǎn)換為科學(xué)計(jì)數(shù)法,并且數(shù)字精度相比以前將會(huì)有誤差。
實(shí)現(xiàn)一個(gè)算法進(jìn)行大數(shù)的相加:
function sumBigNumber(a, b) {
let res = '';
let temp = 0;
a = a.split('');
b = b.split('');
while (a.length || b.length || temp) {
temp += ~~a.pop() + ~~b.pop();
res = (temp % 10) + res;
temp = temp > 9
}
return res.replace(/^0+/, '');
}
其主要的思路如下:
函數(shù)柯里化概念: 柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)轉(zhuǎn)變?yōu)榻邮芤粋€(gè)單一參數(shù)的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。
1)粗暴版
function add (a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
console.log(add(1)(2)(3)); // 6
2)柯里化解決方案
var add = function (m) {
var temp = function (n) {
return add(m + n);
}
temp.toString = function () {
return m;
}
return temp;
};
console.log(add(3)(4)(5)); // 12
console.log(add(3)(6)(9)(25)); // 43
對(duì)于add(3)(4)(5),其執(zhí)行過程如下:
function add (...args) {
//求和
return args.reduce((a, b) => a + b)
}
function currying (fn) {
let args = []
return function temp (...newArgs) {
if (newArgs.length) {
args = [
...args,
...newArgs
]
return temp
} else {
let val = fn.apply(this, args)
args = [] //保證再次調(diào)用時(shí)清空
return val
}
}
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)()) //15
console.log(addCurry(1)(2)(3, 4, 5)()) //15
console.log(addCurry(1)(2, 3, 4, 5)()) //15
類數(shù)組轉(zhuǎn)換為數(shù)組的方法有這樣幾種:
Array.prototype.slice.call(arrayLike);
Array.prototype.splice.call(arrayLike, 0);
Array.prototype.concat.apply([], arrayLike);
Array.from(arrayLike);
arr = [1,2,3,4,5,6,7,8,9,10],求和
let arr = [1,2,3,4,5,6,7,8,9,10]
arr.reduce((prev, cur) => { return prev + cur }, 0)
arr = [1,2,3,[[4,5],6],7,8,9],求和
let arr = [1,2,3,4,5,6,7,8,9,10]
arr.flat(Infinity).reduce((prev, cur) => { return prev + cur }, 0)
arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和
let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}]
arr.reduce((prev, cur) => {
return prev + cur["a"];
}, 0)
// 轉(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ǔ)起來
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;
}
ES5:
function sum() {
let sum = 0
Array.prototype.forEach.call(arguments, function(item) {
sum += item * 1
})
return sum
}
ES6:
function sum(...nums) {
let sum = 0
nums.forEach(function(item) {
sum += item * 1
})
return sum
}
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 結(jié)果
{ user: 'anonymous',
id: [ 123, 456 ], // 重復(fù)出現(xiàn)的 key 要組裝成數(shù)組,能被轉(zhuǎn)成數(shù)字的就轉(zhuǎn)成數(shù)字類型
city: '北京', // 中文需解碼
enabled: true, // 未指定值得 key 約定為 true
}
*/
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來
const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數(shù)組中
let paramsObj = {};
// 將 params 存到對(duì)象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 處理有 value 的參數(shù)
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解碼
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉(zhuǎn)為數(shù)字
if (paramsObj.hasOwnProperty(key)) { // 如果對(duì)象有 key,則添加一個(gè)值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果對(duì)象沒有這個(gè) key,創(chuàng)建 key 并設(shè)置值
paramsObj[key] = val;
}
} else { // 處理沒有 value 的參數(shù)
paramsObj[param] = true;
}
})
return paramsObj;
}
下面來看一道比較典型的問題,通過這個(gè)問題來對(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é)束了。
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)行呢?
上面提到過遞歸,可以遞歸亮燈的一個(gè)周期:
const step = () => {
task(3000, 'red', () => {
task(2000, 'green', () => {
task(1000, 'yellow', step)
})
})
}
step()
注意看黃燈亮的回調(diào)里又再次調(diào)用了 step 方法 以完成循環(huá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)行。
const taskRunner = async () => {
await task(3000, 'red')
await task(2000, 'green')
await task(2100, 'yellow')
taskRunner()
}
taskRunner()
// 使用閉包實(shí)現(xiàn)
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
// 使用 let 塊級(jí)作用域
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
有30個(gè)小孩兒,編號(hào)從1-30,圍成一圈依此報(bào)數(shù),1、2、3 數(shù)到 3 的小孩兒退出這個(gè)圈, 然后下一個(gè)小孩 重新報(bào)數(shù) 1、2、3,問最后剩下的那個(gè)小孩兒的編號(hào)是多少?
function childNum(num, count){
let allplayer = [];
for(let i = 0; i < num; i++){
allplayer[i] = i + 1;
}
let exitCount = 0; // 離開人數(shù)
let counter = 0; // 記錄報(bào)數(shù)
let curIndex = 0; // 當(dāng)前下標(biāo)
while(exitCount < num - 1){
if(allplayer[curIndex] !== 0) counter++;
if(counter == count){
allplayer[curIndex] = 0;
counter = 0;
exitCount++;
}
curIndex++;
if(curIndex == num){
curIndex = 0
};
}
for(i = 0; i < num; i++){
if(allplayer[i] !== 0){
return allplayer[i]
}
}
}
childNum(30, 3)
let imageAsync=(url)=>{
return new Promise((resolve,reject)=>{
let img = new Image();
img.src = url;
img.οnlοad=()=>{
console.log(`圖片請(qǐng)求成功,此處進(jìn)行通用操作`);
resolve(image);
}
img.οnerrοr=(err)=>{
console.log(`失敗,此處進(jìn)行失敗的通用操作`);
reject(err);
}
})
}
imageAsync("url").then(()=>{
console.log("加載成功");
}).catch((error)=>{
console.log("加載失敗");
})
class EventCenter{
// 1. 定義事件容器,用來裝事件數(shù)組
let handlers = {}
// 2. 添加事件方法,參數(shù):事件名 事件方法
addEventListener(type, handler) {
// 創(chuàng)建新數(shù)組容器
if (!this.handlers[type]) {
this.handlers[type] = []
}
// 存入事件
this.handlers[type].push(handler)
}
// 3. 觸發(fā)事件,參數(shù):事件名 事件參數(shù)
dispatchEvent(type, params) {
// 若沒有注冊(cè)該事件則拋出錯(cuò)誤
if (!this.handlers[type]) {
return new Error('該事件未注冊(cè)')
}
// 觸發(fā)事件
this.handlers[type].forEach(handler => {
handler(...params)
})
}
// 4. 事件移除,參數(shù):事件名 要?jiǎng)h除事件,若無第二個(gè)參數(shù)則刪除該事件的訂閱和發(fā)布
removeEventListener(type, handler) {
if (!this.handlers[type]) {
return new Error('事件無效')
}
if (!handler) {
// 移除事件
delete this.handlers[type]
} else {
const index = this.handlers[type].findIndex(el => el === handler)
if (index === -1) {
return new Error('無該綁定事件')
}
// 移除事件
this.handlers[type].splice(index, 1)
if (this.handlers[type].length === 0) {
delete this.handlers[type]
}
}
}
}
function findMostWord(article) {
// 合法性判斷
if (!article) return;
// 參數(shù)處理
article = article.trim().toLowerCase();
let wordList = article.match(/[a-z]+/g),
visited = [],
maxNum = 0,
maxWord = "";
article = " " + wordList.join(" ") + " ";
// 遍歷判斷單詞出現(xiàn)次數(shù)
wordList.forEach(function(item) {
if (visited.indexOf(item) < 0) {
// 加入 visited
visited.push(item);
let word = new RegExp(" " + item + " ", "g"),
num = article.match(word).length;
if (num > maxNum) {
maxNum = num;
maxWord = item;
}
}
});
return maxWord + " " + maxNum;
}
(async () => {
class HttpRequestUtil {
async get(url) {
const res = await fetch(url);
const data = await res.json();
return data;
}
async post(url, data) {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async put(url, data) {
const res = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async delete(url, data) {
const res = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
}
const httpRequestUtil = new HttpRequestUtil();
const res = await httpRequestUtil.get('http://golderbrother.cn/');
console.log(res);
})();
所謂的原型鏈繼承就是讓新實(shí)例的原型等于父類的實(shí)例:
//父方法
function SupperFunction(flag1){
this.flag1 = flag1;
}
//子方法
function SubFunction(flag2){
this.flag2 = flag2;
}
//父實(shí)例
var superInstance = new SupperFunction(true);
//子繼承父
SubFunction.prototype = superInstance;
//子實(shí)例
var subInstance = new SubFunction(false);
//子調(diào)用自己和父的屬性
subInstance.flag1; // true
subInstance.flag2; // false
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 數(shù)據(jù)劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('獲取數(shù)據(jù)了')
},
set(newVal) {
console.log('數(shù)據(jù)更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 輸入監(jiān)聽
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
// hash路由
class Route{
constructor(){
// 路由存儲(chǔ)對(duì)象
this.routes = {}
// 當(dāng)前hash
this.currentHash = ''
// 綁定this,避免監(jiān)聽時(shí)this指向改變
this.freshRoute = this.freshRoute.bind(this)
// 監(jiān)聽
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存儲(chǔ)
storeRoute (path, cb) {
this.routes[path] = cb || function () {}
}
// 更新
freshRoute () {
this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()
}
}
// 遞歸
function fn (n){
if(n==0) return 0
if(n==1) return 1
return fn(n-2)+fn(n-1)
}
// 優(yōu)化
function fibonacci2(n) {
const arr = [1, 1, 2];
const arrLen = arr.length;
if (n <= arrLen) {
return arr[n];
}
for (let i = arrLen; i < n; i++) {
arr.push(arr[i - 1] + arr[ i - 2]);
}
return arr[arr.length - 1];
}
// 非遞歸
function fn(n) {
let pre1 = 1;
let pre2 = 1;
let current = 2;
if (n <= 2) {
return current;
}
for (let i = 2; i < n; i++) {
pre1 = pre2;
pre2 = current;
current = pre1 + pre2;
}
return current;
}
用一個(gè)滑動(dòng)窗口裝沒有重復(fù)的字符,枚舉字符記錄最大值即可。用 map 維護(hù)字符的索引,遇到相同的字符,把左邊界移動(dòng)過去即可。挪動(dòng)的過程中記錄最大長(zhǎng)度:
var lengthOfLongestSubstring = function (s) {
let map = new Map();
let i = -1
let res = 0
let n = s.length
for (let j = 0; j < n; j++) {
if (map.has(s[j])) {
i = Math.max(i, map.get(s[j]))
}
res = Math.max(res, j - i)
map.set(s[j], j)
}
return res
};
setInterval 的作用是每隔一段指定時(shí)間執(zhí)行一個(gè)函數(shù),但是這個(gè)執(zhí)行不是真的到了時(shí)間立即執(zhí)行,它真正的作用是每隔一段時(shí)間將事件加入事件隊(duì)列中去,只有當(dāng)當(dāng)前的執(zhí)行棧為空的時(shí)候,才能去從事件隊(duì)列中取出事件執(zhí)行。所以可能會(huì)出現(xiàn)這樣的情況,就是當(dāng)前執(zhí)行棧執(zhí)行的時(shí)間很長(zhǎng),導(dǎo)致事件隊(duì)列里邊積累多個(gè)定時(shí)器加入的事件,當(dāng)執(zhí)行棧結(jié)束的時(shí)候,這些事件會(huì)依次執(zhí)行,因此就不能到間隔一段時(shí)間執(zhí)行的效果。
針對(duì) setInterval 的這個(gè)缺點(diǎn),我們可以使用 setTimeout 遞歸調(diào)用來模擬 setInterval,這樣我們就確保了只有一個(gè)事件結(jié)束了,我們才會(huì)觸發(fā)下一個(gè)定時(shí)器事件,這樣解決了 setInterval 的問題。
實(shí)現(xiàn)思路是使用遞歸函數(shù),不斷地去執(zhí)行 setTimeout 從而達(dá)到 setInterval 的效果
function mySetInterval(fn, timeout) {
// 控制器,控制定時(shí)器是否繼續(xù)執(zhí)行
var timer = {
flag: true
};
// 設(shè)置遞歸函數(shù),模擬定時(shí)器執(zhí)行。
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
// 啟動(dòng)定時(shí)器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
// 動(dòng)態(tài)的加載js文件
function addScript(src) {
const script = document.createElement('script');
script.src = src;
script.type = "text/javascript";
document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 設(shè)置一個(gè)全局的callback函數(shù)來接收回調(diào)結(jié)果
function handleRes(res) {
console.log(res);
}
// 接口返回的數(shù)據(jù)格式
handleRes({a: 1, b: 2});
循環(huán)引用對(duì)象本來沒有什么問題,但是序列化的時(shí)候就會(huì)發(fā)生問題,比如調(diào)用 JSON.stringify()
對(duì)該類對(duì)象進(jìn)行序列化,就會(huì)報(bào)錯(cuò): Converting circular structure to JSON.
下面方法可以用來判斷一個(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ù)組斜向打?。?br>
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
}
更多建議: