思路:將傳入的對象作為原型
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
instanceof 運算符用于判斷構造函數(shù)的 prototype 屬性是否出現(xiàn)在對象的原型鏈中的任何位置。
實現(xiàn)步驟:
具體實現(xiàn):
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 獲取對象的原型
prototype = right.prototype; // 獲取構造函數(shù)的 prototype 對象
// 判斷構造函數(shù)的 prototype 對象是否在對象的原型鏈上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
在調(diào)用 new
的過程中會發(fā)生以上四件事情:
(1)首先創(chuàng)建了一個新的空對象
(2)設置原型,將對象的原型設置為函數(shù)的 prototype 對象。
(3)讓函數(shù)的 this 指向這個對象,執(zhí)行構造函數(shù)的代碼(為這個新對象添加屬性)
(4)判斷函數(shù)的返回值類型,如果是值類型,返回創(chuàng)建的對象。如果是引用類型,就返回這個引用類型的對象。
function objectFactory() {
let newObject = null;
let constructor = Array.prototype.shift.call(arguments);
let result = null;
// 判斷參數(shù)是否是一個函數(shù)
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一個空對象,對象的原型為構造函數(shù)的 prototype 對象
newObject = Object.create(constructor.prototype);
// 將 this 指向新建對象,并執(zhí)行函數(shù)
result = constructor.apply(newObject, arguments);
// 判斷返回對象
let flag = result && (typeof result === "object" || typeof result === "function");
// 判斷返回結果
return flag ? result : newObject;
}
// 使用方法
objectFactory(構造函數(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)轉變?yōu)?resolved 方法
function resolve(value) {
// 判斷傳入元素是否為 Promise 值,如果是,則狀態(tài)改變必須等待前一個狀態(tài)改變后再進行改變
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
// 保證代碼的執(zhí)行順序為本輪事件循環(huán)的末尾
setTimeout(() => {
// 只有狀態(tài)為 pending 時才能轉變,
if (self.state === PENDING) {
// 修改狀態(tài)
self.state = RESOLVED;
// 設置傳入的值
self.value = value;
// 執(zhí)行回調(diào)函數(shù)
self.resolvedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 狀態(tài)轉變?yōu)?rejected 方法
function reject(value) {
// 保證代碼的執(zhí)行順序為本輪事件循環(huán)的末尾
setTimeout(() => {
// 只有狀態(tài)為 pending 時才能轉變
if (self.state === PENDING) {
// 修改狀態(tài)
self.state = REJECTED;
// 設置傳入的值
self.value = value;
// 執(zhí)行回調(diào)函數(shù)
self.rejectedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 將兩個方法傳入函數(shù)執(zhí)行
try {
fn(resolve, reject);
} catch (e) {
// 遇到錯誤時,捕獲錯誤,執(zhí)行 reject 函數(shù)
reject(e);
}
}
MyPromise.prototype.then = function(onResolved, onRejected) {
// 首先判斷兩個參數(shù)是否為函數(shù)類型,因為這兩個參數(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ù)加入對應列表中
if (this.state === PENDING) {
this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
// 如果狀態(tài)已經(jīng)凝固,則直接執(zhí)行對應狀態(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
方法返回一個新的 promise
實例,為了在 promise
狀態(tài)發(fā)生變化時(resolve
/ reject
被調(diào)用時)再執(zhí)行 then
里的函數(shù),我們使用一個 callbacks
數(shù)組先把傳給then的函數(shù)暫存起來,等狀態(tài)改變時再調(diào)用。
那么,怎么保證后一個 then
里的方法在前一個 then
(可能是異步)結束之后再執(zhí)行呢?
我們可以將傳給 then
的函數(shù)和新 promise
的 resolve
一起 push
到前一個 promise
的 callbacks
數(shù)組中,達到承前啟后的效果:
promise
?完成后,調(diào)用其 ?resolve
?變更狀態(tài),在這個 ?resolve
?里會依次調(diào)用 ?callbacks
?里的回調(diào),這樣就執(zhí)行了 ?then
?里的方法了then
?里的方法執(zhí)行完成后,返回一個結果,如果這個結果是個簡單的值,就直接調(diào)用新 ?promise
?的 ?resolve
?,讓其狀態(tài)變更,這又會依次調(diào)用新 ?promise
?的 ?callbacks
?數(shù)組里的方法,循環(huán)往復。如果返回的結果是個 ?promise
?,則需要等它完成之后再觸發(fā)新 ?promise
?的 ?resolve
?,所以可以在其結果的 ?then
?里調(diào)用新 ?promise
?的 ?resolve
?then(onFulfilled, onReject){
// 保存前一個promise的this
const self = this;
return new MyPromise((resolve, reject) => {
// 封裝前一個promise成功時執(zhí)行的函數(shù)
let fulfilled = () => {
try{
const result = onFulfilled(self.value); // 承前
return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //啟后
}catch(err){
reject(err)
}
}
// 封裝前一個promise失敗時執(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)方法是同步注冊的,但注冊到了不同的 ?callbacks
?數(shù)組中,因為每次 ?then
?都返回新的 ?promise
?實例(參考上面的例子和圖)callbacks
?數(shù)組中提前注冊的回調(diào)1) 核心思路
2)實現(xiàn)代碼
一般來說,Promise.all 用來處理多個并發(fā)請求,也是為了頁面數(shù)據(jù)構造的方便,將一個頁面所用到的在不同接口的數(shù)據(jù)一起請求過來,不過,如果其中一個接口失敗了,多個請求也就失敗了,頁面可能啥也出不來,這就看當前頁面的耦合程度了
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ù)組, 然后其 then 注冊的回調(diào)方法是數(shù)組中的某一個 Promise 的狀態(tài)變?yōu)?fulfilled 的時候就執(zhí)行. 因為 Promise 的狀態(tài)只能改變一次, 那么我們只需要把 Promise.race 中產(chǎn)生的 Promise 對象的 resolve 方法, 注入到數(shù)組中的每一個 Promise 實例中的回調(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ā),則重新計時。這可以使用在一些點擊請求的事件上,避免因為用戶的多次點擊向后端發(fā)送多次請求。
// 函數(shù)防抖的實現(xiàn)
function debounce(fn, wait) {
let timer = null;
return function() {
let context = this,
args = arguments;
// 如果此時存在定時器的話,則取消之前的定時器重新記時
if (timer) {
clearTimeout(timer);
timer = null;
}
// 設置定時器,使事件間隔指定事件后執(zhí)行
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
函數(shù)節(jié)流是指規(guī)定一個單位時間,在這個單位時間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行,如果在同一個單位時間內(nèi)某事件被觸發(fā)多次,只有一次能生效。節(jié)流可以使用在 scroll 函數(shù)的事件監(jiān)聽上,通過事件節(jié)流來降低事件調(diào)用的頻率。
// 函數(shù)節(jié)流的實現(xiàn);
function throttle(fn, delay) {
let curTime = Date.now();
return function() {
let context = this,
args = arguments,
nowTime = Date.now();
// 如果兩次時間間隔超過了指定時間,則執(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ù)的實現(xiàn)步驟:
// call函數(shù)實現(xiàn)
Function.prototype.myCall = function(context) {
// 判斷調(diào)用對象
if (typeof this !== "function") {
console.error("type error");
}
// 獲取參數(shù)
let args = [...arguments].slice(1),
result = null;
// 判斷 context 是否傳入,如果未傳入則設置為 window
context = context || window;
// 將調(diào)用函數(shù)設為對象的方法
context.fn = this;
// 調(diào)用函數(shù)
result = context.fn(...args);
// 將屬性刪除
delete context.fn;
return result;
};
apply 函數(shù)的實現(xiàn)步驟:
// apply 函數(shù)實現(xiàn)
Function.prototype.myApply = function(context) {
// 判斷調(diào)用對象是否為函數(shù)
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判斷 context 是否存在,如果未傳入則為 window
context = context || window;
// 將函數(shù)設為對象的方法
context.fn = this;
// 調(diào)用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 將屬性刪除
delete context.fn;
return result;
};
bind 函數(shù)的實現(xiàn)步驟:
// bind 函數(shù)實現(xiàn)
Function.prototype.myBind = function(context) {
// 判斷調(diào)用對象是否為函數(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ù)柯里化指的是一種將使用多個參數(shù)的一個函數(shù)轉換成一系列使用一個參數(shù)的函數(shù)的技術。
function curry(fn, args) {
// 獲取函數(shù)需要的參數(shù)長度
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ù)的長度是否已經(jīng)滿足函數(shù)所需參數(shù)的長度
if (subArgs.length >= length) {
// 如果滿足,執(zhí)行函數(shù)
return fn.apply(this, subArgs);
} else {
// 如果不滿足,遞歸返回科里化的函數(shù),等待參數(shù)的傳入
return curry.call(this, fn, subArgs);
}
};
}
// es6 實現(xiàn)
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
AJAX是 Asynchronous JavaScript and XML 的縮寫,指的是通過 JavaScript 的 異步通信,從服務器獲取 XML 文檔從中提取數(shù)據(jù),再更新當前網(wǎng)頁的對應部分,而不用刷新整個網(wǎng)頁。
創(chuàng)建AJAX請求的步驟:
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 創(chuàng)建 Http 請求
xhr.open("GET", SERVER_URL, true);
// 設置狀態(tài)監(jiān)聽函數(shù)
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 當請求成功時
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 設置請求失敗時的監(jiān)聽函數(shù)
xhr.onerror = function() {
console.error(this.statusText);
};
// 設置請求頭信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 發(fā)送 Http 請求
xhr.send(null);
// promise 封裝實現(xiàn):
function getJSON(url) {
// 創(chuàng)建一個 promise 對象
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
// 新建一個 http 請求
xhr.open("GET", url, true);
// 設置狀態(tài)的監(jiān)聽函數(shù)
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 當請求成功或失敗時,改變 promise 的狀態(tài)
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
// 設置錯誤監(jiān)聽函數(shù)
xhr.onerror = function() {
reject(new Error(this.statusText));
};
// 設置響應的數(shù)據(jù)類型
xhr.responseType = "json";
// 設置請求頭信息
xhr.setRequestHeader("Accept", "application/json");
// 發(fā)送 http 請求
xhr.send(null);
});
return promise;
}
淺拷貝是指,一個新的對象對原始對象的屬性值進行精確地拷貝,如果拷貝的是基本數(shù)據(jù)類型,拷貝的就是基本數(shù)據(jù)類型的值,如果是引用數(shù)據(jù)類型,拷貝的就是內(nèi)存地址。如果其中一個對象的引用內(nèi)存地址發(fā)生改變,另一個對象也會發(fā)生變化。
Object.assign()
是ES6中對象的拷貝方法,接受的第一個參數(shù)是目標對象,其余參數(shù)是源對象,用法:Object.assign(target, source_1, ···)
,該方法可以實現(xiàn)淺拷貝,也可以實現(xiàn)一維對象的深拷貝。
注意:
null
?和 ?undefined
? 不能轉化為對象,所以第一個參數(shù)不能為 ?null
?或 ?undefined
?,會報錯。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}
使用擴展運算符可以在構造字面量對象的時候,進行屬性的拷貝。語法: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ù)組的一個方法,這個方法可以從已有數(shù)組中返回選定的元素:用法:?array.slice(start, end)
?,該方法不會改變原始數(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()
? 方法用于合并兩個或多個數(shù)組。此方法不會更改現(xiàn)有數(shù)組,而是返回一個新數(shù)組。let arr = [1,2,3,4];
console.log(arr.concat()); // [1,2,3,4]
console.log(arr.concat() === arr); //false
// 淺拷貝的實現(xiàn);
function shallowCopy(object) {
// 只拷貝對象
if (!object || typeof object !== "object") return;
// 根據(jù) object 的類型判斷是新建一個數(shù)組還是對象
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;
}// 淺拷貝的實現(xiàn);
function shallowCopy(object) {
// 只拷貝對象
if (!object || typeof object !== "object") return;
// 根據(jù) object 的類型判斷是新建一個數(shù)組還是對象
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;
}// 淺拷貝的實現(xiàn);
function shallowCopy(object) {
// 只拷貝對象
if (!object || typeof object !== "object") return;
// 根據(jù) object 的類型判斷是新建一個數(shù)組還是對象
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
?對象序列化(JSON字符串),再使用 ?JSON.parse
?來反序列化(還原)js對象。JSON.stringify()
?進行處理之后,都會消失。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ù)庫也有提供_.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
// 深拷貝的實現(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
}
巧妙的利用兩個數(shù)的和、差:
a = a + b
b = a - b
a = a - b
主要的實現(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);
遞歸實現(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)遞歸實現(xiàn)
普通的遞歸思路很容易理解,就是通過循環(huán)遞歸的方式,一項一項地去遍歷,如果每一項還是一個數(shù)組,那么就繼續(xù)往下遍歷,利用遞歸程序的方法,來實現(xiàn)數(shù)組的每一項的連接:
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ù)組的每一項進行處理,那么其實也可以用reduce 來實現(xiàn)數(shù)組的拼接,從而簡化第一種方法的代碼,改造后的代碼如下所示:
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)擴展運算符實現(xiàn)
這個方法的實現(xiàn),采用了擴展運算符和 some 的方法,兩者共同使用,達到數(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 兩個方法來共同實現(xiàn)數(shù)組扁平化,由于數(shù)組會默認帶一個 toString 的方法,所以可以把數(shù)組直接轉換成逗號分隔的字符串,然后再用 split 方法把字符串重新轉換為數(shù)組,如下面的代碼所示:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(',');
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
通過這兩個方法可以將多維數(shù)組直接轉換成逗號連接的字符串,然后再重新分隔成數(shù)組。
(5)ES6 中的 flat
我們還可以直接調(diào)用 ES6 中的 flat 方法來實現(xiàn)數(shù)組扁平化。flat 方法的語法:arr.flat([depth])
其中 depth 是 flat 的參數(shù),depth 是可以傳遞數(shù)組的展開深度(默認不填、數(shù)值是 1),即展開一層數(shù)組。如果層數(shù)不確定,參數(shù)可以傳進 Infinity,代表不論多少層都要展開:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.flat(Infinity);
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
可以看出,一個嵌套了兩層的數(shù)組,通過將 flat 方法的參數(shù)設置為 Infinity,達到了我們預期的效果。其實同樣也可以設置成 2,也能實現(xiàn)這樣的效果。在編程過程中,如果數(shù)組的嵌套層數(shù)不確定,最好直接使用 Infinity,可以達到扁平化。
(6)正則和 JSON 方法
在第4種方法中已經(jīng)使用 toString 方法,其中仍然采用了將 JSON.stringify 的方法先轉換為字符串,然后通過正則表達式過濾掉字符串中的數(shù)組的方括號,最后再利用 JSON.parse 把它轉換成數(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ù)組中的重復數(shù)字并且返回新的無重復數(shù)組。
ES6方法(使用數(shù)據(jù)結構集合):
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存儲不重復的數(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ù)必須是一個函數(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ù)必須是一個函數(shù)');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {
res.push(fn(this[i]));
}
return res;
}
輸入字符串s,以及其重復的次數(shù),輸出重復的結果,例如輸入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)) : "";
}
在字符串的原型鏈上添加一個方法,實現(xiàn)字符串翻轉:
String.prototype._reverse = function(a){
return a.split("").reverse().join("");
}
var obj = new String();
var res = obj._reverse ('hello');
console.log(res); // olleh
需要注意的是,必須通過實例化對象之后再去調(diào)用定義的方法,不然找不到該方法。
數(shù)字有小數(shù)版本:
let format = n => {
let num = n.toString() // 轉成字符串
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對數(shù)值有范圍的限制,限制如下:
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_VALUE // 5e-324
Number.MIN_SAFE_INTEGER // -9007199254740991
如果想要對一個超大的整數(shù)(> Number.MAX_SAFE_INTEGER
)進行加法運算,但是又想輸出一般形式,那么使用 + 是無法達到的,一旦數(shù)字超過 Number.MAX_SAFE_INTEGER
數(shù)字會被立即轉換為科學計數(shù)法,并且數(shù)字精度相比以前將會有誤差。
實現(xià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)是把接受多個參數(shù)的函數(shù)轉變?yōu)榻邮芤粋€單一參數(shù)的函數(shù),并且返回接受余下的參數(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
對于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)用時清空
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ù)組轉換為數(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)
// 轉換前:
source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 轉換為:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 1,
name: 'div'
}]
}
}]
代碼實現(xiàn):
function jsonToTree(data) {
// 初始化結果數(shù)組,并判斷輸入數(shù)據(jù)的格式
let result = []
if(!Array.isArray(data)) {
return result
}
// 使用map,將當前對象的id與當前對象對應存儲起來
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)
/* 結果
{ user: 'anonymous',
id: [ 123, 456 ], // 重復出現(xiàn)的 key 要組裝成數(shù)組,能被轉成數(shù)字的就轉成數(shù)字類型
city: '北京', // 中文需解碼
enabled: true, // 未指定值得 key 約定為 true
}
*/
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來
const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數(shù)組中
let paramsObj = {};
// 將 params 存到對象中
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; // 判斷是否轉為數(shù)字
if (paramsObj.hasOwnProperty(key)) { // 如果對象有 key,則添加一個值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果對象沒有這個 key,創(chuàng)建 key 并設置值
paramsObj[key] = val;
}
} else { // 處理沒有 value 的參數(shù)
paramsObj[param] = true;
}
})
return paramsObj;
}
下面來看一道比較典型的問題,通過這個問題來對比幾種異步編程方法:紅燈 3s 亮一次,綠燈 1s 亮一次,黃燈 2s 亮一次;如何讓三個燈不斷交替重復亮燈?
三個亮燈函數(shù):
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
這道題復雜的地方在于需要“交替重復”亮燈,而不是“亮完一次”就結束了。
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)
})
})
這里存在一個 bug:代碼只是完成了一次流程,執(zhí)行后紅黃綠燈分別只亮一次。該如何讓它交替重復進行呢?
上面提到過遞歸,可以遞歸亮燈的一個周期:
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)移除,在一次亮燈結束后,resolve 當前 promise,并依然使用遞歸進行。
const taskRunner = async () => {
await task(3000, 'red')
await task(2000, 'green')
await task(2100, 'yellow')
taskRunner()
}
taskRunner()
// 使用閉包實現(xiàn)
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
// 使用 let 塊級作用域
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
有30個小孩兒,編號從1-30,圍成一圈依此報數(shù),1、2、3 數(shù)到 3 的小孩兒退出這個圈, 然后下一個小孩 重新報數(shù) 1、2、3,問最后剩下的那個小孩兒的編號是多少?
function childNum(num, count){
let allplayer = [];
for(let i = 0; i < num; i++){
allplayer[i] = i + 1;
}
let exitCount = 0; // 離開人數(shù)
let counter = 0; // 記錄報數(shù)
let curIndex = 0; // 當前下標
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(`圖片請求成功,此處進行通用操作`);
resolve(image);
}
img.οnerrοr=(err)=>{
console.log(`失敗,此處進行失敗的通用操作`);
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) {
// 若沒有注冊該事件則拋出錯誤
if (!this.handlers[type]) {
return new Error('該事件未注冊')
}
// 觸發(fā)事件
this.handlers[type].forEach(handler => {
handler(...params)
})
}
// 4. 事件移除,參數(shù):事件名 要刪除事件,若無第二個參數(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);
})();
所謂的原型鏈繼承就是讓新實例的原型等于父類的實例:
//父方法
function SupperFunction(flag1){
this.flag1 = flag1;
}
//子方法
function SubFunction(flag2){
this.flag2 = flag2;
}
//父實例
var superInstance = new SupperFunction(true);
//子繼承父
SubFunction.prototype = superInstance;
//子實例
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(){
// 路由存儲對象
this.routes = {}
// 當前hash
this.currentHash = ''
// 綁定this,避免監(jiān)聽時this指向改變
this.freshRoute = this.freshRoute.bind(this)
// 監(jiān)聽
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存儲
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;
}
用一個滑動窗口裝沒有重復的字符,枚舉字符記錄最大值即可。用 map 維護字符的索引,遇到相同的字符,把左邊界移動過去即可。挪動的過程中記錄最大長度:
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 的作用是每隔一段指定時間執(zhí)行一個函數(shù),但是這個執(zhí)行不是真的到了時間立即執(zhí)行,它真正的作用是每隔一段時間將事件加入事件隊列中去,只有當當前的執(zhí)行棧為空的時候,才能去從事件隊列中取出事件執(zhí)行。所以可能會出現(xiàn)這樣的情況,就是當前執(zhí)行棧執(zhí)行的時間很長,導致事件隊列里邊積累多個定時器加入的事件,當執(zhí)行棧結束的時候,這些事件會依次執(zhí)行,因此就不能到間隔一段時間執(zhí)行的效果。
針對 setInterval 的這個缺點,我們可以使用 setTimeout 遞歸調(diào)用來模擬 setInterval,這樣我們就確保了只有一個事件結束了,我們才會觸發(fā)下一個定時器事件,這樣解決了 setInterval 的問題。
實現(xiàn)思路是使用遞歸函數(shù),不斷地去執(zhí)行 setTimeout 從而達到 setInterval 的效果
function mySetInterval(fn, timeout) {
// 控制器,控制定時器是否繼續(xù)執(zhí)行
var timer = {
flag: true
};
// 設置遞歸函數(shù),模擬定時器執(zhí)行。
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
// 啟動定時器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
// 動態(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");
// 設置一個全局的callback函數(shù)來接收回調(diào)結果
function handleRes(res) {
console.log(res);
}
// 接口返回的數(shù)據(jù)格式
handleRes({a: 1, b: 2});
循環(huán)引用對象本來沒有什么問題,但是序列化的時候就會發(fā)生問題,比如調(diào)用 JSON.stringify()
對該類對象進行序列化,就會報錯: Converting circular structure to JSON.
下面方法可以用來判斷一個對象中是否已存在循環(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ù)組的目標值:
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 列進行打印
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 行進行打印
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
}
更多建議: