天天看點

《每天進步一小步》— 25個關于js的開發小技巧

Object

1.對象變量屬性

const flag = true;
const obj = {
    a: 0,
    [flag ? "c" : "d"]: 2
};
// obj => { a: 0, c: 2 }
           

2.對象多餘屬性删除

const { name, age, ...obj } = { name: '張三', age: 13, dec: '描述1', info: '資訊' }
console.log(name)  // 張三
console.log(age)  // 13
console.log(obj)  // {dec: '描述1', info: '資訊' }
           

3.對象嵌套屬性解構

const { info:{ dec} } = { name: '張三', age: 13, info:{dec: '描述1', info: '資訊' }}
console.log(dec) // 描述1
           

4.解構對象屬性别名

const { name:newName } = { name: '張三', age: 13 }
console.log(newName)  // 張三
           

5.解構對象屬性預設值

const { dec='這是預設dec值' } = { name: '張三', age: 13 }
console.log(dec) //這是預設dec值
           

6.攔截對象

利用Object.defineProperty攔截對象

無法攔截數組的值

let obj = { name: '', age: '', sex: '' },
  defaultName = ["這是姓名預設值1", "這是年齡預設值1", "這是性别預設值1"];
Object.keys(obj).forEach(key => {
Object.defineProperty(obj, key, { // 攔截整個object 對象,并通過get擷取值,set設定值,vue 2.x的核心就是這個來監聽
get() {
return defaultName;
    },
set(value) {
      defaultName = value;
    }
  });
});

console.log(obj.name); // [ '這是姓名預設值1', '這是年齡預設值1', '這是性别預設值1' ]
console.log(obj.age); // [ '這是姓名預設值1', '這是年齡預設值1', '這是性别預設值1' ]
console.log(obj.sex); // [ '這是姓名預設值1', '這是年齡預設值1', '這是性别預設值1' ]
obj.name = "這是改變值1";
console.log(obj.name); // 這是改變值1
console.log(obj.age);  // 這是改變值1
console.log(obj.sex); // 這是改變值1

let objOne = {}, defaultNameOne = "這是預設值2";
Object.defineProperty(obj, 'name', {
get() {
return defaultNameOne;
  },
set(value) {
    defaultNameOne = value;
  }
});
console.log(objOne.name); // undefined
objOne.name = "這是改變值2";
console.log(objOne.name); // 這是改變值2
           

利用proxy攔截對象

let obj = { name: '', age: '', sex: '' }
let handler = {
get(target, key, receiver) {
    console.log("get", key); 
return Reflect.get(target, key, receiver);
  },
set(target, key, value, receiver) {
    console.log("set", key, value); // set name 李四  // set age 24
return Reflect.set(target, key, value, receiver);
  }
};
let proxy = new Proxy(obj, handler);
proxy.name = "李四";
proxy.age = 24;
           

defineProterty和proxy的對比:

1.defineProterty是es5的标準,proxy是es6的标準;

2.proxy可以監聽到數組索引指派,改變數組長度的變化;

3.proxy是監聽對象,不用深層周遊,defineProterty是監聽屬性;

4.利用defineProterty實作雙向資料綁定(vue2.x采用的核心)

7.對象深度拷貝

JSON.stringify深度克隆對象;

1.無法對函數 、RegExp等特殊對象的克隆;

2.會抛棄對象的constructor,所有的構造函數會指向Object;

3.對象有循環引用,會報錯

const objDeepClone = obj => {
return clone(obj)
}

const isType = (obj, type) => {
if (typeof obj !== 'object') return false;
// 判斷資料類型的經典方法:
const typeString = Object.prototype.toString.call(obj);
  let flag;
switch (type) {
case 'Array':
      flag = typeString === '[object Array]';
break;
case 'Date':
      flag = typeString === '[object Date]';
break;
case 'RegExp':
      flag = typeString === '[object RegExp]';
break;
default:
      flag = false;
  }
return flag;
};

