JavaScript中的变量、作用域、内存问题和基本包装类型

快来打我* 2022-12-02 01:09 25阅读 0赞

1、变量及作用域

JavaScript 的变量与其他语言的变量有很大区别。JavaScript 变量是松散型的(不强制类型)本质,决定了它只是在特定时间用于保存特定值的一个名字而已。由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变。

基本类型和引用类型的值

ECMAScript 变量可能包含两种不同的数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置 。而引用类型值则是指那些保存在堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
将一个值赋给变量时,解析器必须确定这个值是基本类型值,还是引用类型值。基本类型值有以下几种:Undefined、Null、Boolean、Number 和 String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。
在某些语言中,字符串以对象的形式来表示,因此被认为是引用类型。ECMAScript 放弃这一传统。
如果赋值的是引用类型的值,则必须在堆内存中为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址大小的固定的,因此可以将内存地址保存在栈内存中。这样,当查询引用类型的变量时,先从栈中读取内存地址,然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dvbmd4aWZhY2FpX2JlbGlldmU_size_16_color_FFFFFF_t_70_pic_center

动态属性

定义基本类型值和引用类型值的方式是相似的:创建一个变量并为该变量赋值。但是 ,当这个值保存到变量中以后,对不同类型值可以执行的操作则大相径庭。

  1. var box = new Object(); // 创建引用类型
  2. box.name = 'Lee'; // 新增一个属性
  3. alert(box.name); // 输出

如果是基本类型的值添加属性的话,就会出现问题了。

  1. var box = 'Lee'; // 创建一个基本类型
  2. box.age = 27; // 给基本类型添加属性
  3. alert(box.age); // undefined

复制变量值

在变量复制方面,基本类型和引用类型也有所不同。基本类型复制的是值本身,而引用类型复制的是地址。

  1. var box = 'Lee'; // 在栈内存生成一个 box 'Lee'
  2. var box2 = box; // 在栈内存再生成一个 box2 'Lee'

20200828215136475.png_pic_center
box2 是虽然是 box1 的一个副本,但从图示可以看出,它是完全独立的。也就是说,两个变量分别操作时互不影响。

  1. var box = new Object(); // 创建一个引用类型
  2. box.name = 'Lee'; // 新增一个属性
  3. var box2 = box; // 把引用地址赋值给 box2

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dvbmd4aWZhY2FpX2JlbGlldmU_size_16_color_FFFFFF_t_70_pic_center 1
在引用类型中,box2 其实就是 box,因为他们指向的是同一个对象。如果这个对象中的 name 属性被修改了,box2.name 和 box.name 输出的值都会被相应修改掉了。

传递参数

ECMAScript 中所有函数的参数都是按值传递的,言下之意就是说,参数不会按引用传递,虽然变量有基本类型和引用类型之分。

  1. function box(num) { // 按值传递,传递的参数是基本类型
  2. num += 10; // 这里的 num 是局部变量,全局无效
  3. return num;
  4. }
  5. var num = 50;
  6. var result = box(num);
  7. alert(result); // 60
  8. alert(num); // 50

以上的代码中,传递的参数是一个基本类型的值。而函数里的 num 是一个局部变量,和外面的 num 没有任何联系。
下面给出一个参数作为引用类型的例子。

  1. function box(obj) { // 按值传递,传递的参数是引用类型
  2. obj.name = 'Lee';
  3. }
  4. var p = new Object();
  5. box(p);
  6. alert(p.name);

如果存在按引用传递的话,那么函数里的那个变量将会是全局变量,在外部也可以访问。比如 PHP 中,必须在参数前面加上&符号表示按引用传递。而 ECMAScript 没有这些,只能是局部变量。可以在 PHP 中了解一下。所以按引用传递和传递引用类型是两个不同的概念。

  1. function box(obj) {
  2. obj.name = 'Lee';
  3. var obj = new Object(); // 函数内部又创建了一个对象
  4. obj.name = 'Mr.'; // 并没有替换掉原来的 obj
  5. }

最后得出结论,ECMAScript 函数的参数都将是局部变量,也就是说,没有按引用传递。

