JavaScript 經常使用 API 實例講解

模擬 New 操做符

new 運算符建立一個用戶定義的對象類型的實例或具備構造函數的內置對象的實例javascript

  1. 建立一個空的簡單 JavaScript 對象(即 {});
  2. 鏈接該對象(即設置該對象的構造函數)到另外一個對象 ;
  3. 將步驟1新建立的對象做爲this的上下文 ;
  4. 若是該函數沒有返回對象,則返回this

用法實例講解

  1. 當構造函數沒有返回值時:
function Animal (name, age) {
  this.name = name;
  this.age = age;
  this.speak = '汪汪'
}
Animal.prototype.color = 'red';

Animal.prototype.say = function () {
  console.log('我會叫--' + this.speak);
}

var dog = new Animal('旺財', '3');

console.log(dog.name) // 旺財 
// 打印一個沒有的屬性
console.log(dog.eat) // undefined 
console.log(dog.age) // 3
console.log(dog.speak) // 汪汪
console.log(dog.color) // red
dog.say(); // 我會叫--汪汪
複製代碼
  1. 當構造函數有返回值,且返回值類型爲對象的(不包含 null )

有返回對象的,new 實例化後的對象就是返回的對象。拿不到內部其餘的屬性,也不會獲取到原型上的屬性和方法java

function Animal2 (name, age) {
  this.name = name;
  this.age = age;
  this.speak = '汪汪'
  return {
    name,
    eat: '吃骨頭',
  }
}
Animal2.prototype.color = 'red';

Animal2.prototype.say = function () {
  console.log('我會叫--' + this.speak);
}

var dog = new Animal2('旺財', '3');

console.log(dog.name) // 旺財
console.log(dog.eat) // 吃骨頭
console.log(dog.age) // undefined
console.log(dog.speak) // undefined
console.log(dog.color) // undefined
dog.say(); // 報錯: dog.say is not a function
複製代碼
  1. 構造函數返回 null,

這種狀況實際上和第一種狀況同樣(至關於構造函數沒有返回值)git

當時確實要注意的是,typeof null === 'object' 全部在實現的時候,咱們要進行處理。不能看到 typeof 返回的是object,那麼就返回該值。具體實例看下面的講解。es6

function Animal3 (name, age) {
  this.name = name;
  this.age = age;
  this.speak = '汪汪'
  return null
}
Animal3.prototype.color = 'red';

Animal3.prototype.say = function () {
  console.log('我會叫--' + this.speak);
}

var dog = new Animal3('旺財', '3');

console.log(dog.name) // 旺財
console.log(dog.age) // 3
console.log(dog.speak) // 汪汪
console.log(dog.color) // red
dog.say(); // 我會叫--汪汪
複製代碼

實現代碼

function fakeNew() {

    var obj = {};
	// 獲取第一個參數,而且會刪除第一個參數
    var Constructor = [].shift.call(arguments);
    
	// obj 繼承構造函數上的方法
    obj.__proto__ = Constructor.prototype;

    // Constructor 方法中的 this 指向 obj, 執行 Constructor 方法,至關於給 obj 繼承了Constructor 中的屬性,方法。 (能夠理解就是經過 apply 實現繼承)
    var result = Constructor.apply(obj, arguments);
    iftypeof result === 'object'){
        // result || obj 防止返回的是 null(由於 typeof null == 'object');
        return result || obj;
    } else {
        return obj;
    }
};
複製代碼

咱們來簡寫一下 New ,經過 ES6 寫很簡單 3 行代碼搞定。github

function fakeNew(Constructor, ...rest) {
	// Object.create 方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__。
    var obj = Object.create(Constructor.prototype),
    var result = Constructor.apply(obj, rest);
    return typeof result === 'object' ? result || obj : obj;

};
複製代碼

爲何會經過 Object.create 方法來實現讓對象的原型繼承 Constructor.prototype 屬性呢?數組

因爲現代 JavaScript 引擎優化屬性訪問所帶來的特性的關係,更改對象的 [[Prototype]]在***各個***瀏覽器和 JavaScript 引擎上都是一個很慢的操做。其在更改繼承的性能上的影響是微妙而又普遍的,這不單單限於 obj.__proto__ = ... 語句上的時間花費,並且可能會延伸到***任何***代碼,那些能夠訪問***任何***[[Prototype]]已被更改的對象的代碼。若是你關心性能,你應該避免設置一個對象的 [[Prototype]]。相反,你應該使用 Object.create()來建立帶有你想要的[[Prototype]]的新對象。瀏覽器