/**
* deep clone
* @param  {[type]} parent object 需要進行克隆的對象
* @return {[type]}        深克隆後的對象
*/
const clone = parent => {
// 維護兩個儲存循環引用的數組
const parents = []
const children = []

const _clone = parent => {
if (parent === null) return null
if (typeof parent !== 'object') return parent

    let child, proto

if (isType(parent, 'Array')) {
// 對數組做特殊處理
      child = []
    } else if (isType(parent, 'RegExp')) {
// 對正則對象做特殊處理
      child = new RegExp(parent.source, getRegExp(parent))
if (parent.lastIndex) child.lastIndex = parent.lastIndex
    } else if (isType(parent, 'Date')) {
// 對Date對象做特殊處理
      child = new Date(parent.getTime())
    } else {
// 處理對象原型
      proto = Object.getPrototypeOf(parent)
// 利用Object.create切斷原型鍊
      child = Object.create(proto)
    }

// 處理循環引用
const index = parents.indexOf(parent)

if (index !== -1) {
// 如果父數組存在本對象,說明之前已經被引用過,直接傳回此對象
return children[index]
    }
    parents.push(parent)
    children.push(child)

for (const i in parent) {
// 遞歸
      child[i] = _clone(parent[i])
    }

return child
  }
return _clone(parent)
}

console.log(objDeepClone({ 
  name: '張三', age: 23, 
  obj: { name: '李四', age: 46},
  arr:[1,2,3]
})) // { name: '張三', age: 23, obj: { name: '李四', age: 46 }, arr:[ 1, 2, 3 ] }
           

對象深度克隆實際上就是要相容Array,RegExp,Date,Function類型;

克隆函數可以用正則取出函數體和參數,再定義一個函數将取出來的值指派進去

詳細請戳對象深度拷貝

8.對象是否相等

如果用JSON.stringify轉化屬性順序不同,也不相等;

而且不支援無法對函數 、RegExp等特殊對象的克隆

function deepCompare(x, y) {
var i, l, leftChain, rightChain;

function compare2Objects(x, y) {
var p;

// remember that NaN === NaN returns false
// and isNaN(undefined) returns true
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
return true;
    }

// Compare primitives and functions.     
// Check if both arguments link to the same object.
// Especially useful on the step where we compare prototypes
if (x === y) {
return true;
    }

// Works in case when functions are created in constructor.
// Comparing dates is a common scenario. Another built-ins?
// We can even handle functions passed across iframes
if ((typeof x === 'function' && typeof y === 'function') ||
      (x instanceof Date && y instanceof Date) ||
      (x instanceof RegExp && y instanceof RegExp) ||
      (x instanceof String && y instanceof String) ||
      (x instanceof Number && y instanceof Number)) {
return x.toString() === y.toString();
    }

// At last checking prototypes as good as we can
if (!(x instanceof Object && y instanceof Object)) {
return false;
    }

if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
return false;
    }

if (x.constructor !== y.constructor) {
return false;
    }

if (x.prototype !== y.prototype) {
return false;
    }

// Check for infinitive linking loops
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
return false;
    }

// Quick checking of one object being a subset of another.
// todo: cache the structure of arguments[0] for performance
for (p in y) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
      } else if (typeof y[p] !== typeof x[p]) {
return false;
      }
    }

for (p in x) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
      } else if (typeof y[p] !== typeof x[p]) {
return false;
      }

switch (typeof (x[p])) {
case 'object':
case 'function':

          leftChain.push(x);
          rightChain.push(y);

if (!compare2Objects(x[p], y[p])) {
return false;
          }

          leftChain.pop();
          rightChain.pop();
break;

default:
if (x[p] !== y[p]) {
return false;
          }
break;
      }
    }

return true;
  }

if (arguments.length < 1) {
return true; 
  }

for (i = 1, l = arguments.length; i < l; i++) {

    leftChain = []; //Todo: this can be cached
    rightChain = [];

if (!compare2Objects(arguments[0], arguments[i])) {
return false;
    }
  }

return true;
}

const obj1 = { 
name: '張三', age: 23, 
obj: { name: '李四', age: 46 }, 
arr: [1, 2, 3],
date:new Date(23),
reg: new RegExp('abc'),
fun: ()=>{}
 }
const obj2 = { 
name: '張三', age: 23, 
obj: { name: '李四', age: 46 }, 
arr: [1, 2, 3],
date: new Date(23),
reg: new RegExp('abc'),
fun: ()=>{}
 }
