前端小白红宝书之旅:变量、作用域与内存(一)

前言

在这里插入图片描述

受到前端大牛学长的点拨,开始新人小白的前端博客之旅,请大家多多指正与关照。

此文是对《JavaScript 高级程序设计》一书学习笔记,希望能借此巩固js的基础和对一些核心概念有更深入的了解。

今天我们来看一看js中的原始值引用值,以及传递参数


原始值与引用值

ECMAScript变量可以包含两种不同类型的数据原始值和引用值。

原始值(primitive value)就是最简单的数据,引用值(reference value)则是由多个值构成的对象。

在把一个值赋给变量时,JavaScript 引擎必须确定这个值是原始值还是引用值。

JavaScript 引擎工作原理

  • 获取解释器中编译好的执行码
  • 浏览器预解析阶段一:收集变量
   var name = '前端菜鸟
   age = '好饿发'  // 两个变量都会被var定义,提升到当前作用域链的最前端
  let colors = new Array("red", "blue", "green")
  let person = new Object()
  person.name = '好饿发'
       function One(arr, obj) {
           console.log(arguments[0]);
           console.log(arguments[1]);
       }
       One(colors, person)   
       // arguments[0] : ["red", "blue", "green"]   
       // arguments[1] : obj.name = '好饿发'    
       // ES5中,函数内部存在一个特殊的对象:arguments
       // arguments是一个特殊的类数组对象,包含调用函数时传入的所有参数

JavaScript 引擎查找作用域中var声明的变量、arguments参数、function声明定义

  • 浏览器预解析阶段二:分号补全
     // JS执行是需要分号的,但为什么以下语句却可以正常运行呢
     console.log('前端小白')
     console.log('前端萌新')
     

预解析阶段为没有分号的语句,自动添加分号

  • JavaScript 引擎执行js代码

详见大神文章: https://www.cnblogs.com/caiyy/p/10509659.html

六种原始值类型:Undefined、Null、Boolean、Number、String和Symbol

Symbol 数据类型补充说明

  • ES6新增数据类型
  • 符号没有字面量语法,无法覆盖已经存在的对象属性
  • 符号类型不能用作构造函数吗,与关键字new关键字一起使用,这意味着无法像Boolean、String那样创建符号类型的包装对象,但是我们可以使用Object()函数进行强制包装
  • 符号类型示例唯一、不可变,用于创建唯一记号,进而用作非字符串形式的对象属性
     let a = "SAS"
        let genericsSymbol = Symbol(a)
        let otherGenericsSymbol = Symbol(a)
        console.log(genericsSymbol === otherGenericsSymbol); // False

保存原始值的变量是按值(byvalue)访问的,因为我们操作的就是存储在变量中的实际值。

引用值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。

在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身。为此,保存引用值的变量是按引用(byreference)访问的。

动态属性

原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋一个值。不过,在变量保存了这个值之后,可以对这个值做什么,则大有不同。对于引用值而言,可以随时添加、修改和删除其属性和方法。请看引用值示例:

let color = {
            name : 'red',
            id : 'AB20SD',
            101 : true 
        }
        console.log(color.name); // red

原始值不能拥有属性和方法,尽管在尝试给原始值添加属性时,代码仍然正常运行。

let name = "好恶发";
name.id = 4777;
console.log(name.id); // undefined

这是因为在实际执行代码中,创建的原始值包装对象只存在于访问它的那行代码执行期间。
这也是引用类型和原始值在包装类型上的主要区别,在通过new实例化引用类型之后,得到的实例在完全离开作用域的时候才会被销毁


我们也可以使用new关键字包装原始值,产生一个可以带有属性与对象的实例,但是此时该实例的类型属于Object,而非原始属性

let name1 = new String();
        name1.id = {
            a : "7k7k" ,
            url : "http://www.4399.com"
        };
        console.log(name1.id.a);    // 7k7k
        console.log(name1.id.url);  // http://www.4399.com

复制值

除了存储方式不同,原始值和引用值在通过变量复制时也有所不同。在通过变量把一个原始值爱到另一个变量时,原始值会被复制到新变量的位置。请看下面的例子:

// 两个变量的存储位置是否相同?
let str1 = '4399'
let str2 = str1

此时js会在栈空间开辟一个新的内存空间,用于存储变量st2,二者独立运行,互相无法干扰
(栈中为字符串引用地址,实际变量存储在堆地址中)

复制后的变量对象存储情况
str14399
str24399

而将引用值从一个变量赋值给另外一个变量时的情况就完全不同了,存储在变量中的值虽然也会被赋值到新变量所在位置,所不同的是,此处复制的只是一个指针,它们都指向堆内存中的同一个对象,因此只要对任意一个指向该对象的变量进行修改,其它指向该对象的变量都会反映出变化。

let one = new Object()
        one.name = '哇哈哈'
        let two = one
        console.log(one.name); // 哇哈哈
        console.log(two.name);  // 哇哈哈
        two.name = "笑嘻嘻"
        console.log(one.name); // 笑嘻嘻
        console.log(two.name); // 笑嘻嘻
 

图解:

2.png

传递参数

ECMAScript 中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样。

在按值传递参数时,值会被复制到一个局部变量(即一个命名参数,或者用ECMAScript的话说,就是arguments对象中的一个槽位)。

在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部(然而实际上,这种情况在ECMAScript中是不会被允许的)

let count1 = 50
        function sum(num) {
            num += 10;
            return num 
        }
        let count2 = sum(count1) 
        
        console.log(count2);   // 50
        console.log(count1);   // 60

相信函数中传递的为原始值时,大家都清楚这是按值传参,内部的变量不会对外部进行干扰

然而要是传递的是对象,会发生什么情况呢?

function setObject(obj) {
          obj.name = '前端菜鸟'
        }

        let person = new Object()
        setObject(person)
        console.log(person);   // 前端菜鸟

此时函数内部的对象的变化,明显传递到了函数外部

那么我们是否可以得出结论,当函数传参中,对象是按引用传递的呢?

实际上并不能,恰恰相反,函数传参中,对象仍然是按值传递的

下面我们来看一段代码

    function setObject(obj) {
          obj.name = '前端菜鸟'
          obj = new Object()
          obj.name = '好恶发'
        }

        let person = new Object()
        setObject(person)
        console.log(person.name);   // 前端菜鸟

为什么person.name仍然是前端菜鸟而非好恶发

这是因为在第一次传参中,内部变量仍然可以通过访问按值传递的对象指针,指向存储在全局作用域的堆内存

我们在第二次传参中,创建了一个指向内部作用域的变量,原始引用依然存在,如果函数是按引用传参,那么外部对象,内部对象都应该指向这个位于内部作用域的对象,显示的结果也应该为好恶发

请看图解:

第一次传参后:

3.png

第二次传参后:

4.png

它们完全成为了相互独立的两个对象,其中内部对象在函数执行完毕之后,就会被销毁

确定类型

相信大家都经常使用typeof操作符查询原始值变量类型

但是我们使用typeof操作符查询引用值时,只会显示Object,这显然达不到我们查询的目的。

因此我们可以使用instanceof操作符来查询引用值类型变量

let three = new Object(1,2)
        let four = new Object("sd")
        console.log(three instanceof Object);  // true
        console.log(three instanceof Array);   // false
        console.log(three instanceof RegExp);  // false
        console.log(three instanceof Object);  // true
        console.log(three instanceof Array);   // false
        console.log(three instanceof RegExp);  // false
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

好饿发归来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值