JavaScript 变量

来源:互联网 发布:zip如何安装mysql 编辑:程序博客网 时间:2024/06/13 17:41

JavaScript 变量

JavaScript 被称为弱类型语言,是指它的变量是无类型的,可以用来存储各种类型的 JavaScript 值, 同时它能够在以后任意时刻被重新赋值其它类型。

什么是变量(variable)

变量是内存里用来存储值的空间。每个变量都有唯一的内存地址。

基本类型变量

JavaScript 的值分为两大类,基本类型(primitive type) 和 对象(object)。这里的基本类型变量就是指存储基本类型值的变量, 注意变量是无类型的,这里给它杜撰了一个类别是为了避免歧义。

基本类型变量,其内存地址保存的值就是其被赋值时的值。基本类型的值是不可变的,要想改变基本类型变量中的值,实际上做的是给这个变量重新赋值:

var a = 1;// want to change it to another value? reassign it thena = 10;

对象变量

对象,也称为复合类型或引用类型,在这里使用引用类型的术语更形象一些。同上,对象变量就是引用对象值的变量。

注意我这没有定义它为存储对象值的变量,而是引用,因为实际上对象变量的内存地址存储的是能够引用这个对象值的“指针”。

对象的值是可变的,但是改变对象的值并不改变对象变量的值。这里再明确一点,对象变量保存的是指向对象值所在内存地址的信息,调用变量时可以通过这个信息获取这个对象值,我们这里可以假设对象变量的值是“地址类型”的值。同时还要明白一点,这里说的改变对象的值不是给对象变量重新赋值。

var objA = {  x: 1,  y: 2};var objB = objA;objA.x = 100;console.log(objA === objB); // still true

为什么会这样?因为我们使用 objA 和 objB 的时候,它们看似是对象,实则是“地址类型”的值,它们自身是两个不同内存空间保存着相同的“地址类型”值,js engine 是这样做的:

// what js engine does, in pseudo codevar objA = addressA -> {x: 1, y: 2}var objB = objA = addressA -> {x: 1, y: 2}// addressA, a mimic value type "地址类型"

这里,“地址类型”是杜撰的,但理解它很重要。

基本类型变量 vs 对象变量

相同的是,它们都是变量,并且都拥有唯一的内存地址,它们的内存地址不共享。因为,如果地址共享,以下情况不会发生:

// same, about reassignment// for primitivevar a = 1;var b = a;a = 10;console.log(a === b); // falseconsole.log(a);   // 10console.log(b);   // 1// for objectvar c = {x: 1, y: 2};var d = c;c = {x: 1, y: 2}; // 这里,有一个右值的概念,就是取值,直接量会在内存中新建一个空间来存储这个对象值,也就有了对应的新的“地址类型”值。// 因此,变量的内存地址没变(这一点是我目前的理解),而其存储的”地址类型”的值已变。console.log(c === d); // falseconsole.log(c);   // {x: 1, y: 2}console.log(d);   // {x: 1, y: 2}// 再说一次,它们看似对象且一样,实际“地址类型”的值不一样

不同的是,额,说到这,它们其实没有不同。任何变量之间都没有本质的不同,它们惟一的区别就是其内存中保存的值不同而已。我这里想说的不同,其实是变量重新赋值和值的改变之间的不同:

// not same, primitive type can't do this, object can, which is changing valuevar a = {x: 1, y: 2};var b = a;a.x = 100;console.log(a === b); // truea = {x: 100, y: 2};console.log(a === b); // false// nodejsexports = someValue;  // error, why?module.exports = someValue; // no error// prototype inheritfunction F() {};F.prototype = {/* some properties */};var f = new F();console.log(f.constructor === Object); // true, why not F ?

通过这些说明,我们应该能理解变量的一些本质。总结一下,变量具有唯一且不变的内存地址,要么直接存储基本类型的值,要么间接存储对象的值(实际存储“地址类型”的值, 它可以引用对象值)。

变量申明(variable declaration)

或者叫做变量定义,就是新建一个保存值的符号。

使用 var

