声明、定义、引用、赋值
这几个名词经常出现 应该都很熟悉,但放在一起,你们会知道这其中的差别吗。
申明
申明:声明语句是本身什么也没有实现,只是通过创建变量和函数。查找栈内存,如果不存在创建的对象,则创建,并指向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; }
|