手寫JavaScript常用的函式

DBCdouble發表於2019-04-15

一、bind、call、apply函式的實現

改變函式的執行上下文中的this指向,但不執行該函式(位於Function建構函式的原型物件上的方法)

Function.prototype.myBind = function (target) {
    if (typeof this !== 'function') {
        throw Error('myBind is not a function')
    }
    var that = this
    var args1 = [...arguments].slice(1)
    var func = function () {
        var args2 = [..arguments].slice(1)
        return that.apply(target || window, args1.concat(args2))    }
    return func
}

Function.prototype.myCall = function (context=window) {
    if (typeof this !== 'function') {
        throw Error('myBind is not a function')    
    }     
    context.fn = this
    var args = [...arguments].slice(1)
    var result = context.fn(..args)    
    delete context.fn
    return result                                                                                                                                                                                                                                                                                                                                                                                                         
}

Function.prototype.myApply = function (context=window) {
    if (typeof this !== 'function') {
        throw Error('myApply is not a function')
    }
    context.fn = this
    var result
    if (argument[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }
    delete context.fn
    return result
}複製程式碼

二、引用資料型別的深拷貝方法的實現

function cloneDeep (target) {
    function checkType(target) {
        return Object.prototype.toString.call(target).slice(8, -1)
    }
    var result, checkedType = checkType(target)
    if (checkedType === 'Array') {
        result = []
    } else if (checkedType === 'Object') {
        result = {}
    } else {
        return target
    }
    //遞迴遍歷物件或陣列中的屬性值或元素為原始值為止
    for (var key in target) {
        if ( checkType(target[key]) === 'Array' || checkType(target[key]) === 'Object') {
            result[key] = cloneDeep(target[key])
        } else {
            result[key] = target[key]
        }
    }
    return result
}複製程式碼

思路:

  • 輸入需要深拷貝的目標target輸出深拷貝後的結果
  • 通過Object.prototype.toString準確判斷傳入的目標target的資料型別,當target的資料型別為物件或者陣列時,會對target進行遞迴遍歷直至當遍歷的陣列或者物件中的資料全部為基本資料型別為止

三、陣列flat函式的實現

Array.prototype.flat複製程式碼

四、實現n的階乘

分析:首先找規律,舉例如3的階乘等於3*2*1,也就是等於n*n-1*n-2的階乘,也就是等於3*2*1的階乘,計算到1的階乘之後,整個計算過程才結束。分析到很容易想到通過遞迴來實現這個數的階乘,因為第一,這個計算過程有規律可循,第二它有最終停止計算的出口,也就是當計算到1的時候就停止運算,以下通過遞迴來實現

function factorial (num) {
    if (num < 0) {
        throw new Error('負數沒有階乘')
    }
    if (num === 1 || num === 0) {
        return 1
    }
    return num * factorial(num-1)
}

factorial(3)  //6複製程式碼

五、實現斐波拉契數列

分析:按照上述階乘的分析過程分析,這裡不贅述

function fibonacci (n) {
  //此方法應使用尾遞迴法進行優化,這裡不作優化,簡單實現
  if ( n <= 1 ) {return 1};
  return fibonacci(n - 1) + fibonacci(n - 2);}複製程式碼

六、實現一個計算字串位元組長度的函式

分析:首先我們要知道英文的位元組長度是1,而中文的位元組長度是2,但是如何判斷當前字元位是漢字還是英文呢,通過charCodeAt來判斷當前字元位的unicode編碼是否大於255,如何大於255則是漢字,那就給字串的位元組長度加2,如果小於255則是英文,就給字串的位元組長度加1,以下按照這個思路實現

function countBytesLength(str){
    var length = 0
    //首先遍歷傳入的字串
    for(var i = 0; i < str.length; i++) {
        if (str[i].charCodeAt(i) > 255) {
            length += 2
        } else {
            length++
        }
    }
     return length
}

var str = 'DBCDouble陳'
countBytesLength(str) //11複製程式碼

七、實現isNaN函式

分析:要判斷傳入的值是否是"is not a number"(isNaN全拼),首先進行一個數字的隱式型別轉換,通過Number包裝類來實現Number(x),再判斷Numberz(x)的返回值是否是NaN,如果是的話再與NaN進行比對,但是由於NaN雖然是number型別的,但是是不能進行比較的,所以我們先將Number(x)返回的結果變成字串形式,再去判斷,實現如下

function isNaN(num) {
    var ret = Number(num)
    ret += ''
    if (ret === 'NaN') {
        return true
    }
    return false
} 
isNaN('123abc') // true複製程式碼

八、實現陣列的push函式

分析:首先push函式是位於Array建構函式的原型物件上的方法,所以要在Array.prototype上去定義,然後再分析push函式的作用是往陣列的末尾新增元素,可以新增任意個數的元素,並且最終返回陣列的長度,實現程式碼如下

Array.prototype.push = function () {
    for (var i = 0; i< arguments.length; i++) {
        this[this.length] = arguments[i]
    }
    return this.length
}複製程式碼

七、實現能夠識別所有資料型別的typeof

分析:首先typeof是位於window物件上的全域性方法,所以我們定義完成之後要將其掛載到window上,其次要實現識別所有資料型別包括:基本資料型別和複雜資料型別(引用資料型別),我們需要通過Object.prototype.toString方法去做才唯一能夠最準確判斷當前值為什麼資料型別,實現程式碼如下

window.typeof = function (value) {
  return Object.prototype.toString.call(val).slice(8, -1)
}複製程式碼

八、實現陣列的去重方法

分析:首先因為是給所有陣列例項實現一個去重方法,所以同樣是在原型鏈上進行程式設計

Array.prototype.unique = function () {
    //這裡是利用物件鍵hash值的唯一性來去重
    var obj = {}
    var result = []
    for (var i = 0; i < this.length; i++) {
        if (!obj[this[i]]) {
            obj[this[i]] = true
            result.push(this[i])
        }
    }
    return result
}

var arr = [1,2,2,3,3]
arr.unique() //[1,2,3]複製程式碼

Array.prototype.unique = function () {
    //利用ES6的Array.prototype.includes
    var result = []
    for (var i = 0; i < this.length; i++) {
        if (!result.includes(this[i])) {
            result.push(this[i])
        }
    }
    return result
}複製程式碼

Array.prototype.unique = function () {
    //利用ES6的Set
    var result = new Set(this)   //生成一個類陣列
    return Array.from(result)    //通過Array.from將類陣列轉換成真正的陣列
}複製程式碼

Array.prototype.unique = function () {
    //利用Array.prototype.filter返回符合條件的元素
    //利用Array.prototype.indexOf返回陣列中第一次出現當前元素的索引值
    //該方法寫法最為優雅,一行程式碼搞定,函數語言程式設計
    return this.filter((item, index) => this.indexOf(item) === index)
}複製程式碼

九、實現函式的防抖、節流

function debounce (fn, wait=300) {
    var timer
    return function () {
        if (timer) {
            clearTimeOut(timer)
        }
        timer = setTimeout({
            fn.apply(this, arguments) 
        }, wait)
    }
}

function throttle (fn, wait=300) {
    var prev = +new Date()
    return function () {
       var now = +new Date()
       if (prev - now > 300) {
          fn.apply(this, arguments)
          prev = now
       }
    }
}複製程式碼

十、封裝ajax

function ajax (options) {
    options = options || {}
    options.url = options.url || ''
    options.method = options.method.toUpperCase() || 'GET'
    options.async = options.async || true
    options.data = options.data || null
    options.success = options.success || function () {}
    var xhr = null 
    if (XMLHttpRequest) {
        xhr = new XMLHttpRequest()
    } else {
        xhr = new ActiveXObject('Microsoft.XMLHTTP')
    }
    xhr.open(options.url, options.method, options.async)
    var postData = []
    for (var key in options.data) {
        postData.push(key + '='+ options.data[key])
    }
    if (options.method === 'POST') {
        xhr.open(options.method, options.url, options.async )
        xhr.send(postData)
    } else if (options.method === 'GET') {
        xhr.open(options.mehtod, options.url + postData.join('&'), options.async)
        xhr.send(null)
    }
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            options.success(xhr.responseText)
        }
    }
}複製程式碼

