JavaScript基础
参数按值传递
按值传递
- ECMAScript中所有函数的参数都是
按值传递
的。 - 按值传递:把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
var value = 1;
function foo(v) {
v = 2;
console.log(v); //2
}
foo(value);
console.log(value) // 1
内存分布:
改变前:
改变后:
当传递 value 到函数 foo 中,相当于拷贝了一份 value,假设拷贝的这份叫 _value,函数中修改的都是 _value 的值,而不会影响原来的 value 值。
共享传递
按引用传递:传递对象的引用(真实的物理地址),函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。
var obj = {
value: 1
};
function foo(o) {
o.value = 2;
console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2
改变前:
改变后:
var obj = {
value: 1
};
function foo(o) {
o = 2;
console.log(o); //2
}
foo(obj);
console.log(obj.value) // 1
共享传递是指,在传递对象的时候,传递的是地址索引。
所以修改 o.value,可以通过引用找到原值,但是直接修改 o,并不会修改原值。所以上面两个例子其实都是按共享传递。
改变前:
改变后:
函数传递参数 ,传递的是参数的拷贝:
- 指针拷贝,拷贝的是地址索引;
- 常规类型拷贝,拷贝的是值 ;
call、apply、bind和new的实现
call,apply,bind方法解决的是:this指向
this
this首先需要跟执行上下文挂钩
绑定方式:软绑定,硬绑定(call、apply、bind)
软绑定
this.name="window"
function test() {
console.log(this.name);
}
const zhangsan = {
name: "zhangsan",
test: test,
};
test()
zhangsan.test()
硬绑定
function test() {
console.log(this.name);
}
const xiaoming={
name:'xiaoming'}
//硬绑定
test.call(xiaoming)
test.apply(xiaoming)
const t=test.bind(xiaoming)
t()
注意两点:
- call 改变了 this 的指向,指向到 xiaoming;
- test 函数执行了;
call的实现
call() :在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
第一步
使用软绑定的方式实现call,
所以我们模拟的步骤可以分为:
- 将函数设为对象的属性;
- 执行该函数;
- 删除该函数;
// 第一步
// test 是对象的属性名,反正最后也要删除它,所以起什么都可以。
xiaoming.test = test
// 第二步
xiaoming.test()
// 第三步
delete xiaoming.test
根据上述思路,提供一版:
// 第一版
Function.prototype.call2 = function(context) {
// 首先要获取调用call的函数,用this可以获取,this指向的是 bar
context.fn = this;
context.fn();
delete context.fn; //卸磨杀驴
}
// 测试一下
let foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo); // 1
第二步
call除了可以指定this,还可以指定参数
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call(foo, 'kevin', 18);
// kevin
// 18
// 1
可以从 Arguments 对象中取值,取出第二个到最后一个参数,然后放到一个数组里。
上述代码的Arguments中取第二个到最后一个的参数
// 以上个例子为例,此时的arguments为:
// arguments = {
// 0: foo,
// 1: 'kevin',
// 2: 18,
// length: 3
// }
// 因为arguments是类数组对象,所以可以用for循环
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
// 执行后 args为 ["arguments[1]", "arguments[2]", "arguments[3]"]
接下来使用eval拼接成一个函数
eval('context.fn(' + args +')')
考虑到目前大部分浏览器在console中限制eval的执行,也可以使用rest
此处代码为:
// 第二版
Function.prototype.call2 = function(context) {
context.fn = this;
let arg = [...arguments].slice(1) //arguments是类数组对象,将其拆分,并转为数组,然后再从第一个参数开始截取
context.fn(...arg) //arg数组拆分,逐步传入
delete context.fn;
}
// 测试一下
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call2(foo, 'kevin', 18);
// kevin
// 18
// 1
第三步
- this 参数可以传 null,当为 null 的时候,视为指向 window
举个例子:
var value = 1;
function bar() {
console.log(this.value