java中對象包含屬性和方法, 方法即函數, 只有對象可以調用方法
但是js中一切皆對象, 函數是對象, 函數也可以調用方法
Function.prototype.call
Function.prototype.apply
Function.prototype.bind
Function.prototype.toString
為什么js語言會在function原型上掛上這四個函數?
因為好處太多了, 現在我就說一說call的應用
// A對象
let a = {
name: "aaa",
toName(age) {
console.log(age, "我的名字:", this.name)
}
}
// B對象
let b = {
name: "bbb",
}
// A對象調用自己的方法
a.toName('A對象')
// A對象具有某種能力, 能打印自己的名字,但是如何與別人共享這種能力呢?
// 編程思想促進了語法的形成 ==> 于是js語法就設計了這種通用能力
a.toName.call(b, 'B對象')
再深入思考一下, call語法是基于什么形成的呢 ?
了解底層語法有助于我們理解高級的語法
因為在底層的語法眼中, 高級語法只不過是工具, 根本不算語法, 不過是由我定義構建處理來的
理解call函數, 核心必要知識
- 接受函數也可以調用函數這個觀念(違反直覺, 需要編程思維, 原型 原型鏈 prototype)
- 詞法作用域 作用域鏈(不同瀏覽器內核不同, 谷歌瀏覽器是v8設計出來的js執(zhí)行機制, 底層整個系統(tǒng)基于算法 + 數據結構, 總之, 這就是js執(zhí)行規(guī)則的制定者)
- 誰是this, 即誰在調用函數
函數調用, 需要知道函數當前的上下文信息,
因為函數內部的作用域可能會引用函數擁有者的相關信息, 函數擁有者就是函數的上下文,
找不到需要的信息, 通過作用域鏈就會在全局作用域尋找
Object.prototype.mycall = function(obj){
// a.toName.mycall(b, 'B對象')
// 拆開分析 , 上面是我們的目標
// 1. 可以看出, a.toName就是我們要寫的這個函數的調用者, 'B對象',就是傳入的第二個參數
let fn = this
// 此時fn就是我們要借用的函數 a.toName 用來打印名字, 但是此時它打印的名字依然是A對象的
// 2. 如何讓它打印傳入對象obj, 也就是b的名字呢?
// 作用域分析, 方法一: 將A對象與B對象交換作用域 (v8也許有,但我不知道)
// 方法二: 打不過就加入, A對象抄襲B對象, 將函數復制一份, 變成自己的函數, 然后調用自己的函數
obj.copyFn = fn
obj.copyFn()
}
call的本質并不是調用別人的函數, 而是copy了別人的函數
哈哈哈, v8內部的不知道, 反正js基于現有語法就有這個能力 (還有解法, 基于proto,原型鏈的方式, var temp = Object.create(null)), 利用干凈的地方, 防止覆蓋原對象的函數
Function.prototype.call = function(ctx, ...args) {
var proto = null
if ('__proto__' in ctx) {
proto = ctx.__proto__
}
var temp = Object.create(proto)
temp.$f = this
ctx.__proto__ = temp
var result = ctx.$f(...args)
ctx.__proto__ = proto
return result
}
顯然, 我們自己的上面的mycall還要很多問題
- 將傳入的對象添加了copyFn函數, 可能重置掉對象原有的copyFn, 修改了對象, 需要delete刪除
- 傳入對象需要進行類型判斷
- 傳入參數需要處理
- 返回值
Object.prototype.mycall = function (obj) {
obj = obj || window //node環(huán)境下無法使用window
// 這里如果害怕屬性覆蓋可以設置唯一值, 這里就不展開了
// Object.getOwnPropertyNames() // 獲得對象的自己的屬性[]
let fn = this
//給context添加一個方法 指向this
obj[fn] = this
// 處理參數 去除第一個參數this 其它傳入fn函數
let arg = [...arguments].slice(1)
// 或者使用rest運算符獲取剩余參數
// function f(a, ...arg) {
// console.log(arg, Array.isArray(arg))
// }
// f() //[] true
// f(1) //[] true
// f(1, 2) //[2] true
// f(1, 2, 3) //[2, 3] true
let res = context[fn](...arg) //執(zhí)行fn
delete context[fn] //刪除屬性
return res
}
這里我們就實現了自己的call, 以后看見call就知道都不過是些雕蟲小技了(當然真實的實現方式肯定需要考慮更多因素和性能, 而且在v8更大的環(huán)境下, 可能還不止一種實現方式,保持謙虛)
call的雕蟲小技
Array, String等對象函數的借調
Array.prototype.slice.call(arguments)
[].slice.call(arguments)
Object.prototype.toString.call(arr).slice(8, -1) // Array實現借用構造函數繼承
function Person(name,age,love){
this.name=name;
this.age=age;
this.love=love;
this.say=function say(){
alert("姓名:"+name);
}
}
//call方式
function student(name,age){
Person.call(this,name,age);
}
- vue源碼中也有相關應用先寫這么多了哈.