6.函数是特殊的对象2 - JS

在第一部分中,主要总结了函数作为一个对象的常见属性(name/length)、如何自定义属性以及如何使用函数构造器(Function)。

这里总结函数作为对象的常见方法(apply/call/bind/toString)。


使用 call 绑定 this

call 方法指定 this 并且逐个提供参数。

基本语法

  • thisArg,调用 f 时指定的 this 值:
    • 非严格模式下,null/undefined 将被替换为全局对象,
    • 原始值(String/Number/...)将被转换为对象;
  • arg1, arg2, /* …, */ argN 函数的参数,逐个传入。
f.call(thisArg)
f.call(thisArg, arg1)
f.call(thisArg, arg1, arg2)
f.call(thisArg, arg1, arg2, /* …, */ argN)

简单案例

下面一个案例:普通调用时,this 指向全局;call 调用时,this 指向具体对象。

function log() {
	console.log(this.xxx);
	console.log(this);
}
let o = { xxx: 100, yyy: 200 }

log();			// undefined或其他(globalThis.xxx) - globalThis/Window
log.call(o)		// 100 - { xxx: 100, yyy: 200 }
log.call()		// undefined或其他(globalThis.xxx) - globalThis/Window
log.call(null)	// undefined或其他(globalThis.xxx) - globalThis/Window

上面的例子中,undefined/null 将转换成全局对象,但在严格模式下:

"use strict"
function log() {
	console.log(this);
}

log.call()			// undefined
log.call(null)		// null

借用方法

一个对象的方法,给另外一个对象使用。达到目的方案如下:

  1. 直接通过对象调用,并且通过 call 指定其他的对象;
  2. 先将方法赋值成为一个函数,在通过 call 的调用指定其他的对象;
  3. 先将方法赋值成为一个函数,并对 call 进行自绑定。
const cat = {
	name: 'Kitty',
	showName () { console.log(this.name); }
}
const dog = {
	name: '阿旺'
}

cat.showName.call(dog)			// '阿旺'

let showName = cat.showName
showName.call(dog)				// '阿旺'

let boundedShowName = showName.call.bind(showName)
boundedShowName(dog)			// '阿旺'

对象原型链上的方法也可以借用。下面一个例子中,借用数组原型链上的切片方法。

let arr = [1, 2, 3, 4];
const slice = Array.prototype.slice;
const boundedSlice = slice.call.bind(slice);

slice.call(arr, 1)		// [2, 3, 4]
boundedSlice(arr, 1)	// [2, 3, 4]

apply 方法

applycall 方法能够达到几乎等效目的。

基本语法

  • thisArg,调用 f 时指定的 this 值:
    • 非严格模式下,null/undefined 将被替换为全局对象,
    • 原始值(String/Number/...)将被转换为对象;
  • argsArray,类数组对象,调用 f 时的参数(或者如果不需要向函数提供参数,则为 null/undefined)。
f.apply(thisArg)
f.apply(thisArg, argsArray)

apply VS call

大多数情况下,使用 callapply 均可,apply 可能会更快,因为大多数 JS 引擎在内部对其进行了优化。下面一个等效表达中:

  • arr 期望是一个类数组对象;
  • itor 期望是一个可迭代对象。
f.apply(thisArg, arr)
f.call(thisArg, ...itor)

可以看出,apply/call 两者几乎相同,不同的是参数方式,参数在 call() 中逐个作为列表传递,而在 apply() 中它们会组合在一个类数组对象中。

Math.max.apply(null, [1, 3, 5])		// 5
Math.max.call(null, 1, 3, 2)		// 3

简单案例

下面一个例子中:在一个数组后添加若干元素。

let arr = ["a", "b"];
arr.push.apply(arr, [0, 1, 2]);
arr				// ["a", "b", 0, 1, 2]

arr.push.call(arr, 3, 4)
arr				// ["a", "b", 0, 1, 2, 3, 4]

arr.push(arr, ...[5, 6])
arr				// ["a", "b", 0, 1, 2, 3, 4, 5, 6]

呼叫转移

将所有参数、上下文一起传递给另一个函数被称为“呼叫转移”。下面是一种最简单的表达,在外部看来 f()w() 没有声明区别。

function f() { console.log(arguments[0]) }
function w() { f.apply(this, arguments) }

f()		// 1
w()		// 1

使用 bind 创建一个 Bounded Function - 绑定函数

