整理複習一些JS「基礎」

重學一些JS基礎html

手機上看的話,由於掘金網頁/App端沒有導航欄~vue

而後我本身用 vuepress 作的文章備份,用 github Page 部署的,須要能夠看 這裏git

js 文件的話,能夠看 這裏github

一、面向對象

1.1 封裝

function Animal(place) {
  this.place = place;
  this.type = 'animal';

  this.run = function(m) {
    console.log('run: ', m);
  }
}
Animal.prototype = {
  a: 'a'
}
複製代碼

1.2 繼承

new 的時候作了什麼?

  1. 建立一個新對象;
  2. 將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象);
  3. 執行構造函數中的代碼(爲這個新對象添加屬性);
  4. 返回新對象。
function F(type) {
  this.type = type;
  this.f = 'f';

  this.run = function(m) {
    console.log('run: ', m);
  }
}
F.prototype.a = 'a';

function _new() {
  var obj = {};
  var fn = Array.prototype.shift.call(arguments);

  obj.__proto__ = fn.prototype;
  var newObj = fn.apply(obj, arguments);

  return (
    Object.prototype.toString.call(newObj) === '[object Object]'
    ? newObj
    : obj
  );
}

// var f1 = new F();
var f1 = _new(F, 'F4');
console.log('f: ', f1); // { type: 'F4', f: 'f', run: [Function] }
console.log('a: ', f1.a); // a
console.log('type: ', f1.type); // F4
f1.run(2); // 2
複製代碼

繼承方式1:原型

  • 能夠繼承父類內置方法、原型屬性
  • 缺點:不能向構造函數傳參數;與父類共享,互相影響
function Cat1() {
  this.c = 'c';
}
Cat1.prototype = new Animal();

console.log('-------- 1 ---------');
const cc1 = new Cat1();
console.log(cc1.type); // animal
console.log(cc1.a); // a
console.log(cc1.place); // undefined
cc1.run(2); // 2
複製代碼

繼承方式2:call/apply

繼承父類內置方法面試

  • 缺點:沒法繼承父類的原型屬性
  • 解決:看方法三、方法4
function Cat2() {
  // Animal.apply(this, arguments);
  Animal.apply(this, ['深圳']);
  // this.type = 'cat'; // 會覆蓋上面繼承的東西
  this.c = 'c';
}

console.log('--------- 2 --------');
const cc2 = new Cat2();
console.log(cc2.type); // animal
console.log(cc2.a); // undefined
console.log(cc2.place); // 深圳
cc2.run(2); // 2
複製代碼

繼承方式3:Object.create

繼承父類的原型屬性算法

  • 缺點:不能向構造函數傳參數;
  • 解決:與方式2一塊兒用
function Cat3() {
  // Animal.apply(this, arguments);
  Animal.apply(this, ['深圳']);
  // this.type = 'cat'; // 會覆蓋上面繼承的東西
  this.c = 'c';
}
Cat3.prototype = Object.create(Animal.prototype);
Cat3.prototype.constructor = Cat3;

console.log('--------- 3 --------');
const cc3 = new Cat3();
console.log(cc3.type); // animal
console.log(cc3.a); // a
console.log(cc3.place); // 深圳
cc3.run(2); // 2
複製代碼

繼承方式4:寄生+組合

  • 缺點:沒法繼承父類內置方法
  • 解決:與方式2一塊兒用
function Cat4() {
  // Animal.apply(this, arguments);
  Animal.apply(this, ['深圳']);
  // this.type = 'cat'; // 會覆蓋上面繼承的東西
  this.c = 'c';
}

// 能夠改一下,Function.prototype.extend 寫方法
// 使用更方便:Child.extend(Parent);
function extend(Child, Parent) {
  var fn = function() {};
  fn.prototype = Parent.prototype;
  Child.prototype = new fn();
  Child.prototype.constructor = Child;
  Child.uber = Parent.prototype;
}

extend(Cat4, Animal);

console.log('--------- 4 --------');
const cc4 = new Cat4();
console.log(cc4.type); // animal
console.log(cc4.a); // a
console.log(cc4.place); // 深圳
cc4.run(2); // 2
複製代碼

二、事件循環 Event Loop

瀏覽器是單線程,一旦遇到異步任務就會把其中的內容放到任務隊列 Task; 而後瀏覽器在執行同步任務的同時,不斷輪詢任務隊列,若是任務隊列中有任務,會按照 先進先出 的順序執行;數組

2.1 異步任務

分爲微任務 Micro Task,宏任務 Macro Task;瀏覽器

宏任務隊列 中的宏任務 會在上一個宏任務執行完時執行;bash

微任務隊列 中的微任務 則是在主線程空閒時(如每個宏任務執行完)執行;期間有新的微任務會繼續執行,微任務都執行完纔會繼續輪詢 宏任務隊列;閉包

宏任務 Macro Task

  • 瀏覽器
    setTimeout, setInterval, requestAnimationFrame, I/O
  • Node.js
    setTimeout, setInterval, setImmediate

微任務 Micro Task

  • 瀏覽器
    Promsie.then, Promsie.catch, Promsie.finally, MutationObserver
  • Node.js
    Promsie.then, Promsie.catch, Promsie.finally, process.nextTick
