一、高級函數
1-1 安全的類型檢測
想到類型檢測,那么腦海里第一反應應該就是在 Javascript 的世界中到底有哪些類型(這真的是一個非常古老的問題了)
我們大致分為 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 的原理了。
1-1-1 instanceof 的原理
instanceof 的的功能實現是 前者是否為后者的實例 , 具體的代碼就是:
eg:
let res = a instanceof A
// a 是 A 的實例
// A 是 a 的構造函數
// 同時 a 的 __proto__ == A 的 prototype 那么 a instanceof A == true 否則就等於 false
其中 有幾個關鍵的點 如下:
-
關於 constrcutor 、proto 、prototype、原型對象 這四個點的理解。
-
推薦一篇好文章吧 prototype、proto、constructor 的三角關系
-
回到上面 a.proto 指向的就是 a 的原型對象
-
A.prototype 指向的是 實例對象 的 原型對象
var Foo = function() {
this.setName = (name) => {
this.name = name
}
}
var foo = new Foo
Foo.prototype 指向 => 原型對象(理解為公共對象)
// 通過同一個構造函數實例化的多個對象具有相同的原型對象。經常使用原型對象來實現繼承
Foo.prototype.constructor 指向 => 構造函數本身(Foo)
foo.__proto__ 指向 => 原型對象(理解為公共對象)
foo.constructor 指向 => 構造函數 (Foo)
1-2 作用域安全的構造函數
在全局作用域內調用函數構造函數,由於沒有使用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
1-3 惰性載入函數
惰性載入表示函數執行的分支會在函數第一次調用的時候執行,在第一次調用過程中,該函數會被覆蓋為另一個按照合適方式執行的函數,這樣任何對原函數的調用就不用再經過執行的分支去進行判斷了。(節約算力)
1-3-1 應用場景
1、 AJAX 在不同瀏覽器下兼容性
2、 APP 內嵌 H5 不同環境下同一種功能方法,寫法不一樣
3、 H5 在不同平台下多處表現形式因為一個方法而展現的不一樣。
1-3-2 注意的地方
1、應用越頻繁,越能體現這種模式的優勢所在
2、固定不變,一次判定,在固定的應用環境中不會改變
3、復雜的分支判斷,沒有差異性,不需要應用這種模式
1-3-3 Demo
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"}
// 實際有效的 惰性載入函數 上面的 二個 方法返回的值 其實是一樣的。這樣惰性載入函數 才是真實有效。
1-4 函數綁定
這個技巧常常和回調函數與事件處理一起使用,以便在將函數作為變量傳遞的同時保留代碼執行環境
很多JavaScript庫實現了一個可以將函數綁定到指定環境的函數,這個函數一般都叫做bind()。一個簡單的bind()函數接受一個函數和一個環境,並返回一個給的環境中調用給定函數的函數,並且將所有參數原封不動傳遞過去。這個函數返回的是一個閉包。
上面的語言描述總是很虛無飄渺,我們來直接上Demo:
1-4-1 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 具體是什么。
1-4-2 arguments
類數組 (Array-like)
- 可以用下標訪問每個元素
- 有 length 屬性
- arguments 的數據類型為 object
- 可以使用 for 和 for-in 方法
- 不具備 Array 原生方法
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]
1-4-3 ES5 中原生 bind() 方法 詳解
文字解釋起來還是比較吃力,那么我們還是 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
1-5 函數柯里化
又稱部分求值。柯里化其實本身是固定一個可以預期的參數,並返回一個特定的函數,處理批特定的需求。
這增加了函數的適用性,但同時也降低了函數的適用范圍。
文字的定義始終讓人難以接受,還是 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
小結二:
這個例子中,通過顆粒化 創建已經設置好了一個或多個參數的函數。
后續還會有更多的例子,來證明這個點。
注意
無論是柯里化函數或是綁定函數,都會帶來額外的開銷,所以不應濫用。
1-6 反函數柯里化
相反,反柯里化的作用在與擴大函數的適用性,使本來作為特定對象所擁有的功能的函數可以被任意對象所用.
核心:
通過 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 、歡迎推薦 : )