那些我不知道的基础知识
1. function函数传参
1)传参类型为原始类型(字符,数值,布尔)时,为传值传递(passed by value);修改参数的值不会修改原始值。
var a = 1; function name(a) { // 参数相当于name(var a; a=4), 这个a的作用域在函数内,和外部的a是两个变量 a = 3 console.log('--inner--',a); // 3 } name(4); console.log('---outer--',a); // 1
2)传参类型为符合类型(对象,数组,函数等),为传址传递(passed by reference), 传入函数的是原始值的地址。
- 在函数内部修改传入的值的参数会修改原始值。
var obj = {a:1}; function fn(obj) { obj.a = 2; } fn(obj); console.log(obj); // { a: 2 }
- 在函数内部整个替换传入的参数,不会修改原始值。原因是:传入的代表地址的形参,被赋值后,指向另一个地址。原来地址的值不变。
var obj = {a:1}; function fn(obj) { obj = { a: 2 } } fn(obj); console.log(obj); // { a: 1 }
2. 语句和表达式
函数式编程涉及到对表达式的理解。
区别:
- 语句为了完成一段操作,一般不需要返回值;表达式是为了得到一个值,一定会返回一个值。
// 语句为了完成一段操作,如循环语句,赋值语句等 for(let i=0; i<5;i++){ console.log(i); }; var a = 1 + 3; // 表达式是为了返回一个值 if (true && 1) // 括号中是表达式,返回1
- 语句以分号结尾。表达式不能以分号结尾,加上分号,会变成语句。
- js引擎规定,function出现在行首时,时函数声明语句。非行首时,是函数表达式。
// 此为函数表达式 var foo = function() {};
3. 变量(var/function/let/const/class)
1.区分大小写
a和A是两个不同的变量,对于上面四种变量声明方式来说都是。
var a = 1; var A = 1;
2.变量提升(var和function)
对于var和function声明的变量存在变量提升,提升到当前函数作用域顶部或者全局作用域顶部;
js代码编译运行过程分为: 词法分析->语法解析(AST)->代码生成->代码执行;
编译器在进行代码生成时,遇到变量声明,编译器询问当前作用域是否已经存在该变量。
如果是,忽略;如果否,则要求作用域在当前作用域声明一个变量。
所以:
在js执行时,所有的声明类语句已经声明过了,他们在代码生成阶段阶段已经声明过了。
而且作用域声明的时候在当前作用域的顶部进行声明。
所以变量提升其实指的是变量声明提升,函数声明也会提升。
❌不要在块级作用域中声明函数,因为在ES5中function整个声明提升;ES6中只提升函数名。
console.log(a); // undefined var a = 100; // 相当于 var a; console.log(a); a=100;
对于let/const/class声明的变量(或常量)不存在变量提升,并且存在暂时性死区。
<script> // 暂时性死区是说作用域内只要声明了let变量,声明之前就不能使用 var a = 100; function add() { // 调用add()后报错;编译时不报错。 a = 1000;// Uncaught ReferenceError: Cannot access 'a' before initialization let a; console.log(a); } add(); </script> <script> // 下面是函数赋初值造成的“暂时性死区”现象 var a = 100; // Uncaught ReferenceError: Cannot access 'a' before initialization function sum(a=a) { // 参数赋值形成单独的作用域 相当于在函数内let a=a; } sum(); // 声明时不会报错,调用时报错 </script>
<script> // class变量也不存在变量提升; const b = new a();//Uncaught ReferenceError: Cannot access 'a' before initialization class a{ } </script>
3. 变量作用域
对于var和function变量来说只有函数作用域和全局作用域。
对于let/const/class存在块级作用。
对于for循环来说,如果循环变量使用var,其实循环变量是全局变量或者函数内变量;
如果循环变量使用let声明,实际生成了两层块级作用域;变量声明是一层,循环体是一层;
<script> var a= []; // i是全局变量,跳出循环时i=3; for (var i=0; i<3; i++) { a[i] = function() { console.log(i); } }; // 数组a包含三个函数元素 console.log(a.toString()); /* function(){console.log(i)}, function(){console.log(i)}, function(){console.log(i)}, */ a[2](); // 3 </script>
<script> var a= []; // let块级作用域变量,每次循环都相当于重新声明一个新的i变量,新的块级作用域 for (let i=0; i<3; i++) { a[i] = function() { console.log(i); } }; // 数组a包含三个函数元素 console.log(a.toString()); /* function(){console.log(i)}, function(){console.log(i)}, function(){console.log(i)}, */ a[2](); // 2 </script>
<script> // for循环生成两层块级作用域 for (let i=0; i<3; i++) {// 声明循环变量的部分是外层块级作用域;会循环3次 let i = 'hello'; // 循环体是内层块级作用域;内部作用域使用内部的块级变量 console.log(i); }; // hello // hello // hello </script>
4.变量重复声明
var 和function允许重复声明,后面的会覆盖前面的;let和const不允许重复声明,会报错。
var a = 1; var a = 2; console.log(a); // 2
// 只要出现了let声明,前后再次声明同一个变量就会报错 let a = 1; var a = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared console.log(a);
函数参数其实是一个隐藏的变量声明位置
function add1(a) { //var a var a = 4; // 重复声明 console.log(a); // 4 } function add2(a) {// var a let a = 4;// Uncaught SyntaxError: Identifier 'a' has already been declared console.log(a); } add1(); // 不会执行,因为在代码生成阶段就报错了 add2(); // 不会执行
5.顶层对象的属性
var和function在全局环境声明的变量,会成为全局对象的属性,即window对象的属性;
但是let,const, class的不会。
var a = 100; console.log(window.a);//100 let b = 10; console.log(window.b);//undefined class c{}; console.log(window.c);//undefined const D = 1; console.log(window.D);//undefined
6.变量命名规则
- 汉字可以作为变量名
- 保留字不能作为变量名
// 保留字有: arguments、break、case、catch、class、const、continue、 debugger、default、delete、do、else、enum、eval、export、 extends、false、finally、for、function、if、implements、import、in、 instanceof、interface、let、new、null、package、private、 protected、public、return、static、super、switch、this、throw、 true、try、typeof、var、void、while、with、yield
4.条件语句,循环语句
1)switch条件和case变量,比较是严格相等===;case后面要加break,否则会执行所有case语句;
2)break跳出所有的循环;continue语句跳出本次循环;
如果存在多层嵌套循环,则两者都是针对最内层循环。
3)使用标签,可以配合break和continue跳出最外层循环。
❌一定注意不要把赋值表达式当成条件表达式,否则会出现死循环,因为赋值表达式永远是true;===