JavaScript权威指南学习笔记(一)

本文深入探讨JavaScript中的变量类型,包括基础变量与引用变量的区别,以及它们如何影响内存管理。文章详细解释了按共享传递的概念,并通过实例展示了如何确定变量类型及作用域的工作原理。

变量

  1. Primitive Values(基础变量):null,undefined, String,Number,Boolean
  2. Reference Values(引用变量):Object that made up of multiple values(就是一种由许多变量组成的对象)
  3. 基础变量:accessed by value,即直接操作是储存在变量中的值
  4. 引用变量:因为他stored in memory中,但是JavaScript又不允许直接操作内存,所以你操作object(对象)的时候,其实是在操作对它的引用(accessed by reference)。
    • 注:在很多语言中,字符串是object,但JavaScript打破了这条传统。

Dynamic Property(动态属性)

  • 只有引用型变量才能设置动态属性。
var person = new object();
person.name = "Carl";
alert(person.name);

Copying Values

//基础变量,对b进行任何操作不会对a产生影响
var a =1;
var b =a;
console.log(b);//1

//引用变量,y复制x,它们指向同一堆内存
var x =new Object();
var y =x;
y.height=8;
console.log(x.height);//8

JS是按值传递还是按引用传递

原文链接

在分析这个问题之前,我们需了解什么是按值传递(call by value),什么是按引用传递(call by reference)。在计算机科学里,这个部分叫求值策略(Evaluation Strategy)。它决定变量之间、函数调用时实参和形参之间值是如何传递的。

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

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

按引用传递会使函数调用的追踪更加困难,有时也会引起一些微妙的BUG。

按值传递由于每次都需要克隆副本,对一些复杂类型,性能较低。两种传值方式都有各自的问题。

我们先看一个C的例子来了解按值和引用传递的区别:
void Modify(int p, int * q)
{
    p = 27; // 按值传递 - p是实参a的副本, 只有p被修改
    *q = 27; // q是b的引用,q和b都被修改
}
int main()
{
    int a = 1;
    int b = 1;
    Modify(a, &b);   // a 按值传递, b 按引用传递,
                     // a 未变化, b 改变了
    return(0);
}

这里我们可以看到:

a => p按值传递时,修改形参p的值并不影响实参a,p只是a的副本。
b => q是按引用传递,修改形参q的值时也影响到了实参b的值。

探究JS值的传递方式

JS的基本类型,是按值传递的。

var a = 1;
function foo(x) {
    x = 2;
}
foo(a);
console.log(a); // 仍为1, 未受x = 2赋值所影响

再来看对象:

var obj = {x : 1};
function foo(o) {
    o.x = 3;
}
foo(obj);
console.log(obj.x); // 3, 被修改了!

说明o和obj是同一个对象,o不是obj的副本。所以不是按值传递。 但这样是否说明JS的对象是按引用传递的呢?我们再看下面的例子:

var obj = {x : 1};
function foo(o) {
    o = 100;
}
foo(obj);
console.log(obj.x); // 仍然是1, obj并未被修改为100.

如果是按引用传递,修改形参o的值,应该影响到实参才对。但这里修改o的值并未影响obj。 因此JS中的对象并不是按引用传递。那么究竟对象的值在JS中如何传递的呢?

按共享传递 call by sharing
准确的说,JS中的基本类型按值传递,对象类型按共享传递的(call by sharing,也叫按对象传递、按对象共享传递)。最早由Barbara Liskov. 在1974年的GLU语言中提出。该求值策略被用于Python、Java、Ruby、JS等多种语言。

该策略的重点是:调用函数传参时,函数接受对象实参引用的副本(既不是按值传递的对象副本,也不是按引用传递的隐式引用)。 它和按引用传递的不同在于:在共享传递中对函数形参的赋值,不会影响实参的值。如下面例子中,不可以通过修改形参o的值,来修改obj的值。

var obj = {x : 1};
function foo(o) {
    o = 100;
}
foo(obj);
console.log(obj.x); // 仍然是1, obj并未被修改为100.

然而,虽然引用是副本,引用的对象是相同的。它们共享相同的对象,所以修改形参对象的属性值,也会影响到实参的属性值。

var obj = {x : 1};
function foo(o) {
    o.x = 3;
}
foo(obj);
console.log(obj.x); // 3, 被修改了!

对于对象类型,由于对象是可变(mutable)的,修改对象本身会影响到共享这个对象的引用和引用副本。而对于基本类型,由于它们都是不可变的(immutable),按共享传递与按值传递(call by value)没有任何区别,所以说JS基本类型既符合按值传递,也符合按共享传递。

var a = 1; // 1是number类型,不可变 
var b = a; 
b = 6;

据按共享传递的求值策略,a和b是两个不同的引用(b是a的引用副本),但引用相同的值。由于这里的基本类型数字1不可变,所以这里说按值传递、按共享传递没有任何区别。

基本类型的不可变(immutable)性质
基本类型是不可变的(immutable),只有对象是可变的(mutable). 例如数字值100, 布尔值true, false,修改这些值(例如把1变成3, 把true变成100)并没有什么意义。比较容易误解的,是JS中的string。有时我们会尝试“改变”字符串的内容,但在JS中,任何看似对string值的”修改”操作,实际都是创建新的string值。

var str = "abc";
str[0]; // "a"
str[0] = "d";
str; // 仍然是"abc";赋值是无效的。没有任何办法修改字符串的内容

而对象就不一样了,对象是可变的。

var obj = {x : 1};
obj.x = 100;
var o = obj;
o.x = 1;
obj.x; // 1, 被修改
o = true;
obj.x; // 1, 不会因o = true改变

