call,apply,bind,new實現原理
在實際開發過程中,對於函式封裝時,不確定外部是誰呼叫的,呼叫函式內部方法時,有可能是window
呼叫這時就會報錯,常使用call
,apply
,bind
來繫結this
指向。
Function.prototype.call()
call()
方法呼叫一個函式, 其具有一個指定的this值和分別地提供的引數。
該方法和apply()
類似,區別在於,call()
可以接收若干引數,而apply()
接收的是一個包含多個引數的陣列。
語法:fun.call(thisArg, arg1, arg2, ...)
call 可以繼承
通過父類的建構函式call
方法實現繼承
function Product(name, price) { this.name = name; this.price = price; } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } var cheese = new Food('feta', 5); console.log(cheese) // Food { name: 'feta', price: 5, category: 'food' } 複製程式碼
例項都會擁有在Product建構函式中新增的name屬性和price屬性,但category屬性是在各自的建構函式中定義的。
call 方法呼叫匿名函式
var animals = [ { species: 'Lion', name: 'King' }, { species: 'Whale', name: 'Fail' } ]; for (var i = 0; i < animals.length; i++) { (function(i) { console.log('#' + i + ' ' + this.species + ': ' + this.name) } ).call(animals[i], i); } 複製程式碼
for迴圈體內,我們建立了一個匿名函式,然後通過呼叫該函式的call方法,將每個陣列元素作為指定的this值執行了那個匿名函式。
call方法指定上下文的this
function greet() { var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' '); console.log(reply); } var obj = { animal: 'cats', sleepDuration: '12 and 16 hours' }; greet.call(obj); // cats typically sleep between 12 and 16 hours 複製程式碼
Function.prototype.apply()
apply()
呼叫一個指定this
值的函式, 接收作為一個數組或者類陣列物件提供的引數
語法:func.apply(thisArg, [argsArray])
apply 將陣列新增到另一個數組
var array = ['a', 'b']; var elements = [0, 1, 2]; array.push.apply(array, elements); console.log(array); // ["a", "b", 0, 1, 2] 複製程式碼
apply 找出最大值和最小值
var numbers = [5, 6, 2, 3, 7]; var max = Math.max.apply(null, numbers) var min = Math.min.apply(null, numbers); 複製程式碼
如果引數組非常大,將引數陣列切塊後,迴圈傳入目標方法:
function minOfArray(arr) { var min = Infinity; var QUANTUM = 32768; for (var i = 0, len = arr.length; i < len; i += QUANTUM) { var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len))); min = Math.min(submin, min); } return min; } var min = minOfArray([5, 6, 2, 3, 7]); console.log(min) // 2 複製程式碼
Function.prototype.bind()
bind()
方法建立一個新函式, 在呼叫時設定this關鍵字為提供的值。
並在呼叫新函式時,將給定引數列表作為原函式的引數序列的前若干項。
語法:function.bind(thisArg, [arg1[, arg2[, ...]]])
建立繫結函式
his.x = 9;// 在瀏覽器中,this指向全域性的 "window" 物件 var module = { x: 81, getX: function() { return this.x; } }; module.getX(); // 81 var retrieveX = module.getX; retrieveX(); // 返回9 - 因為函式是在全域性作用域中呼叫的 var boundGetX = retrieveX.bind(module);// 建立一個新函式,把 'this' 繫結到 module 物件 boundGetX(); // 81 複製程式碼
偏函式
function list() { return Array.prototype.slice.call(arguments); } function addArguments(arg1, arg2) { return arg1 + arg2 } var list1 = list(1, 2, 3); // [1, 2, 3] var result1 = addArguments(1, 2); // 3 // 建立一個函式,它擁有預設引數列表。 var leadingThirtysevenList = list.bind(null, 37); // 建立一個函式,它擁有預設的第一個引數 var addThirtySeven = addArguments.bind(null, 37); var list2 = leadingThirtysevenList(); // [37] var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3] var result2 = addThirtySeven(5); // 37 + 5 = 42 var result3 = addThirtySeven(5, 10); // 37 + 5 = 42 ,第二個引數被忽略 複製程式碼
Call原理
Function.prototype.myCall = function(context) { content = context ? Object(context) : window context.fn = this let args = [] for(let i =1; i<arguments.length;i++) { args.push('arguments['+i+']') } let r = eval('context.fn('+args+')') delete context.fn return r } 複製程式碼
當fn1.myCall(fn2)
時,綁定當前this
需要context.fn = this
等價於context.fn = fn1
呼叫的時候context.fn()
等價於fn2.fn()
此時this
是fn2
並執行fn1
。
當fn1.myCall.myCall(fn2)
是此時都是執行myCall
函式,this
為window
, 並執行fn2
函式。
apply原理
function fn1() { console.log(1, arguments) } Function.prototype.myApply = function(context, args) { context = context ? Object(context) : window context.fn = this if (!args) { return context.fn() } let r = eval('context.fn(' + args + ')') delete context.fn; return r } fn1.myApply('hello', [1, 2, 3, 4]) // 1 Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ] 複製程式碼
bind原理
let obj = { name: 'joker' } function fn() { console.log(this.name) } Function.prototype.bind = function(context) { } let bindFn = fn.bind(obj) bindFn() // joker 複製程式碼
從上面例子可以看出
this bind
Function.prototype.bind = function(context) { let _me = this return function() { return _me.apply(context) } } 複製程式碼
bind 還可以多次傳參 用法:
let obj = { name: 'joker' } function fn(name, age) { console.log(this.name + '今年' + name + age + '歲了') } let bindFn = fn.bind(obj, '大概') bindFn(10) // joker今年大概10歲了 複製程式碼
繫結this
的時候傳遞了一個值, 執行bindFn
又傳了一個引數,因此之前的函式需要改造
Function.prototype.bind = function(context) { let _me = this let bindArgs = [].slice.call(arguments, 1) // 獲取bind方法傳入的引數 return function() { let fnArgs = [].slice.call(arguments) // 獲取函式執行傳入的引數 return _me.apply(context, bindArgs.concat(fnArgs)) } } 複製程式碼
如果當前繫結的函式被new
了,當定函式中的this
是當前函式的例項,用法
let obj = { name: 'joker' } function fn(name, age) { console.log(this)//this是fn } let bindFn = fn.bind(obj) let instance = new bindFn() 複製程式碼
那麼這個方法還需要改造一下, 如果當前函式執行中的this
是fBound
的例項,說明是new
執行的,那麼當前this
就是函式的例項,否則是context
Function.prototype.bind = function(context) { let _me = this let bindArgs = [].slice.call(arguments, 1) function Fn() {} let fBound = function() { let fnArgs = [].slice.call(arguments) return _me.apply(this instanceof fBound ? this : context, bindArgs.concat(fnArgs)) } Fn.prototype = this.prototype fBound.prototype = new Fn(); return fBound } 複製程式碼