检测类型

要检测一个变量的类型,我们可以通过 typeof 运算符来判别。诸如:

  1. var box = 'Lee';
  2. alert(typeof box); // string

虽然 typeof 运算符在检查基本数据类型的时候非常好用,但检测引用类型的时候,它就不是那么好用了。通常,我们并不想知道它是不是对象,而是想知道它到底是什么类型的对象。因为数组也是 object,null 也是 Object 等等。这时我们应该采用 instanceof 运算符来查看。

  1. var box = [1,2,3];
  2. alert(box instanceofArray); // 是否是数组
  3. var box2 = {};
  4. alert(box2 instanceof Object); // 是否是对象
  5. var box3 = /g/;
  6. alert(box3 instanceof RegExp); // 是否是正则表达式
  7. var box4 = new String('Lee');
  8. alert(box4 instanceof String); // 是否是字符串对象

当使用 instanceof 检查基本类型的值时,它会返回 false。

执行环境及作用域

执行环境是 JavaScript 中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
全局执行环境是最外围的执行环境。在 Web 浏览器中,全局执行环境被认为是 window 对象。因此所有的全局变量和函数都是作为 window 对象的属性和方法创建的。

  1. var box = 'blue'; // 声明一个全局变量
  2. function setBox() {
  3. alert(box); // 全局变量可以在函数里访问
  4. }
  5. setBox(); // 执行函数

全局的变量和函数,都是 window 对象的属性和方法。

  1. var box = 'blue';
  2. function setBox() {
  3. alert(window.box); // 全局变量即 window 的属性
  4. }
  5. window.setBox(); // 全局函数即 window 的方法

当执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。如果是全局环境下,需要程序执行完毕,或者网页被关闭才会销毁。
每个执行环境都有一个与之关联的变量对象,就好比全局的 window 可以调用变量和属性一样。局部的环境也有一个类似 window 的变量对象,环境中定义的所有变量和函数都保存在这个对象中。(我们无法访问这个变量对象,但解析器会处理数据时后台使用它)函数里的局部作用域里的变量替换全局变量,但作用域仅限在函数体内这个局部环境。

  1. var box = 'blue';
  2. function setBox() {
  3. var box = 'red'; // 这里是局部变量,出来就不认识了
  4. alert(box);
  5. }
  6. setBox();
  7. alert(box);

通过传参,可以替换函数体内的局部变量,但作用域仅限在函数体内这个局部环境。

  1. var box = 'blue';
  2. function setBox(box) { // 通过传参,替换了全局变量
  3. alert(box);
  4. }
  5. setBox('red');
  6. alert(box);

函数体内还包含着函数,只有这个函数才可以访问内一层的函数。

  1. var box = 'blue';
  2. function setBox() {
  3. function setColor() {
  4. var b = 'orange';
  5. alert(box);
  6. alert(b);
  7. }
  8. setColor(); // setColor()的执行环境在 setBox()内
  9. }
  10. setBox();

每个函数被调用时都会创建自己的执行环境。当执行到这个函数时,函数的环境就会被推到环境栈中去执行,而执行后又在环境栈中弹出(退出),把控制权交给上一级的执行环境。
当代码在一个环境中执行时,就会形成一种叫做作用域链的东西。它的用途是保证对执行环境中有访问权限的变量和函数进行有序访问。作用域链的前端,就是执行环境的变量对象。
20200828215402529.png_pic_center

没有块级作用域

块级作用域表示诸如 if 语句等有花括号封闭的代码块,所以,支持条件判断来定义变量。

  1. if (true) { // if 语句代码块没有局部作用域
  2. var box = 'Lee';
  3. }
  4. alert(box);

for 循环语句也是如此。

  1. for (var i = 0; i < 10; i ++) { // 没有局部作用域
  2. var box = 'Lee';
  3. }
  4. alert(i);
  5. alert(box);

var 关键字在函数里的区别:

  1. function box(num1, num2) {
  2. var sum = num1 + num2; // 如果去掉 var 就是全局变量了
  3. return sum;
  4. }
  5. alert(box(10,10));
  6. alert(sum); // 报错