出自 MDN 原話app

也就是說 Object.setPrototypeOf(obj, Constructor.prototype)wordpress

var obj = {};obj.__proto__ = Constructor.prototype 兩種方法,不管在寫法仍是性能上都沒有 Object.create 方法來的更好函數

apply作了什麼?

上面模擬實現的 new 方法中 var result = Constructor.apply(obj, rest); 這行代碼作了什麼?

其實就是經過 apply 來實現屬性和方法的繼承。不清楚的同窗能夠看下 apply 的模擬實現過程。

apply 的模擬實現

Function.prototype.fakeApply = function (obj, arr) {
    var context = obj || window;
    context.fn = this;
    var result;
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }
    delete context.fn
    return result;
}
複製代碼

咱們用 ES6 寫法來簡寫一下 apply

Function.prototype.fakeApply = function(obj, arr = []) {
  var context = obj || window;
  context.fn = this;
  result = context.fn(...arr);
  delete context.fn;
  return result
};
複製代碼

同理 call 的模擬方法

// 經過...arr 接收後面全部的參數
Function.prototype.fakeCall = function(obj, ...arr) {
  var context = obj || window;
  context.fn = this;
  // 將接受的參數展開執行
  result = context.fn(...arr);
  delete context.fn;
  return result
}; 
複製代碼

在開發過程當中根據實際狀況選擇使用方式,那麼均可以用的話建議選擇 call 方法,由於 call 的性能高於 aplly。

由於 apply 第二個參數是數組,最終執行的時候仍是要將數組轉變成一個一個參數,來執行函數,所以性能比 apply 差 ( 也就是差一點點 )

高階函數

Array.prototype.map

Array.prototype.myMap = function(fn){
    const arr = this;
    const newArr = [];
    for(let i = 0; i<arr.length; i++){
      var temp = fn(arr[i], i, arr)
      newArr.push(temp);
    }
    return newArr;
  }
複製代碼

完整的 map 他是有第二個參數的

Array.map( callback [, thisArg ] )

callback

原數組中的元素調用該方法後返回一個新數組。它接收三個參數,分別爲 currentValue、index、array。

currentValue

​ callback的第一個參數,數組中當前被傳遞的元素。

index

​ callback的第二個參數,數組中當前被傳遞的元素的索引。

array

​ callback的第三個參數,調用map()方法的數組,即原數組。

thisArg

執行callback函數時this指向的對象

Array.prototype.map polyfill源碼實現:地址傳送門

// 實現 ECMA-262, Edition 5, 15.4.4.18
// 參考: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.map) {
  Array.prototype.map = function(callback, thisArg) {

    var T, A, k;

    if (this == null) {
      throw new TypeError(" this is null or not defined");
    }

    // 1. 將O賦值爲調用map方法的數組.
    var O = Object(this);

    // 2.將len賦值爲數組O的長度.
    var len = O.length >>> 0;

    // 3.若是callback不是函數,則拋出TypeError異常.
    if (Object.prototype.toString.call(callback) != "[object Function]") {
      throw new TypeError(callback + " is not a function");
    }

    // 4. 若是參數thisArg有值,則將T賦值爲thisArg;不然T爲undefined.
    if (thisArg) {
      T = thisArg;
    }

    // 5. 建立新數組A,長度爲原數組O長度len
    A = new Array(len);

    // 6. 將k賦值爲0
    k = 0;

    // 7. 當 k < len 時,執行循環.
    while(k < len) {

      var kValue, mappedValue;

      //遍歷O,k爲原數組索引
      if (k in O) {

        //kValue爲索引k對應的值.
        kValue = O[ k ];

        // 執行callback,this指向T,參數有三個.分別是kValue:值,k:索引,O:原數組.
        mappedValue = callback.call(T, kValue, k, O);

        // 返回值添加到新數組A中.
        A[ k ] = mappedValue;
      }
      // k自增1
      k++;
    }

    // 8. 返回新數組A
    return A;
  };      
}
複製代碼

Array.prototype.reduce

簡易實現