console.log('Start!');

setTimeout(() => {
  console.log('setTimeout1')
}, 0);

new Promise((resolve, reject) => {
  console.log('Promise');
  resolve();

  setTimeout(() => {
    console.log('setTimeout2');
  }, 0);

  Promise.resolve().then(() => {
    console.log('then2');
  })
}).then(() => {
  console.log('then1');
})

console.log('End!');

// Start, Promise, End, then2, then1, setTimeout1, setTimeout2;
複製代碼

2.2 循環異步輸出

for 循環 調用 setTimeout 問題

由於 for 循環 是同步的,而 setTimeout 是異步宏任務,每一次循環都會在任務隊列添加一次 console.log(i) ,等到 i===9 的時候循環結束,這個時候 i++ 因而 i=10 了,再依次調用 console.log(i),因此打印 10個10

// 10個10
for (var i=0; i<10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}
複製代碼

解決方法1: setTimeout 第三個參數

// 0-9
for (var i=0; i<10; i++) {
  setTimeout((j) => {
    console.log(j);
  }, 0, i);
}
複製代碼

解決方法2: 閉包

// 0-9
for (var i=0; i<10; i++) {
  (function(j) {
    setTimeout(() => {
      console.log(j);
    }, 0)
  })(i)
}
複製代碼

解決方法3: let

let 有塊級做用域

這樣是每次 for 都是獨立的 i

// 0-9
for (let i=0; i<10; i++) {
  setTimeout((i) => {
    console.log(i);
  }, 0, i);
}
複製代碼

這樣就不行了,全部的 i 都是同一個 i

// 10個10
let i;
for (i=0; i<10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}
複製代碼

三、this

執行上下文,重在 執行 二字;

能夠理解爲是一個對象,通常函數是哪一個對象的 key,這個函數的 this 就是那個對象;除非 call/bind/apply 改變了 this

/** * this */
function a() {
  // 若是 a.call(obj),則是 { a: 'a', fn: [Function: fn] },
  // 不然 global/Window
  console.log('a: ', this); 
  
  b();
  function b() {
    console.log('b: ', this); // global/Window
    c();
    function c() {
      console.log('c: ', this); // global/Window
    }
  }
}

const obj = {
  a: 'a',
  fn: function() {
    console.log('fn: ', this); // { a: 'a', fn: [Function: fn] }
  }
};

obj.fn();

a.call(obj);
複製代碼

3.1 箭頭函數

看這位大佬的 文章

箭頭函數與普通函數的區別:

  • 沒有 this
  • 沒有 arguments
  • 沒法 call/bind/apply 切換 this
  • 沒有原型
  • 沒有構造函數,不能 new

3.2 call/apply/bind

都是切換上下文,綁定 this

call

切換上下文,當即執行,參數展開非數組

/** * call * 切換上下文,當即執行,參數展開非數組 * @param {*} ctx 執行上下文 */
Function.prototype._call = function(ctx) {
  ctx = ctx || {};
  ctx.fn = this;

  var args = [];
  // 展開參數
  // arguments[0] 是 ctx.fn 函數
  for(var i=1; i<arguments.length; i++) {
    args.push('arguments['+ i +']');
  }

  var res = eval('ctx.fn('+ args +')')
  
  delete ctx.fn;
  return res;
}

// 測試
var a = 'window-a';
var obj = {
  a: 'obj-a',
  fn: function(c, d) {
    console.log('a:', this.a);
    console.log('c:', c);
    console.log('d:', d);
  }
};
var obj2 = {
  a: 'obj2-a'
};
var obj3 = {
  a: 'obj3-a'
};

// a: obj2-a, c: cc2, d: dd2
obj.fn._call(obj2, 'cc2', 'dd2'); 

// a: obj3-a, c: cc3, d: dd3
obj.fn._call(obj3, 'cc3', 'dd3'); 

// a: window-a, c: cc-null, d: dd
obj.fn._call(null, 'cc-null', 'dd'); 

// a: window-a, c: cc-undefined, d: dd
obj.fn._call(undefined, 'cc-undefined', 'dd'); 
複製代碼

apply

切換上下文,當即執行,參數是數組

/** * apply * 切換上下文,當即執行,參數爲數組 * @param {*} ctx 執行上下文 */
Function.prototype._apply = function(ctx) {
  if (
    arguments.length > 2 ||
    Object.prototype.toString.call(arguments[1]) !== '[object Array]'
  ) {
    console.warn('參數只能一個,且爲數組!');
    return;
  }
  ctx = ctx || {};
  ctx.fn = this;

  var args = [];
  for(var i=0; i<arguments[1].length; i++) {
    args.push('arguments[1]['+ i +']');
  }

  var res = eval('ctx.fn('+ args +')');

  delete ctx.fn;
  return res;
}

// 測試
var a = 'window-a';
var obj = {
  a: 'obj-a',
  fn: function(c, d) {
    console.log('a:', this.a);
    console.log('c:', c);
    console.log('d:', d);
  }
};
var obj2 = {
  a: 'obj2-a'
};
var obj3 = {
  a: 'obj3-a'
};

// a: obj-a, c: cc, d: dd
obj.fn('cc', 'dd');

