函数实参为对象时的陷阱

1. 正文

rc-util 源码仓库

上周在实现某个弹层功能的时候,用到了rc-util里的 contains 方法函数, 结果 code-review 的时候同事对该代码提出了疑问:

export default function contains(root, n) {
  let node = n;
  while (node) {
    if (node === root) {
      return true;
    }
    node = node.parentNode;
  }
  return false;
}

上述代码是 antd 内部抽象的一个工具方法,用来判断某个dom是否为另一个dom的祖先节点。

同事疑问的是 let node = n; 这段代码是不是多余的?

首先一开始的理解是 函数参数 n 是一个对象,一个dom节点对象。 如果用 node 保存 n 的值,防止 node = node.parentNode 这段代码执行的时候,会改变传入的实参 n 对应的值。

毕竟以下的代码我们都很熟悉:

function contains(root, n) {
  if(n) {
    n.a = 3
  }
}

const A = {a:1};
const B = {a:2};
contains(A,B)
console.log(B)    // {a:3}

即当实参为对象时,函数内部是可以改变该对象的值从而影响函数之外的实参。

但是测试另外一段代码,发现和理解的不一样:

function contains(root, n) {
  if(n) {
    n = {a:3}
  }
}

const A = {a:1};
const B = {a:2}
contains(A,B)
console.log(B) // {a:2}

n.a = 3n = {a:3} 这两段代码是不一样的。

网上也有相关资料,其实可以简单的理解为: 当函数一开始执行时,n 是指向实参 B 的一个引用.

n.a = 3 是在引用上关联了一个属性,此时和 B 还是同一个引用,因此会改变实参B的值。

n = {a:3} 则使得 n 不再指向实参 B, 而是指向一个新对象{a:3},也就是 nB 彻底断绝了关系,因此不会改变实参 B 的值。

是不是可以给蚂蚁的团队提个issue建议删除该代码,不过有这句代码也不会有什么bug 该行代码可能是为了符合 eslint 规范: 不对函数的参数进行赋值. 链接

2. 相关资料

JavaScript深入之参数按值传递

Last updated