非常不建议不使用 var 就初始化变量,因为这种方法会导致各种意外发生。所以初始化变量的时候一定要加上 var。一般确定变量都是通过搜索来确定该标识符实际代表什么。

  1. var box = 'blue';
  2. function getBox() {
  3. return box; // 代表全局 box
  4. } // 如果加上函数体内加上 var box = 'red'
  5. alert(getBox()); // 那么最后返回值就是 red

20200828215448959.png_pic_center
变量查询中,访问局部变量要比全局变量更快,因为不需要向上搜索作用域链。

2、内存问题

JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。其他语言比如 C 和 C++,必须手工跟踪内存使用情况,适时的释放,否则会造成很多问题。而 JavaScript 则不需要这样,它会自行管理内存分配及无用内存的回收。
JavaScript 最常用的垃圾收集方式是标记清除。垃圾收集器会在运行的时候给存储在内存中的变量加上标记。然后,它会去掉环境中正在使用变量的标记,而没有被去掉标记的变量将被视为准备删除的变量。最后,垃圾收集器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。
垃圾收集器是周期性运行的,这样会导致整个程序的性能问题。比如 IE7 以前的版本 ,它的垃圾收集器是根据内存分配量运行的,比如 256 个变量就开始运行垃圾收集器,这样 ,就不得不频繁地运行,从而降低的性能。
一般来说,确保占用最少的内存可以让页面获得更好的性能。那么优化内存的最佳方案 ,就是一旦数据不再有用,那么将其设置为 null 来释放引用,这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象。

  1. var o = {
  2. name : 'Lee'
  3. };
  4. o = null; // 解除对象引用,等待垃圾收集器回收

3、基本包装类型

为了便于操作基本类型值,ECMAScript 提供了 3 个特殊的引用类型:Boolean、Number 和 String。这些类型与其他引用类型相似,但同时也具有与各自的基本类型相应的特殊行为 。实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象 ,从而能够调用一些方法来操作这些数据。

基本包装类型概述

  1. var box = 'Mr. Lee'; // 定义一个字符串
  2. var box2 = box.substring(2); // 截掉字符串前两位
  3. alert(box2); // 输出新字符串

变量 box 是一个字符串类型,而 box.substring(2)又说明它是一个对象。只有对象才会调用方法,最后把处理结果赋值给 box2。’Mr. Lee’是一个字符串类型的值,按道理它不应该是对象,不应该会有自己的方法,比如:

  1. alert('Mr. Lee'.substring(2)); // 直接通过值来调用方法
  1. 字面量写法:

    var box = ‘Mr. Lee’; // 字面量
    box.name = ‘Lee’; // 无效属性
    box.age = function () { // 无效方法

    1. return 100;

    };
    alert(box); // Mr. Lee
    alert(box.substring(2)); // . Lee
    alert(typeof box); // string
    alert(box.name); // undefined
    alert(box.age()); // 错误

  2. new 运算符写法:

    var box = new String(‘Mr. Lee’); // new 运算符
    box.name = ‘Lee’; // 有效属性
    box.age = function () { // 有效方法

    1. return 100;

    };
    alert(box); // Mr. Lee
    alert(box.substring(2)); // . Lee
    alert(typeof box); // object
    alert(box.name); // Lee
    alert(box.age()); // 100

以上字面量声明和 new 运算符声明很好的展示了他们之间的区别。但有一定还是可以肯定的,那就是不管字面量形式还是 new 运算符形式,都可以使用它的内置方法。并且 Boolean 和 Number 特性与 String 相同,三种类型可以成为基本包装类型。在使用 new 运算符创建以上三种类型的对象时,可以给自己添加属性和方法,但我们建议不要这样使用,因为这样会导致根本分不清到底是基本类型值还是引用类型值。

Boolean 类型

Boolean 类型没有特定的属性或者方法。

Number 类型

Number 类型有一些静态属性(直接通过 Number 调用的属性,而无须 new 运算符)和方法。
Number 静态属性


