这种方式最常用,使用 var 申明的变量会有以下特性:

自动成为全局对象的不可配置属性:

// no strict modevar a = 1;console.log(Object.getOwnPropertyDescriptor(this, "a"));// { value: 1, writable: true, enumerable: true, configurable: false  }

变量提升(hoisting), 只要申明了变量,任何情况下变量定义被提升到当前作用域顶部,被显示赋值之前拥有默认值 undefined

'use strict';   // no error even in strict modevar condition = Math.random() > 0.5 ? true : false;console.log(a);   // undefined /* 1. no error */if (condition) {  var a = 1;    // repeated declaration /* 2. no error again */} else {  var a = 2;}

可重复申明,上例已说明。

严格模式中规定变量必须先申明再赋值, 非严格模式未申明变量成为全局对象可配置属性, ES 标准规定(用了 must 一词)我们总是先申明变量再使用它。

function test() {  'use strict';  a = 10;  console.log(a); // error}// no strict modefunction f() {  a = 10;}f();console.log(Object.getOwnPropertyDescriptor(this, a));// { value: 10, writable: true, enumerable: true, configurable: true  }

使用 let

这是 ES6 新增的变量申明关键字。它的作用基本上跟 var 一样,都申明了一个变量,但是它有以下不同:
let 申明的变量不会成为全局对象的属性:

// no strict mode , for exmaple description purposevar a = 1;let b = 1;"a" in this;  // true"b" in this;  // false

没有变量提升,有“暂时性死区”(TDZ, temporary dead zone), 申明前使用报错。

console.log(a); // undefinedconsole.log(b); // errorvar a = 1;let b = 1;// another one for TDZfunction test(a = b, b) {}test(undefined, 1); // error

同一作用域内不可重复申明相同变量。

var a = 1;let a = 2;  // error// anotherlet a = 1;let a = 2;  // error

上面3条都是对使用 var 申明变量副作用的限制,其实 let 更重要的特性是它的块级作用域, 这个块级作用域分为两种,一种是语句块作用域,一种是语句作用域:

// 语句块作用域{  let a = 1;  console.log(a); // 1}console.log(a); // error// 语句作用域for (let a = 0; a < 10; a += 1) {  console.log(a); // 0, 1, 2 ... -> 9}// 这里, let 是在 {} 语句块之外定义的,那么它应该是个全局变量吧?console.log(a); // error /* wtf? */// 那么,for 语句头部定义的变量必须得是局部变量了, 那作用域是 {} ?for (let a = 1; a < 10; a += 1 + b) {  let b = 1;  console.log(a); // error /* wtf? */}// 那么,for 语句头部定义的变量就是作用域在头部内的变量,我称之为语句作用域。// {} 内部可以读取头部作用域的变量,反之不然, 这就相当于 header 是 body 的外部作用域, 这很奇妙

使用 const

const 也是 ES6 新增的关键字,使用 const 申明的变量所具有的特性几乎跟 let 申明的变量一样,除了以下不同:

const 申明的变量必须初始化,即申明时赋值。

const a;  // error// anotherconst a = 1;  // no error

const 申明的变量不能被重新赋值。回忆前面提到的重新赋值和值的改变之间的区别。

const a = 1;a = 2;const b = {x: 1};b.x = 10;   // no error /* 再再说一次,b 的“地址类型”值没变,变的是其引用的对象值, remember that?  */

块级作用域

块级作用域和函数作用域作为局部作用域的特性是一致的, 都遵循词法作用域。块级作用域相当于立即执行的函数表达式(IIFE), 因此其内部嵌套的函数拥有闭包的一切特性。

// varvar funcs = [];for (var i = 0; i < 10; i += 1) {  funcs.push((function(value) {    return function() {      return value;    }   })(i));}for (let f of funcs) {  console.log(f()); // 1, 2, ... -> 9}// letvar funcs = [];for (let i = 0; i < 10; i += 1) {  funcs.push(function() {    return i;  });}for (let f of funcs) {  console.log(f()); // 0, 1, 2, ... -> 9}
0 0
原创粉丝点击