js基础-变量的传递

最近发现一个问题很好玩,来跟大家分享一下。

  var a = {};

  funcutin b(a) {
    a.b = 1;
  }

  b(a);

  console.log(a);
复制代码

如果能够理解值传递和引用传递的同学,就知道打印出来的就是 { b: 1};

如果是下边这样的写法呢

  var a = {};

  function b(a) {
    a = 1;
  }

  b(a);

  console.log(a);

复制代码

此时打印出来是

如果改写成这样的:

  var a = {};

  function b(a) {
    a.b = 1;
    var a = 1;
  }

  b(a);

  console.log(a);

复制代码

此时打印出来是

这样的呢:

  var a = {};

  function b(a) {
    var a = 1;
    a.b = 1;
  }

  b(a);

  console.log(a);

复制代码

此时打印出来是

终极大招来临:

  var a = { b: 1 };

  function b(a) {
    var a;
    console.log(a.b);
    a.b = 3;
    a = { b: 2};
    console.log(a.b);
  }

  b(a);

  console.log(a.b);
复制代码

此时打印的是?

数据类型

在 javascript 中数据类型可以分为两类:

  • 原始数据类型值 Primitive type,比如Undefined,Null,Boolean,Number,String。

  • 引用类型值,也就是对象类型 Object type,比如Object,Array,Function,Date等。

值传递和引用传递

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

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

通俗来说就是值传递将整个变量都穿进去了,引用传递传入进去的是一个对象的副本(指针)

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。——百度百科

JS中保存基本类型的值和标识符在栈区,而引用类型的标识符和地址保存在栈区,值保存在堆内存中

简单举例

  var a = 1;
  var b = a;
  b = 2;
  console.log(a); // 1
  console.log(b); // 2


  var c = {};
  var d = c;
  c.name = '小周';
  console.log(c); // { name: '小周' }
  console.log(d); // { name: '小周' }
复制代码

接下来回到正题

变量的定义(宣告)和赋值

首选我们来看一段代码

  var a = 1;
  var a;
  console.log(a); // 1
复制代码

这里第二行对a是一个重复宣告,而不是赋值,变量只有定义(宣告)后未赋值的情况下才会输出undefined,除非手动赋值undefined;那么这里,JS引擎对于重复宣告的规定以最近的变量指定(也就是赋值)作为变量在执行时的值,所以第二行的var a;其实相当于无效;

函数中形参和局部变量同名

ps: 在我们自己写代码中,一般不会做这样的蠢事。

js对形参在变量对象中是如何保存的呢,请看规范:

10.5 Declaration Binding Instantiation Every execution context has an associated VariableEnvironment. Variables and functions declared in ECMAScript code evaluated in an execution context are added as bindings in that VariableEnvironment‘s Environment Record. For function code, parameters are also added as bindings to that Environment Record.

意思就是: 无论是形参还是函数中声明的变量,JS对他们的处理是没有区别的,都是保存在这个函数的变量对象中作为局部变量进行处理;结合下我们刚才说的同名宣告,下边的题就能读懂了

  function b(param) {
    var param;
    console.log(param); // 1
    param = 2;
    console.log(param); // 2
  }

  b(1);
复制代码

其实行参跟局部变量是同一个东西,都是保存在函数内部的变量。

变量是引用类型呢?

回到我们刚才终极大问题

  var a = { b: 1 };

  function b(a) {
    var a;
    console.log(a.b);
    a.b = 3;
    a = { b: 2};
    console.log(a.b);
  }

  b(a);
  console.log(a.b);
复制代码

整体分析一下

  1. 函数内部a重复宣告,所以第一次打印时 a.b 为1
  2. 接下来a.b = 3; 因为传递进来是引用类型,所以传递进来的是一个指针,那么这个引用指向的堆内存的那块空间里的b改变为3
  3. 接下来给a重新赋值,其实是实际上就是让它指向新开辟的空间,存放着{n:2}这个对象,那么之前的引用就断掉了,也就是说行参a已经不指向全局里那个a指向的空间了。所以,在函数中,会输出新空间里的a.b,就是2。
  4. 在函数外,a的指向还是旧空间里边的(相对于第三步来说).所以此时a.b就是3。

思考:

  1. a.b为何是1呢?函数内部通过哪种形式去访问外部的变量呢?
  2. 第三步当中的a是全局变量还是局部变量? 去掉var a呢?

此次分享只是单纯的发现如果去深入理解js,就会打开了一扇新世界的大门。随时欢迎大家来喝我们一起互相交流,一起打开代码新世界的大门。

补充:

  (function() {
    var a = {}
    function b(a) { // 函数声明的时候,叫做形参
      // 形参等同于一次声明
      a = 1;
    }
    b(a) // 此时的a是实参
    console.log(a) // {}
  })();
  (function() {
    //var a = 1;
    //var a;
    //==
    //var a;
    //var a;
    //a = 1;
    // 变量命名提升规则
    // 当对一个变量进行重复声明的时候,默认以最后一次
    // 有效声明为主
  })();
  (function () {
    var a = {};
    function b(a) {
      var a
      a.b = 1;
      var a = 1;
      console.log(a)
    }
    console.dir(b);
    b(a);

    console.log(a); // { b:1 }
  })();
  console.log('--------------------------');
  (function() {
    var a = {};

    function b(a) {
      var a = 1;
      a.b = 1;
      console.dir(a);
      function c() {}
      console.dir(c)
    }
    
    b(a);

    console.log(a);

  })()

  var a = {}
  // 对象的浅拷贝跟深拷贝
  // 浅拷贝
  // Object.assign(currentObj, targetObj)
  // { ...currentObj, ...targetObj}
  var b = {...a}
  b.c = 1;
  console.log(b, a)
  // 浅拷贝的适用范围 对象的所有的值都只能是基础数据类型
  // 深拷贝  _.clone()
复制代码
// 在es5当中 var 声明的是就是全局变量,global作用域等同于window对象
// 在es6中 let 和 const声明的在Script 作用域上,级别略低于全局作用域
// 这样就很好的解释了为什么window.a获取不到,但是a却能获取到
// 在函数初始化的时候,会将自己内部所有权限访问到的作用域都挂载到[[Scope]]上
// 每个函数都会有自身的[[Scope]]只读属性,这样的一个属性被我们称之为作用域链
// 跟script标签有关系
 let a = 1;
 const b = 2;
 var c = 3;
 console.log('window.d', window.d)
 function d() {

 }
 console.log('window.a', window.a)
 console.log('window.b', window.b)
 console.log('window.c', window.c)
 
 console.dir(d)
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值