属性 描述
MAX_VALUE 表示最大数
MIN_VALUE 表示最小值
NaN 非数值
NEGATIVE_INFINITY 负无穷大,溢出返回该值
POSITIVE_INFINITY 无穷大,溢出返回该值
prototype 原型,用于增加新属性和方法

Number 对象的方法






























方法 描述
toString() 将数值转化为字符串,并且可以转换进制
toLocaleString() 根据本地数字格式转换为字符串
toFixed() 将数字保留小数点后指定位数并转化为字符串
toExponential() 将数字以指数形式表示,保留小数点后指定位数并转化为字符串
toPrecision() 指数形式或点形式表述数,保留小数点后面指定位数并转化为字符串
  1. var box = 1000.789;
  2. alert(box.toString()); // 转换为字符串,传参可以转换进制
  3. alert(box.toLocaleString()); // 本地形式,1,000.789
  4. alert(box.toFixed(2)); // 小数点保留,1000.78
  5. alert(box.toExponential()); // 指数形式,传参会保留小数点
  6. alert(box.toPrecision(3)); // 指数或点形式,传参保留小数点

String 类型

String 类型包含了三个属性和大量的可用内置方法。
String 对象属性






















属性 描述
length 返回字符串的字符长度
constructor 返回创建 String 对象的函数
prototype 通过添加属性和方法扩展字符串定义

String也包含对象的通用方法,比如 valueOf()、toLocaleString()和 toString()方法,但这些方法都返回字符串的基本值。
字符方法


















方法 描述
charAt(n) 返回指定索引位置的字符
charCodeAt(n) 以 Unicode 编码形式返回指定索引位置的字符
  1. var box = 'Mr.Lee';
  2. alert(box.charAt(1)); // r
  3. alert(box.charCodeAt(1)); // 114
  4. alert(box[1]); // r,通过数组方式截取

box[1]在 IE 浏览器会显示 undefined,所以使用时要慎重。
字符串操作方法


























方法 描述
concat(str1…str2) 将字符串参数串联到调用该方法的字符串
slice(n,m) 返回字符串 n 到 m 之间位置的字符串
substring(n,m) 同上
substr(n,m) 返回字符串 n 开始的 m 个字符串
  1. var box = 'Mr.Lee';
  2. alert(box.concat(' is ', ' Teacher ', '!')); // Mr.Lee is Teacher !
  3. alert(box.slice(3)); // Lee
  4. alert(box.slice(3,5)); // Le
  5. alert(box.substring(3)); // Lee
  6. alert(box.substring(3,5)); // Le
  7. alert(box.substr(3)); // Lee
  8. alert(box.substr(3,5)); // Lee
  9. var box = 'Mr.Lee';
  10. alert(box.slice(-3)); // Lee,6+(-3)=3 位开始
  11. alert(box.substring(-3)); // Mr.Lee 负数返回全部
  12. alert(box.substr(-3)); // Lee,6+(-3)=3 位开始
  13. var box = 'Mr.Lee';
  14. alert(box.slice(3, -1)); // Le 6+(-1)=5, (3,5)
  15. alert(box.substring(3, -1)); // Mr. 第二参为负,直接转 0,并且方法会把较小的数字提前,(0,3)
  16. alert(box.substr(3, -1)); // '' 第二参数为负,直接转 0 ,(3,0)

IE 的 JavaScript 实现在处理向 substr()方法传递负值的情况下存在问题,它会返回原始字符串,使用时要切记。
字符串位置方法


















方法 描述
indexOf(str, n) 从 n 开始搜索的第一个 str,并将搜索的索引值返回
lastIndexOf(str, n) 从 n 开始搜索的最后一个 str,并将搜索的索引值返回
  1. var box = 'Mr.Lee is Lee';
  2. alert(box.indexOf('L')); // 3
  3. alert(box.indexOf('L', 5)); // 10
  4. alert(box.lastIndexOf('L')); // 10
  5. alert(box.lastIndexOf('L', 5)); // 3,从指定的位置向前搜索