// a: obj2-a, c: cc2, d: dd2
obj.fn._apply(obj2, ['cc2', 'dd2']);

// a: obj3-a, c: cc3, d: dd3
obj.fn._apply(obj3, ['cc3', 'dd3']);

// a: window-a, c: cc-null, d: dd-null
obj.fn._apply(null, ['cc-null', 'dd-null']);

// a: window-a, c: cc-undefined, d: dd-undefined
obj.fn._apply(undefined, ['cc-undefined', 'dd-undefined']);
複製代碼

bind

切換上下文,返回一個新函數;不會當即執行

/** * bind * 切換上下文,返回一個新函數;不會當即執行 * @param {*} ctx 執行上下文 */
Function.prototype._bind = function(ctx) {
  ctx = ctx || {};
  ctx.fn = this;

  var args = [];
  // 展開參數
  // arguments[0] 是 ctx.fn 函數
  for (var i=1; i<arguments.length; i++) {
    args.push('arguments['+ i +']');
  }
  
  var res = eval('ctx.fn(' + args +')');

  delete ctx.fn;
  return function() {
    res;
  };
}

// 測試
var a = 'window-a';
var obj = {
  a: 'obj-a',
  fn: function(c, d) {
    console.log('a:', this.a);
    console.log('c:', c);
    console.log('d:', d);
  }
};
var obj2 = {
  a: 'obj2-a'
};
var obj3 = {
  a: 'obj3-a'
};

obj.fn('c', 'd'); // a: obj-a, c: c, d: d

var fn1 = obj.fn._bind(null, 'cc1', 'dd1');
fn1(); // a: window-a, c: cc1, dd1

var fn2 = obj.fn._bind(obj2, 'cc2', 'dd2');
fn2(); // a: obj2-a, c: cc2, d: dd2

var fn3 = obj.fn._bind(obj3, 'cc3', 'dd3');
fn3(); // a: obj3-a, c: cc3, d: dd3

// 這個時候是不能再綁定的,因此打印的是第一次綁定的內容
fn3.bind(obj2, 'cc2', 'dd2');
fn3(); // a: obj3-a, c: cc3, d: dd3
複製代碼

3.3 閉包

內部函數,私有變量

  • 閉包:有權訪問外部做用域的私有變量的函數;
  • 被閉包引用的變量不會被自動清理(gc)

也能夠這麼理解:函數的內部函數引用外部的私有變量,那麼內部函數就是閉包;

複製代碼

四、對象/數組拷貝

4.1 淺拷貝

只拷貝一層 key,若是這個 key 是複雜數據類型(Object/Array)的話,有引用賦值

function clone(objArr) {
  var getType = o => Object.prototype.toString.call(o);
  var isObjectOrArray = o => (
    getType(o) === '[object Object]' 
    || getType(o) === '[object Array]'
  );
  if (!isObjectOrArray(objArr)) return objArr;

  var newObj = getType(objArr) === '[object Object]' ? {} : [];
  Object.keys(objArr).forEach(item => {
    newObj[item] = objArr[item];
  })
  return newObj;
}

var obj1 = {
  a: 'a',
  b: {
    c: 'c1'
  }
};

var obj2 = clone(obj1);
console.log(obj2); // { a: 'a', b: { c: 'c1' } }
obj2.a = 'a2';
obj2.b.c = 'c2';
console.log(obj2); // { a: 'a2', b: { c: 'c2' } }
console.log(obj1); // { a: 'a', b: { c: 'c2' } }

var arr1 = [1, [3]];
var arr2 = clone(arr1);
console.log(arr2); // [ 1, [ 3 ] ]
arr2[0] = 2;
arr2[1][0] = 4;
console.log(arr2); // [ 2, [ 4 ] ]
console.log(arr1); // [ 1, [ 4 ] ]
複製代碼

4.2 深拷貝

  • 判斷 objArr 是否對象或數組,否的話直接返回;
  • 是的話,對象則給新變量初始化爲對象 {},數組則 []
  • 而後 循環判斷每一個 keykey 的值是對象或數組的話 遞歸執行
/** * 深拷貝 * 判斷 `obj` 是否對象或數組,否的話直接返回; * 是的話,對象則給新變量初始化爲對象 `{}`,數組則 `[]`, * 而後 循環判斷每一個 `key`,`key` 的值是對象或數組的話 遞歸執行 */
function deepClone(objArr) {
  var getType = o => Object.prototype.toString.call(o);
  var isObjectOrArray = o => (
    getType(o) === '[object Object]' 
    || getType(o) === '[object Array]'
  );
  if (!isObjectOrArray(objArr)) return objArr;

  var newObjArr = getType(objArr) === '[object Object]' ? {} : [];

  Object.keys(objArr).forEach(item => {
    newObjArr[item] = isObjectOrArray(objArr[item])
      ? deepClone(objArr[item])
      : objArr[item]
  })

  return newObjArr; 
}

var obj3 = {
  a: 'a',
  b: {
    c: 'c1',
    e: {
      f: 'f'
    }
  },
  d: [0, 1, [2]]
};

