目錄javascript
想到類型檢測,那麼腦海裏第一反應應該就是在 Javascript 的世界中到底有哪些類型(這真的是一個很是古老的問題了)html
咱們大體分爲 2 類: 基本類型 和 引用類型 其中 基本類型 包括了: string、number、bool、undefined、null 其中 引用類型 包括了: Array、Function、Object
那咱們用 type 和 instanceof 分別來看下這幾種數據類型斷定返回的內容
爲何說 利用 type 和 instanceof 是不安全的類型檢測前端
const str = 'test' const num = 12 const bool = false const unde = undefined const nulls = null const Array = [1,2,3,4] const Object = {name: 'zzz'} const checkType = (type) => { return typeof(type) } // 使用 type 來判斷 console.log(checkType(str)) // string console.log(checkType(num)) // number console.log(checkType(bool)) // boolean console.log(checkType(unde)) // undefined console.log(checkType(nulls)) // object console.log(checkType(Array)) // object console.log(checkType(Object)) // object // 很顯然 null、Array、Object 返回的都是 object 不夠安全 ( bug 點 ) // 用 instanceof 來判斷 const checkInstance = (type) => { return type instanceof String } console.log(checkInstance(str)) // 是 false 這是爲何呢? // 那麼咱們就須要來介紹下 instanceof 的原理了。
instanceof 的的功能實現是 前者是否爲後者的實例 , 具體的代碼就是:
eg:java
let res = a instanceof A // a 是 A 的實例 // A 是 a 的構造函數 // 同時 a 的 __proto__ == A 的 prototype 那麼 a instanceof A == true 不然就等於 false
其中 有幾個關鍵的點 以下:git
推薦一篇好文章吧 prototype、proto、constructor 的三角關係github
A.prototype 指向的是 實例對象 的 原型對象chrome
var Foo = function() { this.setName = (name) => { this.name = name } } var foo = new Foo Foo.prototype 指向 => 原型對象(理解爲公共對象) // 經過同一個構造函數實例化的多個對象具備相同的原型對象。常用原型對象來實現繼承 Foo.prototype.constructor 指向 => 構造函數自己(Foo) foo.__proto__ 指向 => 原型對象(理解爲公共對象) foo.constructor 指向 => 構造函數 (Foo)
在全局做用域內調用函數構造函數,因爲沒有使用new,致使在全局做用域添加冗餘的屬性數組
function Person(name,job) { this.name = name this.job = job } // 假如爲使用 New 操做 var person = Person('zhangsan','sell') console.log(window.name, window.job) // zhangsan sell console.log(person.name, person.job) // VM76:11 Uncaught TypeErrorr
這個問題是由this對象的晚綁定形成的
所以,須要在函數裏面確認this對象是正確類型的實例
:瀏覽器
function Person(name){ if(this instanceof Person){ this.name = 'zhang'; } else { return new Person(name) } } var person = Person('zhang') console.log(window.name) // '' console.log(person.name) // zhang
惰性載入表示函數執行的分支會在函數第一次調用的時候執行,在第一次調用過程當中,該函數會被覆蓋爲另外一個按照合適方式執行的函數,這樣任何對原函數的調用就不用再通過執行的分支去進行判斷了。(節約算力)安全
一、 AJAX 在不一樣瀏覽器下兼容性
二、 APP 內嵌 H5 不一樣環境下同一種功能方法,寫法不同
三、 H5 在不一樣平臺下多處表現形式由於一個方法而展示的不同。
一、應用越頻繁,越能體現這種模式的優點所在
二、固定不變,一次斷定,在固定的應用環境中不會改變
三、複雜的分支判斷,沒有差別性,不須要應用這種模式
const getCurEnv = () => { // 當前環境爲 chrome 瀏覽器環境 return window.navigator.userAgent.toLowerCase().match(/chrome/i) !== null } const Person = function(name) { this.name = name } const http = { created: function() { if (getCurEnv()) { console.log(this) this.created = function() { console.log('test1') return new Person('zhang1') } console.log('test2') return new Person('zhang2') } else { this.created = function() { console.log('test3') return new Person('zhang3') } } }, Atest: ()=> { console.log(this) // window {} }, Ftest: function() { console.log(this) // http {} } } http.created() // test2 Person {name: "zhang2"} http.created() // test1 Person {name: "zhang1"} // 實際有效的 惰性載入函數 上面的 二個 方法返回的值 實際上是同樣的。這樣惰性載入函數 纔是真實有效。
這個技巧經常和回調函數與事件處理一塊兒使用,以便在將函數做爲變量傳遞的同時保留代碼執行環境
不少JavaScript庫實現了一個能夠將函數綁定到指定環境的函數,這個函數通常都叫作bind()。一個簡單的bind()函數接受一個函數和一個環境,並返回一個給的環境中調用給定函數的函數,而且將全部參數原封不動傳遞過去。這個函數返回的是一個閉包。
上面的語言描述老是很虛無飄渺,咱們來直接上Demo:
var obj1 = { name: 'zhang', getName: function() { console.log(arguments[0][2], 'obj1') return this.name } } var obj2 = { name: 'lisi', getName: function() { console.log(arguments, 'obj2') return this.name } } function Bind(fn, context) { return fn.call(context, arguments) } Bind(obj1.getName,obj2,'xxxxx') // Arguments [Arguments(3), callee: ƒ, Symbol(Symbol.iterator): ƒ] "obj1" // 'lisi' // 這裏咱們對於 arguments 的 理解和操做來講都是比較陌生,那麼下面 咱們再來介紹下 // arguments 具體是什麼。
類數組 (Array-like)
Demo
var test = function() { console.log(arguments) console.log(arguments[0]) console.log(arguments.length) console.log(typeof arguments) for(var i = 0; i<arguments.length; i++) { var ele = arguments[i] console.log(ele) } for(x in arguments) { console.log(arguments[x]) } // arguments.split(' ') // Uncaught TypeError: arguments.split is not a function } test(1,2,3,4) // Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ] // 1 // 4 // object // 1 2 3 4 // 1 2 3 4
將類數組 轉化爲 數組
var test = function() { console.log(arguments) var arrArg = Array.prototype.slice.call(arguments) console.log(arrArg) } test(1,2,3,4) // [1, 2, 3, 4]
var test = function() { console.log(arguments) var arrArg = Array.from(arguments) console.log(arrArg) } test(1,2,3,4) // [1, 2, 3, 4]
文字解釋起來仍是比較吃力,那麼咱們仍是 showCode~
Demo:
var obj = { a: 1, b: 2, getCount: function(c, d) { return this.a + this.b + c + d } } console.log(obj.getCount(3,4)) // 10 window.a = window.b = 0 var funcs = obj.getCount funcs(3,4) // 7
bind是function的一個函數擴展方法, bind 之後代碼從新綁定了 func 內部的 this 指向(obj)
兼容 IE9 +
Demo:
var obj = { a: 1, b: 2, getCount: function(c, d) { return this.a + this.b + c + d } } console.log(obj.getCount(3,4)) // 10 window.a = window.b = 100 var funcs = obj.getCount.bind(obj) funcs(3,4) // 10 // var funcs = obj.getCount.bind(window) // funcs(3,4) // 207
又稱部分求值。柯里化其實自己是固定一個能夠預期的參數,並返回一個特定的函數,處理批特定的需求。
這增長了函數的適用性,但同時也下降了函數的適用範圍。
文字的定義始終讓人難以接受,仍是 showCode 吧
Demo:假設你要寫一個 記帳的工具,而後記錄天天的數據,最後統計整個星期的數據。
how ?
let weekCost = 0 const cost = function(num) { weekCost += num } cost(100) // 100 cost(200) // 300 cost(300) // 600
這個時候天天都會進行一次 總帳,這個是我不想看到的,由於不想天天都被這個總帳看着心痛,畢竟工資不夠花是常態。我就但願每一個星期給我來一次總帳刺激。
const currying = function(fn) { let args = [] return function() { if (arguments.length == 0) { return fn.apply(this, args) } else { let test = [].push.apply(args,arguments) // return fn.call(this, arguments) } } } const costs = (function() { let money = 0 return function() { money = 0 for(let i = 0; i<arguments.length; i++) { money += arguments[i] } return money } })() let cost = currying(costs) cost(100) cost(100) cost(100) cost(100) cost(100) console.log(cost()) // 500 cost(100) cost(100) console.log(cost()) // 700
小結一:
上面的 dmeo 中,當調用 cost() 時,若是明確帶上參數,代表此時並不進行真正的求值計算,而是把這些參數保存起來,此時讓 cost() 函數返回另一個函數。只有當咱們以不帶參數的形式執行 cost() 時,才利用前面保存的全部參數,真正開始求值計算。這是一個具象的函數顆粒化的方法。那麼咱們想把函數顆粒化抽象出來又須要怎麼來歸納吶? 🤔 下面的例子,咱們再來看下這個顆粒化!
Demo
const currying = function(fn) { let args = Array.prototype.slice.call(arguments, 1) return function() { let innerArgs = Array.prototype.slice.call(arguments) return fn.apply(this, args.concat(innerArgs)) } } const add = function(n, m) { return n + m } var curriedAdd = currying(add, 3) console.log(curriedAdd(5)) // 8
小結二:
這個例子中,經過顆粒化 建立已經設置好了一個或多個參數的函數。 後續還會有更多的例子,來證實這個點。
注意
不管是柯里化函數或是綁定函數,都會帶來額外的開銷,因此不該濫用。
相反,反柯里化的做用在與擴大函數的適用性,使原本做爲特定對象所擁有的功能的函數能夠被任意對象所用.
經過 uncurrying 函數,講本來只能用於某個對象的方法,擴展到更多的 對象能夠引用。
showCode:
Function.prototype.uncurrying = function() { var that = this; return function() { return Function.prototype.call.apply(that, arguments); } } const test1 = {} const test2 = {} test1.sayHi = function () { return "Hello " + this.value +" "+[].slice.call(arguments); } test2.sayHiuncurrying = test1.sayHi.uncurrying() console.log(test2.sayHiuncurrying({value:'world'},"hahaha")); // Hello world hahaha
核心的代碼已經展現出來了, 仔細的品讀品讀~
好了,今天就先寫到這裏,後面會繼續完善對於 Demo 的解釋,不明白的能夠留言討論~
GitHub 地址:(歡迎 star 、歡迎推薦 : )