js参数传递
先贴一段《JavaScript高级程序设计》的介绍
ECMAScript 中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样。
原始类型(按值传递)
function addTen(num) {
num += 10;
console.log(num); // 30
}
let count = 20;
addTen(count);
console.log(count); // 20
引用类型(按共享传递)
// case1
function changeName(obj) {
obj.name = 'newName'
console.log(obj.name); // 'newName'
}
let person = {
name: 'cx'
}
changeName(person);
console.log(person.name); // 'newName'
// case2
function changeName(obj) {
obj = { name: 'newName' }
console.log(obj.name); // 'newName'
}
let person = {
name: 'cx'
}
changeName(person);
console.log(person.name); // 'cx'
那为什么case1能按引用传递呢?因为基本类型值存储于栈内存中,传递的就是当前值,修改不会影响原有变量的值。而引用类型在栈中存的是地址,传递的是地址索引,修改的是该地址在堆内存中的值。例子2中将obj重新赋值为另一个地址,所以它不会影响到person对象。
call
分析call
具体用法
function showVal() {
console.log('实参', ...arguments);
return `当前值是${this.value}`
}
var value = 2
const obj = {
value: 1,
}
showVal.call(obj, { aa: 111 }, { b: 222 }) // // 实参 { aa: 111 } { b: 222 } 当前值是1
showVal.call(null, { aa: 111 }, { b: 222 }) // // 实参 { aa: 111 } { b: 222 } 当前值是2
简要分析
- 改变了
this指向 - 运行了
showVal函数 - 能传递指定参数
- 不传
this参数时,this指向全局(浏览器指向window,node环境指向global/globalThis)
实现call
步骤
- 将调用的函数设置为对象的属性
- 执行该函数并返回值
- 删除该函数
实现
Function.prototype.myCall1 = function (context) {
let args = Array.prototype.slice.call(arguments, 1)
context ??= globalThis
context.fn = this
let result = context.fn(...args)
delete context.fn
return result
}
//测试
window.value = 2
function showVal() {
console.log('实参', ...arguments);
return `当前值是${this.value}`
}
const obj = {
value: 1,
}
console.log(showVal.myCall1(obj, { aa: 111 }, { b: 222 })); // 实参 { aa: 111 } { b: 222 } 当前值是1
console.log(showVal.myCall1(null, { aa: 111 }, { b: 222 })); // 实参 { aa: 111 } { b: 222 } 当前值是2
优化
Function.prototype.myCall2 = function (context, ...args) {
context ??= globalThis
let fnSymbol = Symbol() // 保证键的唯一性
context[fnSymbol] = this
let result = context[fnSymbol](...args)
delete context[fnSymbol]
return result
}
apply
分析与实现
与call类似,不过入参为数组,若不是数组则直接调用函数且参数为非对象类型时报错
Function.prototype.myApply = function (context, args) {
if (typeof args !== 'object') throw new TypeError('CreateListFromArrayLike called on non-object')
context ??= globalThis
args = Array.isArray(args) ? args : []
let fnSymbol = Symbol()
context[fnSymbol] = this
let result = context[fnSymbol](...args)
delete context[fnSymbol]
return result
}
window.value = 2
function showVal() {
console.log('实参', ...arguments);
return `当前值是${this.value}`
}
const obj = {
value: 1,
}
console.log(showVal.apply(obj, { aa: 111 }, { b: 222 })); // 实参 当前值是1
console.log(showVal.myApply(obj, [{ aa: 111 }, { b: 222 }])); // 实参 { aa: 111 } { b: 222 } 当前值是1
console.log(showVal.myApply(null, { aa: 111 }, { b: 222 })); // 实参 当前值是2
bind
分析bind
具体用法
var value = 2
function showVal() {
console.log('实参', ...arguments);
return `当前值是${this.value}`
}
const obj = {
value: 1,
}
const _bind = showVal.bind(obj, 'params1')
console.log(_bind('params2')); // 实参 params1 params2; 当前值是1
简要分析
- 改变
this指向,返回一个函数 bind函数和它返回的函数都可以接收参数
实现bind
Function.prototype.myBind = function (context) {
const self = this
let args = Array.prototype.slice.call(arguments, 1)
return function () {
let bindArgs = Array.prototype.slice.call(arguments)
return self.apply(context, args.concat(bindArgs))
}
}
// rest参数写法
Function.prototype.myBind2 = function (context, ...args) {
return (...args2) => {
return this.call(context, ...args, ...args2)
}
}
window.value = 2
function showVal() {
console.log('实参', ...arguments);
return `当前值是${this.value}`
}
const obj = {
value: 1,
}
const _bind = showVal.myBind(null, 'params1')
console.log(_bind('params2')); // 实参 params1 params2; 当前值是1
构造函数效果模拟
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
也就是当bind返回的函数当构造函数使用时,调用bind时传入的this会失效,但其他参数依然会生效
bind演示
window.value = 2
function showVal(name, age) {
this.name = name
console.log('值:', this.value, '参数', arguments);
}
const obj = {
value: 1,
}
const newBind = showVal.bind(obj, 'cx')
newBind('params2') // 值: 1 参数: Arguments(2) ['cx', 'params2']
console.log('----------------');
const foo = new newBind(24) // 值: undefined 参数: Arguments(2) ['cx', 24]
console.log(foo); // showVal { name: 'cx' }
构造函数效果实现
function showVal(name, age) {
this.name = name
console.log('值:', this.value, '参数', arguments);
}
showVal.prototype.xxx = 'xxx'
const obj = {
value: 1,
}
Function.prototype.myBind = function (context, ...args) {
const self = this
//空函数进行中转
const Fntemp = function () { }
const FBound = function (...args2) {
return self.apply(this instanceof Fntemp ? this : context, [...args, ...args2])
}
// FBound.prototype = this.prototype // 直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype
Fntemp.prototype = this.prototype
FBound.prototype = new Fntemp()
return FBound
}
const newBind = showVal.myBind(obj, 'cx')
newBind('params2') // 值: 1 参数: Arguments(2) ['cx', 'params2']
console.log('----------------');
const foo = new newBind(24) // 值: undefined 参数: Arguments(2) ['cx', 24]
console.log(foo); // FBound {name: 'cx'}
new
new的过程
《JavaScript高级程序设计》里是这么介绍的
- 在内存中创建一个新对象
- 这个新对象内部的
[[Prototype]]特性被赋值为构造函数的prototype属性 - 构造函数内部的
this被赋值为这个新对象(即this指向新对象) - 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
实现new
function newFn(fatherFn, ...args) {
if (typeof fatherFn !== 'function') return `the frist param must be a function`
let obj = Object.create(fatherFn.prototype)
let res = fatherFn.call(obj, ...args)
return res instanceof Object ? res : obj
}
function Person(name, age) {
this.name = name
this.age = age
this.xxx = 'xxx'
return null
}
Person.prototype.say = function () {
console.log(`I am ${this.name}, I'm ${this.age} years old. `);
}
const person1 = newFn(Person, 'cx', 24)
console.log(person1); // Person { name: 'cx', age: 24, xxx: 'xxx' }
person1.say() // I am cx, I'm 24 years old.