var obj4 = deepClone(obj3);
console.log(obj4); // { a: 'a', b: { c: 'c1', e: { f: 'f' } }, d: [ 0, 1, [ 2 ] ] }
obj4.a = 'a2';
obj4.b.c = 'c2';
obj4.b.e.f = 'f1';
obj4.d[0] = 1;
obj4.d[2][0] = 3;
console.log(obj4); // { a: 'a2', b: { c: 'c2', e: { f: 'f1' } }, d: [ 1, 1, [ 3 ] ] }
console.log(obj3); // { a: 'a', b: { c: 'c1', e: { f: 'f' } }, d: [ 0, 1, [ 2 ] ] }
複製代碼

五、ES6一些數組方法的實現

5.1 Array.prototype.map

  • item循環序列號 做爲兩個參數傳給回調函數
  • 回調函數的返回值做爲 item,返回一個與原數組同樣長度的新數組

語法:

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Return element for new_array
}[, thisArg])
複製代碼

實現:

/** * Array.prototype.map * 將 `item`、`循環序列號` 做爲兩個參數傳給回調函數 * 回調函數的返回值做爲 `item`,返回一個與原數組同樣長度的新數組 */
Array.prototype._map = function(cb) {
  var arr = this;
  var _this = arguments[1] || window;
  var newArr = [];

  // while 寫法
  var i = 0;
  while(i < arr.length) {
    newArr.push(cb.call(_this, arr[i], i, arr));
    i++;
  }
  
  // for 循環寫法:
  // for(var i=0; i<arr.length; i++) {
  // newArr.push(cb.call(_this, arr[i], i, arr));
  // }

  return newArr;
}

// 測試
var arr = [
  { a: 'a1', b: 'b1', c: ['c1'], d: 'd' },
  { a: 'a2', b: 'b1', c: ['c2'], d: 'd' },
  { a: 'a3', b: 'b2', c: ['c2'], d: 'd' },
  { a: 'a4', b: 'b3', c: ['c3'], d: 'd' },
];
var arr1 = arr._map(function(item) {
  console.log('this: ', this); // { a: 'aaaa' }
  return item.a
}, { a: 'aaaa' });
var arr2 = arr._map(item => item.b);
var arr3 = arr._map(item => {
  return {
    a: 'aa',
    b: item.b
  }
});
console.log(arr1); // [ 'a1', 'a2', 'a3', 'a4' ]
console.log(arr2); // [ 'b1', 'b1', 'b2', 'b3' ]
console.log(arr3);
複製代碼

5.2 Array.prototype.forEach

for 循環,將 item循環序列號當前數組 做爲參數傳給回調函數;循環直接執行回調函數

語法:

arr.forEach(callback(currentValue [, index [, array]])[, thisArg]);
複製代碼

實現:

/** * Array.prototype.forEach * for 循環,將 `item`,`循環序列號`,`當前數組` 做爲參數傳給回調函數;循環直接執行回調函數 */
Array.prototype._forEach = function(cb) {
  var arr = this;
  var _this = arguments[1] || window;
  var i = 0;

  while(i < arr.length) {
    cb.call(_this, arr[i], i, arr);
    i++;
  }
}

// 測試
var arr = [
  { a: 'a1', b: 'b1', c: ['c1'], d: 'd' },
  { a: 'a2', b: 'b1', c: ['c2'], d: 'd' },
  { a: 'a3', b: 'b2', c: ['c2'], d: 'd' },
  { a: 'a4', b: 'b3', c: ['c3'], d: 'd' },
];
arr.forEach(function(item) {
  console.log('this: ', this); // this: { a: 'aaaa' }

  item.a = 'aa';
  item['d'] = 'dd';
}, { a: 'aaaa' });
console.log(arr);
/** [ { a: 'aa', b: 'b1', c: [ 'c1' ], d: 'dd' }, { a: 'aa', b: 'b1', c: [ 'c2' ], d: 'dd' }, { a: 'aa', b: 'b2', c: [ 'c2' ], d: 'dd' }, { a: 'aa', b: 'b3', c: [ 'c3' ], d: 'dd' } ] */
複製代碼

5.3 Array.prototype.filter

  • item循環序列號當前數組 做爲參數傳給回調函數;
  • 回調函數的返回值做爲條件,去過濾原數組,返回符合條件的 item 組成的數組

語法:

var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
複製代碼

實現:

/** * Array.prototype.filter * 將 `item`,`循環序列號`,`當前數組` 做爲參數傳給回調函數; * 回調函數的返回值做爲條件,去過濾原數組,返回符合條件的 `item` 組成的數組 */
Array.prototype._filter = function(cb) {
  var arr = this;
  var _this = arguments[1] || window;
  var newArr = [];
  var i = 0;

  while(i < arr.length) {
    var res = Boolean(cb.call(_this, arr[i], i));
    if (res) newArr.push(arr[i]);
    i++;
  }

  return newArr;
}

var arr1 = arr._filter(function(item) {
  console.log('this: ', this); // { a: 'aaaa' }
  return item.a === 'a1'
}, { a: 'aaaa' });
var arr2 = arr._filter(item => item);
console.log(arr1); // [ { a: 'a1', b: 'b1', c: [ 'c1' ], d: 'd' } ]
console.log(arr2);
/** [ { a: 'a1', b: 'b1', c: [ 'c1' ], d: 'd' }, { a: 'a2', b: 'b1', c: [ 'c2' ], d: 'd' } ] */
複製代碼