console.log(deepCompare(obj1,obj2)) // true
           

判斷對象是否相等,實際上就是要處理Array,Date,RegExp,Object,Function的特殊類型是否相等

9.對象轉化為字元串

通過字元串+Object 的方式來轉化對象為字元串(實際上是調用 .toString() 方法)

'the Math object:' + Math.ceil(3.4)                // "the Math object:4"
'the JSON object:' + {name:'曹操'}              // "the JSON object:[object Object]"
           

覆寫對象的toString和valueOf方法來自定義對象的類型轉換

2  * { valueOf: ()=>'4' }                // 8
'J' + { toString: ()=>'ava' }                // "Java"
           

當+用在連接配接字元串時,當一個對象既有toString方法又有valueOf方法時候,JS通過盲目使用valueOf方法來解決這種含糊;

對象通過valueOf方法強制轉換為數字,通過toString方法強制轉換為字元串

Function

10.函數隐式傳回值

(()=>3)()  //3
(()=>(
3
))()
           

函數省略大括号,或者将大括号改成小括号可以確定代碼以單個語句的形式進行求值

11.函數自執行

const Func = function() {}(); // 常用

(function() {})(); // 常用
(function() {}()); // 常用
[function() {}()];

new function() {};
new function() {}();
void function() {}();
typeof function() {}();
delete function() {}();

+ function() {}();
- function() {}();
~ function() {}();
! function() {}();
           

12.函數異步執行

Promise

Promise.reject('這是第二個 reject 值').then((data)=>{
console.log(data)
}).catch(data=>{
console.log(data) //這是第二個 reject 值
})
           

Generator

function* gen(x) {
const y = yield x + 6;
return y;
}

// yield 如果用在另外一個表達式中,要放在()裡面
// 像上面如果是在=右邊就不用加()
function* genOne(x) {
const y = `這是第一個 yield 執行:${yield x + 1}`;
return y;
}

const g = gen(1);
//執行 Generator 會傳回一個Object,而不是像普通函數傳回return 後面的值
g.next() // { value: 7, done: false }
//調用指針的 next 方法,會從函數的頭部或上一次停下來的地方開始執行,直到遇到下一個 yield 表達式或return語句暫停,也就是執行yield 這一行
// 執行完成會傳回一個 Object,
// value 就是執行 yield 後面的值,done 表示函數是否執行完畢
g.next() // { value: undefined, done: true }
// 因為最後一行 return y 被執行完成,是以done 為 true
           

Async/Await

function getSomething() {
return "something";
}
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2); //something 和 hello async
}
test();
           

String

13.字元串翻轉

function reverseStr(str = "") {
return str.split("").reduceRight((t, v) => t + v);
}

const str = "reduce123";
console.log(reverseStr(str)); // "123recuder"
           

14.url參數序列化

将對象序列化成url參數傳遞

function stringifyUrl(search = {}) {
return Object.entries(search).reduce(
(t, v) => `${t}${v[0]}=${encodeURIComponent(v[1])}&`,
Object.keys(search).length ? "?" : ""
  ).replace(/&$/, "");
}

console.log(stringifyUrl({ age: 27, name: "YZW" })); // "?age=27&name=YZW"
           

15.url參數反序列化

一般會通過location.search拿到路由傳遞的參數,并進行反序列化得到對象

function parseUrlSearch() {
const search = '?age=25&name=TYJ'
return search.replace(/(^\?)|(&$)/g, "").split("&").reduce((t, v) => {
const [key, val] = v.split("=");
    t[key] = decodeURIComponent(val);
return t;
  }, {});
}

console.log(parseUrlSearch()); // { age: "25", name: "TYJ" }
           

16.轉化為字元串

const val = 1 + ""; // 通過+ ''空字元串轉化
console.log(val); // "1"
console.log(typeof val); // "string"

const val1 = String(1);
console.log(val1); // "1"
console.log(typeof val1); // "string"
           

Number

17.數字千分位