这里定义变量obj,值是object,然后设置obj.x属性的值为100。而后定义另一个变量o,值仍然是这个object对象,此时obj和o两个变量的值指向同一个对象(共享同一个对象的引用)。所以修改对象的内容,对obj和o都有影响。但对象并非按引用传递,通过o = true修改了o的值,不会影响obj。

所以可以说JS既不是按值传递也不是按引用传递,而是按共享传递。

Determing Type

利用typeof:

var a = "123";
var b = 123;
var c = true;
var d;
var e = null;
var f = new Object;
console.log(typeof(a));//string
console.log(typeof(b));//number
console.log(typeof(c));//boolean
console.log(typeof(d));//undefined
console.log(typeof(e));//object
console.log(typeof(f));//object

如果某一个变量是某一类型实例的话,执行instanceof操作符就会返回true

alert(person instanceof Object) //对象
alert(array instanceof Array) //数组
alert(pattern instanceof RegExp) //正则
  • 所有引用变量执行instanceof都会返回true,
  • 所有基础变量执行instanceof都会返回false。

执行上下文和作用域

  • 每一个可执行上下文都有一个相关联的variable object,用来存放它定义的变量和函数。这个object无法通过代码获取,但是在各个场景后面处理数据。

  • 全局执行上下文(global execution context)是最重要的一个。在网络浏览器中,全局上下文是 window object,全局的变量和函数都相当于是window这个对象的属性和方法。网页或浏览器关闭时,全局执行上下文结束。

  • 每一个函数都有它自己的执行上下文(execution context)。当执行到某一个函数时,此函数的execution context被压入context栈。当此函数执行结束时,函数的context被栈弹出,将控制权返还给压栈前的执行上下文环境。这种control execution flow贯穿整个JavaScript程序。

扩大作用域链

  • 基本作用域有两种,global和function(第三种在调用eval()时)
    * 扩大作用域链方式:with和try catch中的catch区块(block),它们都在作用域链的前段添加了一个variable object

没有块(block)级别作用域

for(var i=0;i<10;i++){
    console.log(i);
}
console.log(i);//10
//“函数级”作用域
function adds(A,B){
    var i = A+B;
    return i;
}
var result = adds(3,10);//13
console.log(i);//error

标识符(JavaScript Identifier)查询

查询从scope chain的起始开始,如果从local context查到,停止查询,如果没查到会按照scope chain继续查询(这里包含各种原型链(prototype chain)),这个过程一直持续到global context,如果没有查到,就是这个Identfier没有被声明。

var color = "red";
function getColor(){
    return color;
}
console.log(getColor());

标识符查询


垃圾回收

  • JavaScript内存自动管理

Mark and Sweep

当变量出现(comes into)在上下文,比如在函数中声明一个变量,它就会因为在这个上下文(context)里而被标记;逻辑上,在函数执行的过程中变量的内存是不会被释放的;当上下文结束(goes out of context)时,变量同样会因为上下文结束而被标记。

当garbage collector启动时,它标记了所有储存在内存中的变量,然后它会删除那些在context中的变量以及它们所引用的那些变量的标记,然后标记那些要被删除的变量,因为它们无法通过context中的变量获取了。最后garbage collector就删除这些标记了的变量,从而把它们所占用的内存重新释放。

Reference Counting

a serious issue: circular references

function problem(){
    var objectA = new Object();
    var objectB = new Object();
    objectA.someOtherObject = objectB;//reference count 2
    objectB.anotherObject = objectA;//reference count 2, can't release memory.
}
  • Mark and Sweep is better now.

Managing Memory

如果某一数据不再使用,最好把它设置为null,释放引用(dereferencing a value)。这条建议通常针对全局对象和它的属性,局部变量不用担心,当context结束,局部变量会自动被释放。

function A(name){
    var k =new Object();
    k.name=name
    return k;
}   
var globalOne = A("test");//A函数执行完,k自动释放
globalOne=null;//手动释放
  • dereferencing a value并不是自动回收它所关联的内存,关键是确保变量已经离开context,从而在下次garbage collector 启动时,内存被回收。

总结

JavaScript变量有两种类型:基础变量 引用变量

  • 基础变量包括:null undefined number string boolean
  • 基础变量大小固定,存储在栈上
  • 拷贝基础变量就是创建了这个变量的备份
  • 引用变量是对象,存储在堆(heap)上
  • 一个包含引用值的变量实际上只是包含了指向这个对象(object)的指针,而不是对象本身。
  • 拷贝另一个对象的引用只是拷贝了指针,所以两个引用都指向同一个对象。
  • typeof 判断基础变量的类型,instanceof 判断引用变量类型

所有变量,不管基础的,引用的都存在于执行上下文,执行上下文决定了变量的生命周期,并且部分代码能够访问它

  • 可执行上下文存在全局的和函数中的两种
  • 每次进入执行上下文时,它都会创建一个用于搜寻变量和函数的作用域链。
  • 函数的本地上下文可以访问本地变量,包含它的上下文和全局上下文。
  • 全局上下文只能访问全局的函数和变量,不能访问局部的上下文中的任何数据。
  • 可执行上下文中的变量有助于决定什么时候释放内存。

JavaScript是一个垃圾回收编程环境,开发者无需关心内存分配和回收。

  • 变量离开作用域(go out of scope)会自动被标记为待回收的变量,在下次垃圾回收器执行的时候被删除。
  • 占主导地位的垃圾回收算法是标记删除机制
  • 另一种是计数器算法,除了i.e.浏览器以外很少用了。问题出在循环引用。
  • 而标记删除算法能完美解决这种问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值