5.4 Array.prototype.find

  • item循環序列號當前數組 做爲參數傳給回調函數;
  • 回調函數的返回值做爲條件,只找一個,返回第一個符合條件的 item

語法:

var item = arr.find(callback(element[, index[, array]])[, thisArg])
複製代碼

實現:

/** * Array.prototype.find * 將 `item`,`循環序列號`,`當前數組` 做爲參數傳給回調函數; * 回調函數的返回值做爲條件,只找一個,返回第一個符合條件的 `item` */
Array.prototype._find = function(cb) {
  var arr = this;
  var _this = arguments[1] || window;
  var item = null;
  var i = 0;

  while(i < arr.length && item === null) {
    if (Boolean(cb.call(_this, arr[i], i))) {
      item = arr[i];
    }
    i++;
  }

  return item;
}

// 測試
var arr = [
  { a: 'a1', b: 'b1', c: ['c1'], d: 'd' },
  { a: 'a2', b: 'b1', c: ['c2'], d: 'd' },
  { a: 'a3', b: 'b2', c: ['c2'], d: 'd' },
  { a: 'a4', b: 'b3', c: ['c3'], d: 'd' },
];
var item1 = arr._find(item => item);
var item2 = arr._find(function(item) {
  console.log('this: ', this); // { a: 'aaaa' }
  return item.b === 'b1'
}, { a: 'aaaa' });
var item3 = arr._find(item => item.b === 'b2');
console.log(item1); // { a: 'a1', b: 'b1', c: [ 'c1' ], d: 'd' }
console.log(item2); // { a: 'a1', b: 'b1', c: [ 'c1' ], d: 'd' }
console.log(item3); // { a: 'a3', b: 'b2', c: [ 'c2' ], d: 'd' }
複製代碼

5.5 Array.prototype.every

  • item循環序列號當前數組 做爲參數傳給回調函數;
  • 回調函數的返回值做爲條件,判斷是否全部 item 符合;也能夠反向用 Array.some 找一個不符合的來替代

語法:

var isTrue = arr.every(callback(element[, index[, array]])[, thisArg])
複製代碼

實現:

/** * Array.prototype.every * 將 `item`,`循環序列號`,`當前數組` 做爲參數傳給回調函數; * 回調函數的返回值做爲條件,判斷是否全部 `item` 符合;也能夠反向用 `Array.some` 找一個不符合的來替代 */
Array.prototype._every = function(cb) {
  var arr = this;
  var _this = arguments[1] || window;
  var result = false;
  var i = 0;

  while(i < arr.length) {
    result = Boolean(cb.call(_this, arr[i], i));
    i++;
  }

  return result;
}

var res1 = arr._every(function(item) {
  console.log('this: ', this); // { a: 'aaaa' }
  return item.d === 'd'
}, { a: 'aaaa' });
var res2 = arr._every(item => item.a === 'a');
console.log(res1); // true
console.log(res2); // false
複製代碼

5.6 Array.prototype.some

  • item循環序列號當前數組 做爲參數傳給回調函數;
  • 查找符合條件的 item,只找一個,返回 Boolean

語法:

var hasItem = arr.some(callback(element[, index[, array]])[, thisArg])
複製代碼

實現:

/** * Array.prototype.some * 將 `item`,`循環序列號`,`當前數組` 做爲參數傳給回調函數; * 查找符合條件的 `item`,只找一個,返回 `Boolean` */
Array.prototype._some = function(cb) {
  var arr = this;
  var _this = arguments[1] || window;
  var result = false;
  var i = 0;

  while(i < arr.length && !result) {
    result = Boolean(cb.call(_this, arr[i], i));
    i++;
  }

  return result;
}

var has_a1 = arr._some(function(item) {
  console.log('this: ', this); // { a: 'aaaa' }
  return item.a === 'a1'
}, { a: 'aaaa' });
console.log(has_a1); // true

var has_b = arr._some(item => item.b === 'b');
console.log(has_b); // false

var has_b1 = arr._some(item => item.b === 'b1');
console.log(has_b1); // true
複製代碼

5.7 Array.prototype.reduce

  • item循環序列號當前數組 做爲參數傳給回調函數;
  • 累計循環;兩個參數,第一個爲函數(其中,第一個形參爲第二個參數),第二個參數可不傳;
  • 回調函數的返回值做爲下次回調的第二個參數,最終返回回調函數的返回值

語法:

var result = arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
複製代碼

實現:

/** * Array.prototype.reduce * 將 `item`,`循環序列號`,`當前數組` 做爲參數傳給回調函數; * 累計循環;兩個參數,第一個爲函數(其中,第一個形參爲第二個參數),第二個參數可不傳; * 回調函數的返回值做爲下次回調的第二個參數,最終返回回調函數的返回值 */
Array.prototype._reduce = function() {
  var arr = this;
  var i = 0;
  var cb = arguments[0];  // 第一個參數,回調函數
  var cur = arguments[1] || null; // 第二個參數

  while(i < arr.length) {
    cur = cb(cur, arr[i], i, arr);
    i++;
  }

  return cur;
}

// 求和
var list = [1,2,3,4,5,6,7,8,9];
var result = list._reduce((acc, cur) => acc + cur, 0);
console.log(result); // 45