bind 方法创建一个新函数,当调用该新函数时,它会调用原始函数(目标函数-Target Function):

  • 将其 this 上下文绑定为指定值;
  • 同时,还可以绑定一系列参数(会插到新函数传入参数的前面)。

基本语法

  • thisArg,调用绑定函数给原始函数 f 时指定的 this 值:
    • 非严格模式下,null/undefined 将被替换为全局对象,
    • 原始值(String/Number/...)将被转换为对象,
    • 如果使用 new 创建绑定函数,忽略该值;
  • arg1, arg2, /* …, */ argN,在调用原始函数 f 时,逐个插入到传入绑定函数的参数前的参数。。
f.bind(thisArg)
f.bind(thisArg, arg1)
f.bind(thisArg, arg1, arg2)
f.bind(thisArg, arg1, arg2, /* …, */ argN)

bind 参数的绑定顺序

给函数指定 this 值,并不复杂。下面一个案例具体讲述 bind 参数的绑定顺序问题。

  • boundedFn1 成功地绑定了 this 对象 为一个字符串 xy,并且将两个其余参数绑定,在调用时:
    • 目标函数 fnthis 指定为 xy
    • 通过 bind 绑定的其他参数插入在目标函数参数列表的前面,相当于 fn('a', 'b', '1', '2')
  • boundedFn2 失败地绑定了 this 对象 为一个字符串 xxyy,并且将两个其余参数绑定,在调用时:
    • 目标函数 fnthis 指定仍然xy(因为 boundedFn1 已经是一个绑定函数,this 的指定不会被覆盖);
    • 通过 bind 绑定的其他参数插入在目标函数参数列表的前面,以及第一次绑定参数的后面,相当于 fn('a', 'b', 'c', 'd', '1', '2')
function fn(...args) {
	console.log(this, args.join(''));
}

let boundedFn1 = fn.bind('xy', 'a', 'b')
boundedFn1('1', '2')		// String {'xy'} 'ab12'

let boundedFn2 = boundedFn1.bind('xxyy', 'c', 'd')
boundedFn2('1', '2')		// String {'xy'} 'abcd12'

一般的,对于多层绑定函数,this 指定是第一层;其他参数按照层数,依次插入在目标函数的参数列表前面(fn(...argsFloor1, /* ... */, argsFloorX, ...args))。

使用 toString 方法获取函数源码字符串

  1. 函数作为对象,重写了从 Object 继承来的 toString() 方法。toString 方法返回一个包含用于定义函数的源文本段的字符串。
  2. 对于内置函数、由 bind 创建的绑定函数、非 JavaScript 函数,调用 toString(),返回一个看起来像原函数的字符串。
function f() {
	console.log(111)
}

f.toString()				// 'function g() { \n    console.log(111) \n}'
g.bind(null).toString()		// 'function () { [native code] }'
Math.max.toString()			// 'function max() { [native code] }'

实际源代码与 toString() 结果比较

注意:toString 方法可以修改。

function f() { }
class A {
  	a() { }
}
function* g() {	}

f.toString()			// "function f() { }"			普通函数
A.toString()			// "class A { a() { } }"		类
g.toString()			// "function* g() { }"			生成器
((a) => a).toString()		// "(a) => a"				箭头函数
({ a() {} }.a).toString()	// "a() {}"					方法
({ *a() {} }.a).toString()  // "*a() {}"				生成器方法
({ [x]() {} }[x]).toString()// "[x]() {}"				计算方法
Function.prototype.toString.toString()	// "function toString() { [native code] }"
f.bind(null).toString()					// "function () { [native code] }"
(Function("a", "b")).toString()			// function anonymous(a\n) {\nb\n}
(Object.getOwnPropertyDescriptor({ get a() {} }, "a").get).toString()	// "get a() {}"
(Object.getOwnPropertyDescriptor({ set a(x) {} }, "a").set).toString()	// "set a(x) {}"

关于什么的计算方法/属性:计算变量不能点式读取。

let [x, y, z] = ['ad', 'cc', 'gg']
let o = { [x]: 11, [y]: function() {}, [z] (){} }

o.ad	// 11
o.cc	// f () {}
o.gg	// ƒ [z](){}
o[x]	// 11
o[y]	// f () {}
o[z]	// ƒ [z](){}
o.x		// undefined
o.y		// undefined
o.z		// undefined
  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值