Js值传递还是引用传递?

引子

据:scope = {row:{inputVIsible:true}}

之前在做Vue项目时遇到了一个问题:想将scope.row.inputVisible从true改成false。视频中老师是这么敲的:

handleClick(row) {
    row.inputVisible = false
}
// 接收参数scope.row

而我是这么敲的:

handleClick(inputVisible) {
    inputVisible = fakse
}
// 接收参数scope.row.inputVisible

结果我的方法没有实现预期的效果。经过一番百度与Google,终于查出了我想要的答案。

结论

首先说结论:js中没有引用传递。对于js来说:

  • 基本类型是传值调用
  • 引用类型传共享调用

开始

那么什么是基本类型,什么是引用类型呢?MDN上这样说:

基本类型(基本数值、基本数据类型)是一种既非对象也无方法的数据。在 JavaScript 中,共有7种基本类型:stringnumberbigintbooleannullundefinedsymbol (ECMAScript 2016新增)。

所有基本类型的值都是不可改变的。但需要注意的是,基本类型本身和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而原值不能像数组、对象以及函数那样被改变。

什么叫不可改变?那得先知道什么叫改变:

var a = 5;
a = 6;

你可能认为这里a的值改变了。确实,它的值改变了,不过MDN对于这段代码的定义为:替换。

什么是改变呢?看代码:

// 使用字符串方法不会改变一个字符串
var bar = "baz";
console.log(bar);               // baz
bar.toUpperCase();
console.log(bar);               // baz

// 使用数组方法可以改变一个数组
var foo = [];
console.log(foo);               // []
foo.push("plugh");
console.log(foo);               // ["plugh"]

// 赋值行为可以给基本类型一个新值,而不是改变它
bar = bar.toUpperCase();       // BAZ

所以对于这7种基本类型,JavaScript采用的是传值调用,即传递副本(the copy of),我们在函数中操作的都是它的副本,并不能影响到原始值;这也就是它们为什么不可改变的原因。

三种不同传递的定义:

按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。

按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本。这意味着函数形参的值如果被修改,实参也会被修改。同时两者指向相同的值。

传共享传递(call by sharing),函数接收的是对象引用的拷贝(the copy of the reference to object)。这个引用拷贝与形参相关联,并且是它的值。这里既不是按值传递的对象副本,也不是按引用传递的隐式引用。

按值传递和按引用传递还是比较容易区分开来的。但是什么是按共享传递呢?看上去它和按引用传递并没有什么区别。在ECMA-262-3 in detail. Chapter 8. Evaluation strategy中对这一概念作了清晰的描述与案例演示:参数的值不是直接的别名,而是地址的拷贝。在这种情况下对于对象属性的重新赋值并不会像按引用传递那样直接修改原始对象的属性。但是,由于形式参数仍然接收地址,所以它可以访问原始对象的内容(属性),并对其进行修改。

光看文字肯定是理解不了的,上代码:

function modifyProperties(object) {
  object.x = 100;
  object.y = 200;
}

function rewriteObject(object) {
  object = { newX: 1, newY: 2 };
}
// 这是两个函数,分别对形参进行修改。第一个是通过对形参属性的访问进行重新赋值,第二个是通过直接对形参这个对象整体进行重新赋值

point = {
  x: 10,
  y: 20
}// 这是下面将用来演示的原始对象

按照引用传递的结果:

modifyProperties(point)
console.log(point)// 改变了: {x: 100, y: 200}
rewriteObject(point)
console.log(point)// 再次发生改变: {newX: 1, newY: 2}
// 可以看到rewriteObject方法对形参这个对象进行了重新赋值,对象上的属性不再是原来的属性。

按共享传递(即JavaScript对于引用类型的传值方式)的结果:

modifyProperties(point)
console.log(point)// 改变了: {x: 100, y: 200}
rewriteObject(point)
console.log(point)// 重写失败:{x: 100, y: 200}
// rewriteObject并没有成功的对形参进行重新赋值,

看到这里你应该对JavaScript传值有了初步的印象,

再看一个例子:

function setName(obj) {
  obj.name = "Nicholas";
  obj = new Object();
  obj.name = "Greg";
}

var person = new Object();
setName(person);
console.log(person.name);

聪明的你应该知道这里输出的是什么。如果不知道,请自己动手尝试一下!

原因:这里的obj = new Object(); 其实就和上面说的rewriteObject()方法一样,是对于形参的整体重新赋值,所以这里并不能改变原来的对象(origin object)。而obj.name = "Nicholas" 等同于上面的modifyProperties()方法,是对形参对象的某个属性进行赋值,由于形式参数仍然接收地址,所以它可以访问原始对象的内容(属性),并对其进行修改。

回到引子

据:scope = {row:{inputVIsible:true}}

当我们将scope.row.inputVisible作为函数参数时,由于是共享传递,对于形参的赋值并不会影响实参的值。

而当我们将scope.row作为函数的参数时,虽然引用是副本,但是它们共享相同的对象,所以对于形参的属性值的修改会影响到实参的属性值。

最终我们可以这样理解按共享传递:当对于形参本身的修改是不会影响到实参的,这时是按值传递;而当我们想要访问形参的某个属性(例如:修改某个属性值、新增某个属性值、或者删除某个属性值)时,这时对形参属性的修改就会影响到实参的属性,这时就是按引用传递值。

最后,再强调一下结论:js传值方式

  • 基本类型是传值调用
  • 引用类型传共享调用

参考

https://zhuanlan.zhihu.com/p/25314908

https://developer.mozilla.org/

https://www.cnblogs.com/xcsn/p/9158727.html

加载评论