// 統計某個字符出現的次數
var list2 = ['aa', 'bb', 'jj', 'cc', 'dd', 'aa', 'b1'];
var result2 = list2._reduce((acc, cur) => {
  acc[cur] ? acc[cur]++ : acc[cur] = 1;
  return acc;
}, {});
console.log(result2); // { aa: 2, bb: 1, jj: 1, cc: 1, dd: 1 }
複製代碼

六、私有屬性/公有屬性

這個是在又一次面試的時候,面試官問的,挺簡單,雖然第一次遇到這麼問的

/** * 私有屬性,公有屬性 */
function fn() {
  // private
  var list = [];

  // public
  this.a = function() {
    console.log('a');
  }

  // public
  this.b = function() {
    console.log('b');
  }
}

const f = new fn();
console.log(f.list); // undefined
f.a(); // a
複製代碼

七、一些小功能

7.1 數組去重

  • 簡單數組按照 item 去重;
  • 複雜數組按照 item[key] 去重;
/** * 數組去重 * 簡單數組按照 `item` 去重; * 複雜數組按照 `item[key]` 去重; * @param {*} arr * @param {*} key 去重的 key,可選 */
function uniarr(arr, key) {
  var getType = o => Object.prototype.toString.call(o);
  if (getType(arr) !== '[object Array]') return arr;

  if (key && !arr[0].hasOwnProperty(key)) {
    console.warn(arr, '[item] 不存在key: '+key);
    return [];
  }

  var newArr = [];
  arr.forEach(item => {
    var arrItem = key 
      ? item.hasOwnProperty(key) ? item[key] : item 
      : item;
      
    var hasItem = newArr.some(newitem => {
      return (key ? newitem[key] === arrItem : newitem === arrItem)
    });

    if(!hasItem) newArr.push(item);
  })

  return newArr;
}

var list1 = [1,2,3,4,5,6,1,2,3];
console.log(uniarr(list1)); // [ 1, 2, 3, 4, 5, 6 ]

var list2 = [{id: 1}, {id: 2}, {id: 3}, {id: 2}];
console.log(uniarr(list2, 'id')); 
//[ { id: 1 }, { id: 2 }, { id: 3 } ]

console.log(uniarr(list2, 'id1'));
// [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 2 } ] '[item] 不存在 key: id1'
// []
複製代碼

7.2 數組扁平化

  • 判斷 item 是否數組,否的話直接 push 到新數組,
  • 是的話遞歸
/** * 數組扁平化 * 判斷 `item` 是否數組,否的話直接 push 到新數組, * 是的話遞歸 * @param {*} arr */
function singlearr(arr) {
  var getType = o => Object.prototype.toString.call(o);
  if (getType(arr) !== '[object Array]') return arr;

  var newArr = [];
  arr.forEach(item => {
    getType(item) === '[object Array]'
      ? newArr = newArr.concat(singlearr(item))
      : newArr.push(item)
  })

  return newArr;
}

var list2 = [1,2,[3,4],[5,[6,7]]];
console.log(singlearr(list2));
複製代碼

7.3 字符串先後去空格

正則去先後空格最簡單

/** * 字符串先後去空格 */
String.prototype._trim = function() {
  var str = this;
  return str.replace(/^\s|\s$/g,'');
}

var str = ' st r ';
console.log(str.split('')); // [ ' ', 's', 't', ' ', 'r', ' ' ]
console.log(str._trim().split('')); // [ 's', 't', ' ', 'r' ]
複製代碼

7.4 獲取 URL 參數

  • 默認返回 url 轉化的 key/value 對象,
  • 有傳 keyurl 轉化的對象有這個 key 的時候,直接返回值
/** * 獲取 URL 參數 * 默認返回 url 轉化的 key/value 對象, * 有傳 key 且 url 轉化的對象有這個 key 的時候,直接返回值 * @param {*} url 形如 a=1&b=2 * @param {*} key */
function urlUtil(url, key) {
  if (typeof url !== 'string') return;
  if (!url.includes('=')) return url;
  
  var obj = {};
  url.split('&').forEach(item => {
    const [key, value] = item.split('=');
    obj[key] = value;
  })

  if (key && obj.hasOwnProperty(key)) {
    return obj[key];
  }

  if (key && !obj.hasOwnProperty(key)) {
    console.warn(`url: ${url} 中不存在 ${key} 字段`);
    return;
  }

  return obj;
}

var url = 1;
console.log(urlUtil(url)); // a

var url1 = 'a=1';
console.log(urlUtil(url1)); // { a: '1' }
console.log(urlUtil(url1, 'a')); // 1
console.log(urlUtil(url1, 'b')); // url: a=1 中不存在 b 字段 undefined

var url2 = 'a=1&b=2';
console.log(urlUtil(url2)); // { a: '1', b: '2' }
console.log(urlUtil(url2, 'a')); // 1
console.log(urlUtil(url2, 'b')); // 2
複製代碼

7.5 數字/字符串分割

經常使用的如 數字千分號(3位)反向,銀行卡號(4位),身份證號(4位)等

數字千分號

