前言
受到前端大牛学长的点拨,开始新人小白的前端博客之旅,请大家多多指正与关照。
此文是对《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,二者独立运行,互相无法干扰
(栈中为字符串引用地址,实际变量存储在堆地址中)
复制后的变量对象存储情况 | |
---|---|
str1 | 4399 |
str2 | 4399 |
而将引用值从一个变量赋值给另外一个变量时的情况就完全不同了,存储在变量中的值虽然也会被赋值到新变量所在位置,所不同的是,此处复制的只是一个指针,它们都指向堆内存中的同一个对象,因此只要对任意一个指向该对象的变量进行修改,其它指向该对象的变量都会反映出变化。
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); // 笑嘻嘻
图解:
传递参数
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仍然是前端菜鸟
而非好恶发
这是因为在第一次传参中,内部变量仍然可以通过访问按值传递的对象指针,指向存储在全局作用域的堆内存
我们在第二次传参中,创建了一个指向内部作用域的变量,原始引用依然存在,如果函数是按引用传参,那么外部对象,内部对象都应该指向这个位于内部作用域的对象,显示的结果也应该为好恶发
请看图解:
第一次传参后:
第二次传参后:
它们完全成为了相互独立的两个对象,其中内部对象在函数执行完毕之后,就会被销毁
确定类型
相信大家都经常使用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