如果没有找到想要的字符串,则返回-1。
示例:找出全部的 L。

  1. var box = 'Mr.Lee is Lee'; // 包含两个 L 的字符串
  2. var boxarr = []; // 存放 L 位置的数组
  3. var pos = box.indexOf('L'); // 先获取第一个 L 的位置
  4. while (pos > -1) { // 如果位置大于-1,说明还存在 L
  5. boxarr.push(pos); // 添加到数组
  6. pos = box.indexOf('L', pos + 1); // 从新赋值 pos 目前的位置
  7. }
  8. alert(boxarr); // 输出

大小写转换方法


























方法 描述
toLowerCase(str) 将字符串全部转换为小写
toUpperCase(str) 将字符串全部转换为大写
toLocaleLowerCase(str) 将字符串全部转换为小写,并且本地化
toLocaleupperCase(str) 将字符串全部转换为大写,并且本地化
  1. var box = 'Mr.Lee is Lee';
  2. alert(box.toLowerCase()); // 全部小写
  3. alert(box.toUpperCase()); // 全部大写
  4. alert(box.toLocaleLowerCase()); //
  5. alert(box.toLocaleUpperCase()); //

只有几种语言(如土耳其语)具有地方特有的大小写本地性,一般来说,是否本地化效果都是一致的。
字符串的模式匹配方法


























方法 描述
match(pattern) 返回 pattern 中的子串或 null
replace(pattern, replacement) 用 replacement 替换 pattern
search(pattern) 返回字符串中 pattern 开始位置
split(pattern) 返回字符串按指定 pattern 拆分的数组

以上的 match()、replace()、serach()、split()在普通字符串中也可以使用。

  1. var box = 'Mr.Lee is Lee';
  2. alert(box.match('L')); // 找到 L,返回 L 否则返回 null
  3. alert(box.search('L')); // 找到 L 的位置,和 indexOf 类型
  4. alert(box.replace('L', 'Q')); // 把 L 替换成 Q
  5. alert(box.split(' ')); // 以空格分割成字符串

其他方法


















方法 描述
fromCharCode(ascii) 静态方法,输出 Ascii 码对应值
localeCompare(str1,str2) 比较两个字符串,并返回相应的值
  1. alert(String.fromCharCode(76)); // L,输出 ASCII 码对应值

localeCompare(str1,str2)方法详解:比较两个字符串并返回以下值中的一个:

  1. 如果字符串在字母表中应该排在字符串参数之前,则返回一个负数(多数-1)。
  2. 如果字符串等于字符串参数,则返回 0。
  3. 如果字符串在自附表中应该排在字符串参数之后,则返回一个正数(多数 1)。

    var box = ‘Lee’;
    alert(box.localeCompare(‘apple’)); // 1
    alert(box.localeCompare(‘Lee’)); // 0
    alert(box.localeCompare(‘zoo’)); // -1

HTML 方法






























































方法 描述
anchor(name) <a name=”name”>str</a>
big() <big>str</big>
blink() <blink>str</blink>
bold() <b>Str</b>
fixed() <tt>Str</tt>
fontcolor(color) <font color=”color”>str</font>
fontsize(size) <font size=”size”>str</font>
link(URL) <a href=”URL”>str</a>
small() <small>str</small>
strike() <strike>str</strike>
italics() <i>italics</i>
sub() <sub>str</sub>
sup() <sup>str</sup>

以上是通过 JS 生成一个 html 标签。

  1. var box = 'Lee'; //
  2. alert(box.link('http://www.yc60.com')); // 超链接

发表评论

表情:
评论列表 (有 0 条评论,25人围观)

还没有评论,来说两句吧...

相关阅读

    相关 javaScript变量作用

    作用域是程序源代码中定义变量的区域。 作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。 JavaScript采用词法作用域(lexical scopin

    相关 变量作用内存问题

    变量作用域和内存问题 1.基本类型和引用类型的值 基本类型就是简单的数据段(5种值类型),而引用类型就是对象(操控对象的引用)。 1.1复制变量值 引用类型实际上在复制...