/** * 數字千分號 * @param {*} num */
function numThousand(num) {
  if (typeof (num-0) !== 'number') return num;
  if (num.length < 4) return num;

  // 正則
  // var newNum = (num+'').replace(
  // /(\d)(?=(?:\d{3})+$)/g, 
  // '$1,'
  // );

  // 函數
  var newNum = (num+'').split('');
  var arr = [];
  do {
    // 從後面開始分割
    var start = newNum.length > 3 ? newNum.length - 3 : 0;
    arr.push(newNum.splice(start, newNum.length).join(''));
  } while(newNum.length > 0)

  newNum = arr.reverse().join(',');
  delete arr;

  return newNum;
}

var num1 = 1234567;
console.log(numThousand(num1)); // 1,234,567

var num2 = 123;
console.log(numThousand(num2)); // 123

var num3 = '123';
console.log(numThousand(num3)); // 123

var num4 = '12345';
console.log(numThousand(num4)); // 12,345

var card = '62564749929292';
// 62,564,749,929,292
console.log(numThousand(card)); 
複製代碼

字符按長度分割

/** * 字符按長度分割 * @param {*} num 數字 * @param {*} len 分割的長度,默認三位 * @param {*} sep 分隔符 默認 ',' */
function stringSeparate(str, {len = 3, sep = ','} = {}) {
  if (typeof (str+'') !== 'string') return str;
  if (str.length < 4) return str;

  var newStr = (str+'').split('');
  var arr = []
  do {
    arr.push(newStr.splice(0, len).join(''));
  } while(newStr.length > 0)

  newStr = arr.join(sep);
  delete arr;

  return newStr;
}

var num1 = 1234567;
console.log(stringSeparate(num1)); // 123,456,7

var num2 = 123;
console.log(stringSeparate(num2)); // 123

var num3 = '123';
console.log(stringSeparate(num3)); // 123

var num4 = '12345';
console.log(stringSeparate(num4)); // 123,45

var card = '6217123456789012345';
// 6217 1234 5678 9012 345
console.log(stringSeparate(card, {len: 4, sep: ' '})); 
複製代碼

八、排序算法

暫時就搞兩個~

8.1 冒泡排序

一個一個對比,互換位置

/** * 冒泡排序 * @param {*} arr */
function BubbleSort(arr){
  const getType = o => Object.prototype.toString.call(o);
  if (getType(arr) !== '[object Array]') return arr;

  for (var i=0; i<arr.length; i++) {
    for (var j=i+1; j<arr.length; j++) {
      var temp = '';
      if (arr[i] > arr[j]) {
        temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
      }
    }
  }

  return arr;
}

var arr = [1,3,4,12,34,5,22,8];
console.log(arr.sort()); // [ 1, 3, 4, 5, 8, 12, 22, 34 ]
console.log(BubbleSort(arr)); // [ 1, 3, 4, 5, 8, 12, 22, 34 ]
複製代碼

8.2 快速排序

  • 取一個參考值,而後將剩下的分爲兩份,一份大於參考值的 left: [],一份小於參考值 right: []
  • 而後分別遞歸 left/right, 返回一個 left+mid+right 組成的數組
/** * 快速排序 * 取一個參考值,而後將剩下的分爲兩份,一份大於參考值的 `left: []`,一份小於參考值 `right: []` * 而後分別遞歸 `left/right`, 返回一個 `left+mid+right` 組成的數組 * @param {*} arr 排序的數組 * @param {*} key 一級 key */
function FastSort(arr, key) {
  const getType = o => Object.prototype.toString.call(o);
  if (getType(arr) !== '[object Array]') return arr;
  if (arr.length <= 1) return arr;

  if (key && !arr[0].hasOwnProperty(key)) {
    console.warn(arr, '[item] 不存在key: '+key);
    return [];
  }
  if (!key && getType(arr[0]) === '[object Object]') {
    console.warn('傳一個 key 做爲排序字段');
    return [];
  }

  var mid = arr.shift();
  var left = [];
  var right = [];

  arr.forEach(item => {
    var arrItem = key 
      ? item.hasOwnProperty(key) ? item[key] : item
      : item;
    
    var midItem = key
      ? item.hasOwnProperty(key) ? mid[key] : mid
      : mid;

      arrItem <= midItem ? left.push(item) : right.push(item);
  });

  return FastSort(left, key).concat(mid).concat(FastSort(right, key));
}

var arr = [1,3,4,12,34,654,89,1,66,12,23,45,10,230,342,980];
// [ 1, 1, 3, 4, 10, 12, 12, 23, 34, 45, 66, 89, 230, 342, 654, 980 ]
console.log(FastSort(arr));

var arr1 = [{num: 10}, {num: 26}, {num: 8}, {num: 36}];
// 傳一個 key 做爲排序字段, []
console.log(FastSort(arr1));

// [ { num: 8 }, { num: 10 }, { num: 26 }, { num: 36 } ]
console.log(FastSort(arr1, 'num'));
複製代碼

九、發佈訂閱模式

能夠看 這裏

十、斐波那契數列

理解概念以後仍是很好寫的

/** * 求斐波那契數列 * n<=2時爲1,從3開始,每一個數等於前兩個之和 * @param {*} n */
function fb2(n) {
  const arr = [];
  for (var i = 0; i <= n; i++) {
    arr.push(
      i <= 2 ? 1 : arr[i - 1] + arr[i - 2]
    )
  }
  return arr;
}

