声明、定义、引用、赋值

这几个名词经常出现 应该都很熟悉,但放在一起,你们会知道这其中的差别吗。

申明

申明:声明语句是本身什么也没有实现,只是通过创建变量和函数。查找栈内存,如果不存在创建的对象,则创建,并指向undefined。
var、function、let、const都是声明语句,var 和 let 是用来申明变量的,只是let是es6新增的命令。function 是用来定义函数的。const 是用来申明常量的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let 和 var 的区别
------ let --------
var a = [];
for (var i = 0; i < 10; i++){
a[i] = function () {
console.log(i);
}
}
a[6](); // 10
------ var -------
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
}
}
a[6](); // 6

定义

函数的定义和函数的声明

函数的定义表达式

1
var square = function (x) { return x * x; }

函数申明语句的语法

1
2
3
4
5
6
7
8
9
function functionName ([args1,[args2,[..., argsn]]]) {
statements;
}
例子:
function factorial(n) {
if (n <= 1) return n;
return n * factorial(n-1);
}

关于对象的引用赋值

引用赋值看起来好像很简单,但是背后还是有些东西不搞明白,还是很容易搞糊涂的。比如你先得弄清楚对象的内存的堆栈是怎么存储的。这很关键。

原始类型:number、string、boolean、null、undefined

对象类型:属性集合、函数、数组、日期、正则、error

原始值是不可变得

1
2
3
var a = 'hello' // 定义一个由小写
a.toSuperCase(); // 返回 HELLO
s // s 还是 hello

通常将对象称为引用类型 (reference type), 对象的值都是引用,对象的比较都是引用的比较。

可变的对象引用

1
2
3
4
5
6
var a = []; // | —---堆----- | | ----栈---- |
var b = a; // | a |-->| [] |
b[0] = 1; // | b指向a的引用 | | |
a[0] // | a| | |
a === b

变量的声明
var a;
var a = 0, b = 0;

1
2
3
4
5
6
7
8
9
10
var scope = 'global';
function () {
console.log(scope);
var scope = 'local';
console.log(scope);
}
// 输出
// undefined 变量提升,块级作用域变量覆盖全局作用域同名变量
// local、、、

es5 和es 6中变量提升的区别:

es5的var 中存在变量提升,可以在变量声明前使用;
es6的const、let两种命令不存在变量提升,不能再变量声明前使用变量;

传递

按值传递

1
2
3
4
5
6
7
8
9
10
11
12
function a(str) {
str = str + "5678";
console.log(str);
return str;
}
var s = "1234";
console.log('aaaa',a(s), s);
// 打印的结果
12345678
aaaa 12345678 1234

如上代码中可以得到结果,如果是按值传递,不管方法a中的赋值结果如何,都不会改变方法外部的变量值。

哪些是按值传递的呢?
原始类型(number、string、boolean、null、undefined)的这五种类型是按值传递的。原始类型在内存中是存在栈中,而对象类型是存储在堆内存中,理解对象的内存存储可以让你对传递的概念等有很好的帮助。

按引用传递

看看下面这个练习题的打印结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var arr = [1, 2, 3, 4];
function a (input) {
return input.push([5, 6, 7, 8])
}
function b (input) {
input = '2234'
return [input].concat([5, 6, 7, 8])
}
console.log('aaa', a(arr), arr);
console.log('------------');
console.log('bbbb', b(arr), arr);
// 打印结果
// aaaa 5 [1, 2, 3, 4, [5, 6, 7, 8]]
// ------------
// bbbb ["2234", 5, 6, 7, 8] [1, 2, 3, 4]

aaaa

list是个Object, 它的值存在堆内存中,arr指向堆内存的地址,arr的值变了,arr也就跟着变了。这就是引用传递。

其中的5是list.push返回的数组长度 Array.prototype.push()

bbbb

当函数内部对其重新赋值(即newValue !== oldValue),传入的外部变量不会受影响; Array.prototype.push()

克隆

针对对象的引用传递,改变的是引用的值,无法创建一个一样的对象。有两个解决方法,一种是自己写的克隆的方法 ,一种Object.assign()

不过两种都有缺点,自己写clone比较麻烦,下面代码里的兼容性也不好,arguments.callee在ES 5的严格模式中是背禁用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
深度拷贝方法1
function clone(obj)
{
var o,i,j,k;
if(typeof(obj)!="object" || obj===null)return obj;
if(obj instanceof(Array))
{
o=[];
i=0;j=obj.length;
for(;i<j;i++)
{
if(typeof(obj[i])=="object" && obj[i]!=null)
{
o[i]=arguments.callee(obj[i]);
}
else
{
o[i]=obj[i];
}
}
}
else
{
o={};
for(i in obj)
{
if(typeof(obj[i])=="object" && obj[i]!=null)
{
o[i]=arguments.callee(obj[i]);
}
else
{
o[i]=obj[i];
}
}
}
return o;
}
深度拷贝方法2
var deepCopy= function(source) {
var result={};
for (var key in source) {
result[key] = typeof source[key]===’object’? deepCoyp(source[key]): source[key];
}
return result;
}