function thousandNum(num = 0) {
const str = (+num).toString().split(".");
const int = nums => nums.split("").reverse().reduceRight((t, v, i) => t + (i % 3 ? v : `${v},`), "").replace(/^,|,$/g, "");
const dec = nums => nums.split("").reduce((t, v, i) => t + ((i + 1) % 3 ? v : `${v},`), "").replace(/^,|,$/g, "");
return str.length > 1 ? `${int(str[0])}.${dec(str[1])}` : int(str[0]);
}

thousandNum(1234); // "1,234"
thousandNum(1234.00); // "1,234"
thousandNum(0.1234); // "0.123,4"
console.log(thousandNum(1234.5678)); // "1,234.567,8"
           

18.字元串轉數字

方法一

用*1來轉化為數字,實際上是調用.valueOf方法

'32' * 1            // 32
'ds' * 1            // NaN
null * 1            // 0
undefined * 1    // NaN
1  * { valueOf: ()=>'3' }        // 3
           

方法二

+ '123'            // 123
+ 'ds'               // NaN
+ ''                    // 0
+ null              // 0
+ undefined    // NaN
+ { valueOf: ()=>'3' }    // 3
           

19.判斷小數是否相等

肯定有人會說這還不簡單,直接用’==='比較;

實際上0.1+0.2 !==0.3,因為計算機不能精确表示0.1, 0.2這樣的浮點數,是以相加就不是0.3了

Number.EPSILON=(function(){   //解決相容性問題
return Number.EPSILON?Number.EPSILON:Math.pow(2,-52);
})();
//上面是一個自調用函數,當JS檔案剛加載到記憶體中,就會去判斷并傳回一個結果
function numbersequal(a,b){ 
return Math.abs(a-b)<Number.EPSILON;
  }
//接下來再判斷   
const a=0.1+0.2, b=0.3;
console.log(numbersequal(a,b)); //這裡就為true了
           

20.雙位運算符

雙位運算符比Math.floor(),Math.ceil()速度快

~~7.5                // 7
Math.ceil(7.5)       // 8
Math.floor(7.5)      // 7


~~-7.5            // -7
Math.floor(-7.5)     // -8
Math.ceil(-7.5)      // -7
           

是以負數時,雙位運算符和Math.ceil結果一緻,正數時和Math.floor結果一緻

21.取整和奇偶性判斷

取整

3.3 | 0         // 3
-3.9 | 0        // -3

parseInt(3.3)  // 3
parseInt(-3.3) // -3

// 四舍五入取整
Math.round(3.3) // 3
Math.round(-3.3) // -3

// 向上取整
Math.ceil(3.3) // 4
Math.ceil(-3.3) // -3

// 向下取整
Math.floor(3.3) // 3
Math.floor(-3.3) // -4
           

判斷奇偶數

const num=5;
!!(num & 1) // true
!!(num % 2) // true
           

Boolean

22.判斷資料類型

function dataTypeJudge(val, type) {
const dataType = Object.prototype.toString.call(val).replace(/\[object (\w+)\]/, "$1").toLowerCase();
return type ? dataType === type : dataType;
}
console.log(dataTypeJudge("young")); // "string"
console.log(dataTypeJudge(20190214)); // "number"
console.log(dataTypeJudge(true)); // "boolean"
console.log(dataTypeJudge([], "array")); // true
console.log(dataTypeJudge({}, "array")); // false
           

可判斷類型:undefined、null、string、number、boolean、array、object、symbol、date、regexp、function、asyncfunction、arguments、set、map、weakset、weakmap

23.使用Boolean過濾數組假值

const compact = arr => arr.filter(Boolean)
compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34])  //[ 1, 2, 3, 'a', 's', 34 ]
           

24.短路運算

||(或)

const flag = false || true //true
// 某個值為假時可以給預設值
const arr = false || []
           

&&(與)

const flag1 = false && true //false
const flag2 = true && true //true
           

25.switch 簡寫

可以用對象替代switch,提高代碼可讀性

switch(a) {
case '張三':
return 'age是12'
case '李四':
return 'age是120'
}

// 使用對象替換後
const obj ={
'張三': 'age12',
'李四': 'age120',
}
console.log(obj['張三'])
           
來源:web前端開發公衆号

繼續閱讀