console.log(fb2(15));
// [ 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610 ]
複製代碼

十一、動態規劃

11.1 硬幣問題

思路:

  1. 先求最大數的倍數
  2. 其中兩個的組合(大數優先)
  3. 三個的組合

/**
 * 動態規劃
 * 1塊,4塊,5塊,求總數N塊的最小硬筆數
 * 思路:一、先求最大數的倍數 二、其中兩個的組合(大數優先) 三、三個的組合
 */
function getCoinNum(N) {
  let n1 = 1;
  let n2 = 4;
  let n3 = 5;

  // 輸出結果組合、最少數量,如:12: { result: '5*2,4*0,2*1', minCount: 4 }
  const getResult = (result, minCount) => ({ result, minCount });

  if (N < n2) return getResult(`${n1}*${N}`, N / n1);
  if (N === n1) return getResult(`${n1}*1`, 1);
  if (N === n2) return getResult(`${n2}*1`, 1);
  if (N%n3 === 0) return getResult(`${n3}*${N/n3}`, N/n3);

  for (var j = 0; j < N/n2; j++) {
    for (var k = 0; k < N/n3; k++) {
      if (N === n3 * k + n2 * j) return getResult(`${n3}*${k},${n2}*${j}`, j + k);
      if (N === n2 * j) return getResult(`${n2}*${j}`, j);

      // N - n3*k - n2*j) 結果小於 4,則剩下的由 1 組成
      if ((N - n3 * k - n2 * j) > 0 && (N - n3 * k - n2 * j) < n2) {
        return getResult(
          `${n3}*${k},${n2}*${j},${(N - n3 * k - n2 * j)}*1`,
          j + k + (N - n3 * k - n2 * j)
        );
      }
    }
  }
}

// for (var i=0; i<=50; i++) console.log(`${i}: `, getCoinNum(i));
/**
 * 打印:
 * 0:  { result: '1*0', minCount: 0 }
1:  { result: '1*1', minCount: 1 }
2:  { result: '1*2', minCount: 2 }
3:  { result: '1*3', minCount: 3 }
4:  { result: '4*1', minCount: 1 }
5:  { result: '5*1', minCount: 1 }
6:  { result: '5*1,4*0,1*1', minCount: 2 }
7:  { result: '5*1,4*0,2*1', minCount: 3 }
8:  { result: '5*1,4*0,3*1', minCount: 4 }
9:  { result: '5*1,4*1', minCount: 2 }
10:  { result: '5*2', minCount: 2 }
11:  { result: '5*2,4*0,1*1', minCount: 3 }
12:  { result: '5*2,4*0,2*1', minCount: 4 }
13:  { result: '5*2,4*0,3*1', minCount: 5 }
14:  { result: '5*2,4*1', minCount: 3 }
15:  { result: '5*3', minCount: 3 }
16:  { result: '5*3,4*0,1*1', minCount: 4 }
17:  { result: '5*3,4*0,2*1', minCount: 5 }
18:  { result: '5*3,4*0,3*1', minCount: 6 }
19:  { result: '5*3,4*1', minCount: 4 }
20:  { result: '5*4', minCount: 4 }
21:  { result: '5*4,4*0,1*1', minCount: 5 }
22:  { result: '5*4,4*0,2*1', minCount: 6 }
23:  { result: '5*4,4*0,3*1', minCount: 7 }
24:  { result: '5*4,4*1', minCount: 5 }
25:  { result: '5*5', minCount: 5 }
26:  { result: '5*5,4*0,1*1', minCount: 6 }
27:  { result: '5*5,4*0,2*1', minCount: 7 }
28:  { result: '5*5,4*0,3*1', minCount: 8 }
29:  { result: '5*5,4*1', minCount: 6 }
30:  { result: '5*6', minCount: 6 }
31:  { result: '5*6,4*0,1*1', minCount: 7 }
32:  { result: '5*6,4*0,2*1', minCount: 8 }
33:  { result: '5*6,4*0,3*1', minCount: 9 }
34:  { result: '5*6,4*1', minCount: 7 }
35:  { result: '5*7', minCount: 7 }
36:  { result: '5*7,4*0,1*1', minCount: 8 }
37:  { result: '5*7,4*0,2*1', minCount: 9 }
38:  { result: '5*7,4*0,3*1', minCount: 10 }
39:  { result: '5*7,4*1', minCount: 8 }
40:  { result: '5*8', minCount: 8 }
41:  { result: '5*8,4*0,1*1', minCount: 9 }
42:  { result: '5*8,4*0,2*1', minCount: 10 }
43:  { result: '5*8,4*0,3*1', minCount: 11 }
44:  { result: '5*8,4*1', minCount: 9 }
45:  { result: '5*9', minCount: 9 }
46:  { result: '5*9,4*0,1*1', minCount: 10 }
47:  { result: '5*9,4*0,2*1', minCount: 11 }
48:  { result: '5*9,4*0,3*1', minCount: 12 }
49:  { result: '5*9,4*1', minCount: 10 }
50:  { result: '5*10', minCount: 10 }
 */
複製代碼

參考

相關文章
相關標籤/搜索