Array.prototype.myReduce = function(callback, initialValue ) {
     var previous = initialValue, k = 0, length = this.length;
    // 若是沒有傳入 initialValue 就默認將數組的第 0 個賦給 previous
     if (typeof initialValue === "undefined") {
        previous = this[0];
        k = 1;
     }
    if (typeof callback === "function") {
      for (k; k < length; k++) {
        this.hasOwnProperty(k) && (previous = callback(previous, this[k], k, this));
      }
    } 
    return previous;
  };
複製代碼

Array.prototype.forEach

簡易實現

Array.prototype.MyForEach = function(fn) {
    const arr = this
    for(let i = 0; i<arr.length; i++){
        fn(arr[i],i,arr)
    }
  }
複製代碼

深拷貝

const deepCopy = function(obj){
    return Object.keys(obj).reduce(function (copy, item){
      // 若是對象的 value 類型爲 object 就再次執行 deepCopy 來實現深拷貝
      copy[item] = typeof obj[item] === 'object' ? deepCopy(obj[item]) : obj[item];
      return copy;
      // 判斷 obj 是 數組類型 仍是對象類型 這樣 copy 就是 [] 或者是 {}
    }, Object.prototype.toString.call(obj) === '[object Array]' ? [] : {})
  }
複製代碼

使用箭頭函數和 ,逗號運算符,簡化

const deepCopy = obj => Object.keys(obj).reduce(
  (copy, item) => (copy[item] = typeof obj[item] === 'object' ? deepCopy(obj[item]) : obj[item], copy)
  ,Object.prototype.toString.call(obj) === '[object Array]' ? [] : {}
);
複製代碼

javaScript 循環

for 循環

for(var i=1;i<=3;i++)
{
&emsp;&emsp;for(var i=1;i<3;i++)
&emsp;&emsp;{
&emsp;&emsp;&emsp;&emsp;console.log(i)
&emsp;&emsp;}
}
// 1 2

for(let i=1;i<=3;i++)
{
&emsp;&emsp;for(let i=1;i<3;i++)
&emsp;&emsp;{
&emsp;&emsp;&emsp;&emsp;console.log(i)
&emsp;&emsp;}
}
// 1 2 1 2 1 2
複製代碼

do-while

先執行一次再去作判斷

var a = 10
do{
  console.log(a--)
} while (a>2)
複製代碼

while

先判斷, 再執行

while(i<=10)
{
&emsp;&emsp;循環體
 &emsp;&emsp;i++
}
複製代碼

for in

實例

var obj = {
  a: 1,
  b: [],
  c: function () {}
};
for (var key in obj) {
   console.log(key);
}
// 結果是:
// a
// b
// c
複製代碼

注意:

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

var arr = [3, 5, 7];
arr.foo = 'hello';

for (var i in arr) {
   console.log(i);
}
// 結果是:
// 0
// 1
// 2
// foo
// arrCustom
// objCustom 
複製代碼

自定義的 Array.prototype 上的方法也會遍歷出來,經過原型鏈最後還能遍歷到 arr.proto.proto Object.prototype 上的objCustom

解決方案:

hasOwnProperty() 方法會返回一個布爾值,指示對象自身屬性中是否具備指定的屬性

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

var arr = [3, 5, 7];
arr.foo = 'hello';

for (var i in arr) {
   if (arr.hasOwnProperty(i)) {
    console.log(i);
  }
}
// 結果是:
// 0
// 1
// 2
// foo
複製代碼

數組上面自己的屬性仍是會遍歷出來

若是不想要自己的屬性能夠經過 forEach 來實現

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

var arr = [3, 5, 7];
arr.foo = 'hello';

arr.forEach((item, index)=>{
  console.log(item, index)
})
// 結果是:
// 3 0
// 5 1
// 7 2
複製代碼

或者經過 for of 來實現

for of

for of 須要遍歷可迭代(iterable)的類型

var obj = {
  a: 1,
  b: [],
  c: function () {}
};
for (var key of obj) {
   console.log(key);
}
// 出錯:
// Uncaught TypeError: obj is not iterable
複製代碼

ES6 標準不認爲 Object 對象是可迭代的,因此不能用 for-of 遍歷之

可迭代的類型有 Array、 Map、Set、arguments 類對象、NodeList 這類 DOM 集合、String**、**generators

最後

全文章,若有錯誤或不嚴謹的地方,請務必給予指正,謝謝!

參考

相關文章
相關標籤/搜索