javacript 是弱类型语言,定义的变量可以随意更改值的类型。

数据类型

名词汇总

  • 基本数据类型
  • 值类型
  • 原始数据类型
  • 引用数据类型
  • 复杂数据类型

类型判断

typeof

1
2
3
4
typeof xxx得到的值有以下几种类型:undefined boolean number string object  functionsymbol ,比较简单,不再一一演示了。这里需要注意的有三点:
typeof null结果是object ,实际这是typeof的一个bugnull是原始值,非引用类型
typeof [1, 2]结果是object,结果中没有array这一项,引用类型除了function其他的全部都是object
typeof Symbol() 用typeof获取symbol类型的值得到的是symbol,这是 ES6 新增的知识

instanceof
用于实例和构造函数的对应。例如判断一个变量是否是数组,使用 typeof 无法判断,但可以使用[1, 2] instanceof Array 来判断。因为,[1, 2]是数组,它的构造函数就是 Array。同理:

1
2
3
4
5
function Foo(name) {
this.name = name;
}
var foo = new Foo("bar");
console.log(foo instanceof Foo); // true

内存图


栈:原始数据类型(Undefined,Null,Boolean,Number、String)
堆:引用数据类型(对象、数组和函数)

两种类型的区别是:
存储位置不同;

  • 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
  • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

参数传递方式

  • 原始类型是按值传递
  • 引用类型是按共享传递

JS 中这种设计的原因是:按值传递的类型,复制一份存入栈内存,这类类型一般不占用太多内存,而且按值传递保证了其访问速度。按共享传递的类型,是复制其引用,而不是整个复制其值(C 语言中的指针),保证过大的对象等不会因为不停复制内容而造成内存的浪费。

值类型&引用类型

除了原始类型,ES 还有引用类型,上文提到的 typeof 识别出来的类型中,只有 object 和 function 是引用类型,其他都是值类型。
根据 JavaScript 中的变量类型传递方式,又分为值类型引用类型,值类型变量包括 Boolean、String、Number、Undefined、Null,引用类型包括了 Object 类的所有,如 Date、Array、Function 等。在参数传递方式上,值类型是按值传递,引用类型是按共享传递。
下面通过一个小题目,来看下两者的主要区别,以及实际开发中需要注意的地方。
自动检测
// 值类型
var a = 10
var b = a
b = 20
console.log(a) // 10
console.log(b) // 20
上述代码中,a b 都是值类型,两者分别修改赋值,相互之间没有任何影响。再看引用类型的例子:
自动检测
// 引用类型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a) // {x: 100, y: 200}
console.log(b) // {x: 100, y: 200}

上述代码中,a b 都是引用类型。在执行了 b = a 之后,修改 b 的属性值,a 的也跟着变化。因为 a 和 b 都是引用类型,指向了同一个内存地址,即两者引用的是同一个值,因此 b 修改属性时,a 的值随之改动。
再借助题目进一步讲解一下。

说出下面代码的执行结果,并分析其原因。
自动检测
function foo(a){
a = a * 10;
}
function bar(b){
b.value = ‘new’;
}
var a = 1;
var b = {value: ‘old’};
foo(a);
bar(b);
console.log(a); // 1
console.log(b); // value: new

通过代码执行,会发现:

  • a 的值没有发生改变
  • 而 b 的值发生了改变

这就是因为 Number 类型的 a 是按值传递的,而 Object 类型的 b 是按共享传递的。
JS 中这种设计的原因是:按值传递的类型,复制一份存入栈内存,这类类型一般不占用太多内存,而且按值传递保证了其访问速度。按共享传递的类型,是复制其引用,而不是整个复制其值(C 语言中的指针),保证过大的对象等不会因为不停复制内容而造成内存的浪费。

引用类型经常会在代码中按照下面的写法使用,或者说容易不知不觉中造成错误
自动检测
var obj = {
a: 1,
b: [1,2,3]
}
var a = obj.a
var b = obj.b
a = 2
b.push(4)
console.log(obj, a, b)

虽然 obj 本身是个引用类型的变量(对象),但是内部的 a 和 b 一个是值类型一个是引用类型,a 的赋值不会改变 obj.a,但是 b 的操作却会反映到 obj 对象上。

定义变量

es5 - 变量

1
2
3
4
5
6
// 通过 var 关键字来定义变量,变量的值可以随意更改(基本、引用)
var num = 0;
var str = "";
var arr = new Array();
var obj = new Object();
var fun = function () {};

es5 - 常量

1
2
3
// 没有确切定义常量的方式
// 可以将变量名全部大写来区分(约定俗成的认知)
var PI = 3.1415926;

es6

  • 通过关键字 let 定义变量
    • 没有变量提升
    • 不得在声明之前调用 (暂时性死区)
    • 通过 let 定义的变量,遇到有大括号的时候,会在这个大括号内,形成这个变量的封闭作用域
1
2
3
4
// 可以使用str来定义变量
// 也可以通过let来定义变量
var str = "";
let sub = "";
  • 通过关键字 const 定义常量
    • 没有变量提升
    • 不得修改值的类型
      • 基本数据类型不得重新赋值
      • 引用数据类型,不得重新赋值(不包括数组、对象的属性)
    • 不得在声明之前调用 (暂时性死区)
    • 通过 const 定义的变量,遇到有大括号的时候,会在这个大括号内,形成这个变量的封闭作用域
1
2
3
const obj = {
name: "gaocaipeng",
};

| 栈 | 堆 |

| |
| ——— | ——- |
| const obj | o1 ={ } |

|
| name gaocaipeng |

|

|

null&undefined 区别

null 表示一个对象被定义了,值为“空值”;
undefined 表示不存在这个值。

typeof undefined //“undefined”
undefined :是一个表示”无”的原始值或者说表示”缺少值”,就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined;
例如变量被声明了,但没有赋值时,就等于 undefined

typeof null //“object”
null : 是一个对象(空对象, 没有任何属性和方法);
例如作为函数的参数,表示该函数的参数不是对象;

注意:
在验证 null 时,一定要使用 === ,因为 == 无法分别 null 和 undefined
undefined 表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:
1)变量被声明了,但没有赋值时,就等于 undefined。 2) 调用函数时,应该提供的参数没有提供,该参数等于 undefined。
3)对象没有赋值的属性,该属性的值为 undefined。
4)函数没有返回值时,默认返回 undefined。

null 表示”没有对象”,即该处不应该有值。典型用法是:
1) 作为函数的参数,表示该函数的参数不是对象。
2) 作为对象原型链的终点。

变量交换

实现变量交换你有几种方式?

变量借用

1
2
3
4
5
var a = 1;
var b = 2;
var c = b;
b = a;
a = c;

解构交换

1
2
3
var a = 1;
var b = 2;
var [b, a] = [a, b];

运算交换

1
2
3
4
5
6
var a = 1;
var b = 2;
a = a + b; // a+b = 3
b = a - b; // a 1
a = a - b; // b 2
console.log(a, b);