十一、實現new操作符

//接受一個函式
//最後返回一個物件
function new (fn) {
    return function () {
      var obj = { 
         '__proto__': fn.prototype
      }
      fn.apply(obj, arguments)      
      return obj
    }
}複製程式碼

十二、常用六種繼承方式

1、原型鏈繼承:子型別的原型物件為父型別的例項物件

function Person (name, age) {
    this.name = name 
    this.age = age
}

Person.prototype.setName = function () {
    console.log(this.name)
}

function Student (height) {
    this.height = height
}

Student.prototype = new Person()
var stu = new Student('175')
console.log(stu)複製程式碼

2、借用建構函式實現繼承:在子類的建構函式中通過call呼叫父類的建構函式實現繼承

function Person (name, age) {
    this.name = name 
    this.age = age
}

Person.prototype.setName = function () {
    console.log(this.name)
}

function Student (height, age, name) {
    Person.call(this, age, name)
    this.height = height
}

var stu = new Studeng(175, 'cs', 24)
console.log(stu)複製程式碼

3、原型鏈+借用建構函式的組合繼承方式:通過在子類建構函式中通過call呼叫父類建構函式,繼承父類的屬性並保留傳參的優點,再通過將父類的例項作為子類的原型物件,實現繼承

function Person (name, age) {
    this.name = name 
    this.age = age
}

Person複製程式碼


相關文章