1. 题目
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
}
fn1.call.call.call(fn2)
2. 分析
要了解这个输出结果,我们要先了解下this的指向和call的实现,下面我一步一步的介绍。
2.1 this指向
this指向当前的调用者。
例子
getValue函数是obj调用的,此时,this为obj对象,所有this.name为qy.
let obj = {
name: 'qy',
getValue(){
console.log(this.name)
}
}
obj.getValue() // qy
obj['getValue']() // qy this指向的也是obj
2.2 call的原理与实现
概念
call 方法是 JavaScript 中 Function 原型链上的一个方法,用于调用一个函数,并在调用时显式地指定 this 的值和传入的参数。
2.2.1 格式
javascript
Copy code
function.call(thisArg, arg1, arg2, ...)
thisArg:在函数运行时使用的this值。arg1, arg2, ...:传递给函数的参数列表。
2.2.2 特点
- 显式设置
this值:call方法允许你在调用函数时显式地设置this值,使得函数可以在不同的上下文中执行。 - 参数逐一传递:与
apply方法不同,call方法需要将参数逐个传递给函数。 - 无返回值的限制:
call方法返回被调用函数的返回值,不限制函数类型。 - 改变函数上下文:可用于继承、借用方法等场景。
2.2.3 使用
- 基本使用
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Alice' };
greet.call(person, 'Hello', '!'); // 输出: Hello, Alice!
在这个例子中,greet 函数的 this 被设置为 person 对象,并传入了两个参数 'Hello' 和 '!'。
- 继承与借用方法
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age, grade) {
Person.call(this, name, age); // 借用 Person 构造函数
this.grade = grade;
}
const student = new Student('Bob', 20, 'A');
console.log(student.name); // 输出: Bob
console.log(student.age); // 输出: 20
console.log(student.grade); // 输出: A
在这个例子中,Student 构造函数借用了 Person 构造函数,以初始化 name 和 age 属性。
- 借用数组方法
const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
const array = Array.prototype.slice.call(arrayLike);
console.log(array); // 输出: ['a', 'b', 'c']
在这个例子中,call 方法将 Array.prototype.slice 方法应用到类数组对象 arrayLike 上,将其转换为真正的数组。
2.2.4 实现call
Function.prototype.call = function(context, ...args) {
var context = context || window //保证唯一性
const fn = Symbol('fn')
context[fn] = this
const result = context[fn](...args)
delete context[fn]
return result
}
2.2.5 总结
call方法是 JavaScript 中的一个强大工具,允许显式地设置this值。- 它可以传递任意数量的参数。
- 主要用于改变函数执行上下文和借用其他对象的方法
2.3 原型
Function.prototype是一个函数.
console.log(typeof Function.prototype) // function
3. 讲解
要了解fn1.call.call.call(fn2) 我们先看下fn1.call(fn2)。
我们先看下的结果。
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
}
fn1.call(fn2) // 1
在调用fn1.call的时候,this指向的是fn1,因此,源码中context[fn](...args)等价于fn1(..args).
我们再看下fn1.call.call.call(fn2),调用多个call的是很好,其实call只是Function里面的一个方法,我们只需要看fn1.call.call(fn2)的结果
fn1.call.call.call.call === Function.prototype.call // true
fn1.call.call === Function.prototype.call // true
这个时候this的指向是call函数,即为Function.prototype.call, 但是context是fn2函数。实现的call中,context[fn](...args)等价于fn2(...args),并绑定call函数。
const context = fn2() {
}
context[Symbol] = call
会继续的调用call, context[Symbol](...args),输出了2.
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
}
fn1.call.call.call(fn2) // 2
4. 扩展
大家可以思考下哈
function fn2() {
console.log(2)
}
Function.call(fn2) // ?
Function.call.call.call(fn2)// ?