前端知識填坑記(二):call和apply,bind ,new
call和apply,bind 的模擬實現
call
call()
方法在使用一個指定的 this
值和若干個指定的引數值的前提下呼叫某個函式或方法。
const foo = {
value: 1
}
function bar() {
console.log(this.value)
}
bar.call(foo) // 1
- call 改變了 this 的指向,指向到 foo
- bar 函式執行了
const foo = {
value: 1,
bar: function() {
console.log(this.value)
}
}
我們模擬的步驟可以分為:
- 將函式設為物件的屬性
- 執行該函式
- 刪除該函式
// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn
第一版:改變作用域
Function.prototype.call = function (con) {
const context = con || window
// 首先要呼叫獲取 call 的函式,可以用 this 獲取
context.fn = this
context.fn()
delete context.fn
}
const foo = {
value: 1
}
function bar() {
console.log(this.value)
}
bar.call(foo)
第二版:實現傳參
Function.prototype.call1 = function (con) {
// 首先要呼叫獲取 call 的函式,可以用 this 獲取
const context = con || window
let args = []
for (let i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
context.fn = this
const result = eval('context.fn(' + args + ')')
delete context.fn
return result
}
const foo = {value: 1}
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value)
}
bar.call1(foo, 'kevin', 18)
apply
apply()
方法在使用一個指定的 this
值和指定的引數陣列的前提下呼叫某個函式或方法。
Function.prototype.apply = function(con, arr) {
const context = con || window
context.fn = this
let result
if(!arr) {
result = context.fn()
} else {
const args = []
for(let i = 0,len = arr.length; i < len; i++) {
args.push(arr[ i ])
}
result = eval('context.fn('+ args +')')
}
delete context.fn
return result
}
bind
bind()
方法會建立一個新函式。當這個新函式被呼叫時,bind()
的第一個引數將作為它執行時的this
,之後的一序列引數將會在傳遞的實參前傳入作為它的引數。
const foo = {
value: 1
}
function bar(name, age) {
console.log(this.value)
console.log(name)
console.log(age)
}
let bindFoo = bar.bind(foo, 'daisy')
bindFoo('18')
函式需要傳name
和age
兩個引數,竟然還可以在bind
的時候,只傳一個name
,在執行返回的函式的時候,再傳另一個引數 age
!
Function.prototype.bind1 = function (con) {
const self = this
// 獲取bind1函式從第二個引數到最後一個引數
let args = Array.prototype.slice.call(arguments, 1)
return function () {
// 這個時候的arguments是指bind返回的函式傳入的引數
const bindArgs = Array.prototype.slice.call(arguments)
s1elf.apply(context, args.concat(bindArgs))
}
}
建構函式效果的模擬實現
一個繫結函式也能使用new
操作符建立物件:這種行為就像把原函式當成構造器。提供的this
值被忽略,同時呼叫時的引數被提供給模擬函式。
當
bind
返回的函式作為建構函式的時候,bind
時指定的this
值會失效,但傳入的引數依然生效。
const value = 2
const foo = {
value: 1
}
function bar(name, age) {
this.habit = 'shopping'
console.log(this.value);
console.log(name)
console.log(age)
}
bar.prototype.friend = 'kevin'
const bindFoo = bar.bind(foo, 'daisy')
const obj = new bindFoo('18')
// undefined
// daisy
// 18
console.log(obj.habit)
console.log(obj.friend)
// shopping
// kevin
儘管在全域性和 foo
中都宣告瞭value
值,最後依然返回了undefind
,說明繫結的this
失效了,這個時候的this
已經指向了obj
。
Function.prototype.bind2 = function (context) {
const self = this;
const args = Array.prototype.slice.call(arguments, 1)
const fBound = function () {
const bindArgs = Array.prototype.slice.call(arguments)
// 當作為建構函式時,this 指向例項,此時結果為 true,將繫結函式的 this 指向該例項,可以讓例項獲得來自繫結函式的值
// 以上面的是 demo 為例,如果改成 `this instanceof fBound ? null : context`,例項只是一個空物件,將 null 改成 this ,例項會具有 habit 屬性
// 當作為普通函式時,this 指向 window,此時結果為 false,將繫結函式的 this 指向 context
self.apply(this instanceof fBound ? this : context, args.concat(bindArgs))
}
// 修改返回函式的 prototype 為繫結函式的 prototype,例項就可以繼承繫結函式的原型中的值
fBound.prototype = this.prototype
return fBound;
}
建構函式效果的優化實現
但是在這個寫法中,我們直接將fBound.prototype = this.prototype
,我們直接修改 fBound.prototype
的時候,也會直接修改繫結函式的prototype
。這個時候,我們可以通過一個空函式來進行中轉:
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable")
}
const self = this
const args = Array.prototype.slice.call(arguments, 1)
const fNOP = function () {}
const fBound = function () {
const bindArgs = Array.prototype.slice.call(arguments)
self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs))
}
fNOP.prototype = this.prototype
fBound.prototype = new fNOP()
return fBound
}
new
new
運算子建立一個使用者定義的物件型別的例項或具有建構函式的內建物件型別之一。
因為new
是關鍵字,所以無法像bind 函式
一樣直接覆蓋,所以我們寫一個函式,命名為objectFactory
,來模擬new
的效果。用的時候是這樣的:
第一版
function objectFactory() {
const obj = new Object()
const Constructor = [].shift.call(arguments)
obj.__proto__ = Constructor.prototype
Constructor.apply(obj, arguments)
return obj
}
在這一版中,我們:
- 用
new Object()
的方式新建了一個物件obj
- 取出第一個引數,就是我們要傳入的建構函式。此外因為
shift
會修改原陣列,所以arguments
會被去除第一個引數 - 將
obj
的原型指向建構函式,這樣obj
就可以訪問到建構函式原型中的屬性 - 使用
apply
,改變建構函式this
的指向到新建的物件,這樣obj
就可以訪問到建構函式中的屬性 - 返回
obj
function Person(name, age) {
this.name = name
this.age = age
this.habit = 'Games'
}
Person.prototype.strength = 60
Person.prototype.sayYourName = function () {
console.log('I am ' + this.name)
}
function objectFactory() {
const obj = new Object()
const Constructor = [].shift.call(arguments) // Person
// arguments 除去了第一個引數(建構函式)後的引數
obj.__proto__ = Constructor.prototype
Constructor.apply(obj, arguments)
return obj;
}
const person = objectFactory(Person, 'Kevin', '18')
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60
person.sayYourName() // I am Kevin
返回值效果實現
接下來我們再來看一種情況,假如建構函式有返回值,舉個例子:
function Person(name, age) {
this.strength = 60;
this.age = age;
return {
name: name,
habit: 'Games'
}
}
const person = new Person('Kevin', '18');
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined
在這裡我們是返回了一個物件,假如我們只是返回一個基本型別的值呢?
function Person (name, age) {
this.strength = 60;
this.age = age;
return 'handsome boy';
}
const person = new Person('Kevin', '18');
console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18
第二版
function objectFactory() {
const obj = new Object()
const Constructor = [].shift.call(arguments)
obj.__proto__ = Constructor.prototype
const ret = Constructor.apply(obj, arguments)
return typeof ret === 'object' ? ret : obj
}
相關文章
- call,apply,bind,new實現原理APP
- this與new、call、apply、bind的關係APP
- this, call, apply 和 bindAPP
- JavaScript之call, apply, bind, new的實現JavaScriptAPP
- JavaScript重識bind、call、applyJavaScriptAPP
- 重寫JS中的apply,call,bind,new方法JSAPP
- this、apply、call、bindAPP
- [譯] Javascript: call()、apply() 和 bind()JavaScriptAPP
- 詳解 new/bind/apply/call 的模擬實現APP
- Javascript - apply、call、bindJavaScriptAPP
- call,apply和bind的區別APP
- 手寫call,apply,bindAPP
- apply,call,bind的用法APP
- call、apply、bind 區別APP
- apply call bind 簡介APP
- call apply bind區別APP
- apply & call & bind 原始碼APP原始碼
- bind/call/apply 深度理解APP
- JavaScript-apply、bind、callJavaScriptAPP
- this指向與call,apply,bindAPP
- 手寫call、apply、bindAPP
- 前端戰五渣學JavaScript——call、apply以及bind前端JavaScriptAPP
- 淺談JavaScript中的apply、call和bindJavaScriptAPP
- 【譯】理解this及call,apply和bind的用法APP
- js深入之實現call、apply和bindJSAPP
- 談談JavaScript中的call、apply和bindJavaScriptAPP
- apply 、call 以及 bind 的使用和區別APP
- JS中的call、apply、bindJSAPP
- JavaScript 中的 apply、call、bindJavaScriptAPP
- JavaScript進階之模擬call,apply和bindJavaScriptAPP
- 模擬實現apply/call/bindAPP
- js call、apply、bind的實現JSAPP
- bind,call,apply模擬實現APP
- call.apply.bind 走一個!APP
- 一文搞懂 this、apply、call、bindAPP
- javascript的call apply和new原理剖析 [手寫]JavaScriptAPP
- javascript 物件導向學習(三)——this,bind、apply 和 callJavaScript物件APP
- js之call,apply和bind的模擬實現JSAPP