一.js中的变量
a.var,let ,const的异同
1.var
var在不进行初始化的条件下的值为undefined,它的作用域是函数作用域,在使用var在函数的内部定义一个变量的时候,意味着该变量在退出前进行销毁。并且它可以进行变量提升。
例子:
function foo(){
conlose.log(age);
var age=26;
}
foo();//undefined
//相当于:
function foo(){
var age;
console.log(age);
age=26;
}
foo();//undefined
2.let
let 在声明变量的时候不需要赋值,作用域为块作用域但是不存在变量提升的情况。
3.const
const相当于一个常量在声明的时候必须初始化变量并且他也是块作用域不存在变量提升
4.重点:异同总结
var和let都不需要初始化变量,但是let是块作用域var是函数作用域,var存在变量提升的情况,并且let在全局作用域中声明的变量不会成为window属性而var会成为。
const与let基本相同但是const在声明变量的时候必须赋值而且不能修改相当于一个常量。
例子:
function test01(){
if (true) {
var name = "Mett";
console.log(name);//Mett
}
console.log(name);//Mett
}
test01();
function test02(){
if (true) {
let name = "Jok";
console.log(name);//Jok
}
console.log(name);//没有定义,什么也不输出
}
test02();
var student='Alise';
console.log(window.student);//Alise
let age=12
console.log(window.age);//undefined
注意: f o r 循环中的 l e t 声明 \textcolor{red}{注意:for循环中的let声明} 注意:for循环中的let声明
在let出现之前for循环定义的变量会渗透到循环体的外部,使用var时最常见的问题是对迭代变量奇特声明和修改。
for(var i=0;i<5;++i){
}
console.log(i);//5;i++先自增,++i先赋值
for(let i=0;i<5;++i){
}
console.log(i);//没有定义,仅限于for循环的内部使用
for(var i=0;i<5;++i){
setTimeout(()=>{
console.log(i)
},0)
}//输出:5,5,5,5,5;因为在退出循环时迭代变量保存的时循环退出的值:5.在之后执行逻辑时,所有的i都是同一个变量,因此输出的额都是最终值。
for(let i=0;i<5;++i){
setTimeout(()=>{
console.log(i)
},0)
}//输出:0,1,2,3,4;因为js后台为每个迭代循环声明了一个新的迭代变量。所以每个setTimeout引用的都是不同的变量实例所以得到的是我们所期望的值。
二.语句
1.for-in语句
for-in语句是一种严格的迭代语句,用于枚举对象中的非符号键属性:
for(const proName in window){
document.write(proname)
}
ES中对象的属性时无序的,因此for-in语句不能保证返回对象属性的顺序。换句话说返回的顺序坑能会因为浏览器的不同而产生不同的结果。
注意:当for-in循环要迭代的变量时null或者undefined时,不执行循环。
2.for-of语句
for-of可以用来遍历可迭代对象的元素
for(const el of [2,4,6,8]){
document.write(el)
}
for-of循环会按照可迭代对象的next()方法产生值的顺序迭代元素。
3.for-in与for-of
共性:
for-in和for-of都可以用来遍历属性
区别:
-
for…in语句用于遍历数组或者对象的属性(对数组或者是对象的属性进行循环遍历)
-
for-in得到对象的key或者数组,字符串的下标;for-of和for-each一样直接得到值
-
for-of不能用于对象
-
for of 不同与 forEach, 它可以与 break、continue和return 配合使用,也就是说 for of 循环可以随时退出循环。
const obj = { a: 1, b: 2, c: 3 } for (let i in obj) { console.log(i) //输出 : a b c } for (let i of obj) { console.log(i) //输出: Uncaught TypeError: obj is not iterable 报错了 }
const arr = ['a', 'b', 'c'] // for in 循环 for (let i in arr) { console.log(i) //输出 0 1 2 } // for of for (let i of arr) { console.log(i) //输出 a b c }
三.变量,作用域,内存
1.变量
原始值:简单的数据; 引用值:由多个值构成的对象
2.typeof与instanceof的区别
typeof :用于判断数据类型,返回值有number,string,boolean,function,undefined,objecat六个,我们可以看到typeof判断null、array、object以及函数的实例(new + 函数)时,它返回的都是object。这就导致在判断这些数据类型的时候得不到真实的数据类型。由此引出instanceof
instanceof :instance的意思是实例,因此可以知道instanceof的作用就是判断该对象是谁的实例,我们也就知道了instanceof是对象运算符。
typeof 和 instanceof 都是用来判断变量类型的,区别在于:
1、typeof判断所有变量的类型,返回值有number、string、boolean、function、object、undefined。
2、typeof对于丰富的对象实例,只能返回object,导致有时候得不到真实的数据类型。
3、instanceof用来判断对象,代码形式(obj1 instanceof obj2)(判断obj1是否为obj2的实例),obj2必须为对象,否则会报错。返回的是布尔值。
4、instanceof可以对不同的实例对象进行判断,判断方法是根据对象的原型链依次向下查询,如果obj2的原型属性存在于obj1的原型链上,(obj1 instanceof obj2)值为true。
3.执行上下文与作用域
执行上下文:
-
全局执行上下文
-
这是默认或基本执行上下文。不在任何函数内的代码都属于全局上下文,一个程序中只能由一个全局执行上下文。他要做的有两件事:
- 创建一个全局对象,在浏览器中为(
window
)。 - 将
this
的值设置为全局对象。
- 创建一个全局对象,在浏览器中为(
-
函数执行上下文:每次调用函数时,都会为该函数创建一个全新的执行上下文。每个函数都有自己的执行上下文,但它是在调用函数时创建的,这个上下文可以保护里面的私有变量和外界互不干扰。
-
Eval执行上下文在
eval
函数执行代码也会产生一种特殊的执行上下文,但我们通常不会使用它。例子1:
var foo=0;//全局作用域 console.log(foo);//0 var myFunction=(function (){ var foo=1;//函数作用域 console.log(foo);//1 var myNesetFunction=(function(){ var foo=2;//函数作用域 console.log(foo);//2 })(); })();
例子2:
//eval()函数会将传入的字符串当作js代码执行也就作用域 eval('var foo=3;console.log(foo);')//eval()作用域;3 //let/const 块作用域,变量无法提升 for( let i=0;i<5;i++){ setTimeout(function(){ console.log(i);//0,1,2,3,4 }) }
作用域
-
什么是作用域?
在js中,我们可以在任意的位置声明变量,但是不同的位置会影响变量的可用范围,这个范围被称为作用域。简单的来说,作用域就是程序中定义变量的区域,它决定了当前执行代码对变量的访问权限。
-
作用域的分类
作用域可以大致分为两种类型,分别是全局作用域和局部作用域。但是由于es6的推行又引入了一个新的作用域:块级作用域
-
全局作用域
全局作用域为程序的最外层作用域,一直存在。
一般情况下属于全局作用域变量具有的以下特征:
-
最外层的函数和在最外层函数外面定义的变量拥有全局作用域;
例子:
// 所有具有全局作用域的变量都会被绑定到 window 对象中,成为 window 对象的一个属性 // 1.最外层的函数和在最外层函数外面定义的变量拥有全局作用域; // 3.所有 window 对象的属性拥有全局作用域。如:window.name。 var n = 2;//全局作用域 function fn() { var a = 1;//函数作用域 return a; } window.name='jack' console.log(fn());//1 console.log(n);//2 console.log('添加到window上的属性',window.fn());//1 console.log('添加到window上的属性',window.n); console.log('添加到window上的属性',window.a);//因为a不属于全局作用域变量,所以window上没有该属性,返回undefined console.log("window自带属性",window.name); // console.log(a);//报错:a is not defined
-
所有未定义直接赋值的变量拥有全局作用域
// 2.所有未定义直接赋值的变量拥有全局作用域。 var n=2 function fn(){ a=1; return a; } fn() console.log(n);//2 console.log(a);//1//如果没有使用fn()哪呢相当于没有fn()同道,没有进行编译,那么就不存在将a放在全局变量中。 console.log('添加到window上的属性',window.n);//添加到window上的属性 2 console.log('添加到window上的属性',window.a);//添加到window上的属性 1
总结
根据上方一,二可以发现虽然只是var的写与不写就对结果残生了很大的影响。从而可以引出对变量声明的思考。这两个均属于使用var的函数作用域声明一类。 在使用var声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近上下文的就是函数的局部上下文。在with语句中,最接近上下文也是函数上下文。如果变量未经声明就被初始化了,那么它就会被自动添加到全局上下文中。但是在严格模式下,未经声明就初始化变量会报错。
-
所有 window 对象的属性拥有全局作用域,例如 window.name、window.location、window.top 等。
-
注意:所有具有全局作用域的变量都会被绑定到 window 对象中,成为 window 对象的一个属性
-
-
局部作用域
和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所以在一些地方会把这种作用域成为函数作用域。
var n =2; function fn(){ var a = 1; return a; } console.log(fn()); // 1 console.log(n); // 2 console.log(a); // 报错error
注意:在函数内定义的局部变量只有在函数被调用时才会生成,当函数执行完毕后会被立即销毁。
-
-
块级作用域
定义:花括号
{...}
内部的区域就是块级作用域区域。在es6之前都是用var来声明变量,而js只有全局作用域和块级作用域.但es6新增的let和const可以声明块级作用域的变量。变量的声明应该距离使用的地方越近越好。并最大限度的本地化。例子:
for (var j = 0; j < 10; j++) { console.log('var',j); } for (let i = 0; i < 10; i++) { console.log('let',i); } console.log(j);//10 console.log(i); // ReferenceError: i is not defined
通过上面的例子可以知道在let出现以前我们使用的都是var,这个时候for循环定义的迭代变量会偷盗循环体的外部,并且迭代变量保存的是导致循环退出的值。在改成使用let之后这个问题就消失了,因为此时的迭代变量作用域仅限于for循环内部,并且js引擎在后台回味每隔迭代循环声明一个新的迭代变量,实现了了块级作用域。
例子:
function fun() { var x=10; var i=5;//var没有块级作用域,把外面的内容污染,解决方法一:利用let 二:利用匿名函数 for(var i=0;i<x;i++){ document.writeln(i) } document.writeln(i) } fun(); //方式一: function fun() { var x=10; var i=5;//var没有块级作用域,把外面的内容污染,解决方法里利用let for(let i=0;i<x;i++){ document.writeln(i) } document.writeln(i) } fun(); //方式二: function fun() { var x = 10; var i = 5;//var没有块级作用域,把外面的内容污染,解决方法里利用let (function () { for (let i = 0; i < x; i++) { document.writeln(i) } })() document.writeln(i) } fun();
-
作用域链:js中的作用域链静态作用域即词法作用域
定义:当声明一个函数时,局部作用域一级一级向上扣起来,就是作用域链。
作用:从定义不难得出作用域链决定了各级上下文中代码在访问变量和函数时的顺序。
执行流程:
-
当执行函数时,总是先从函数内部寻找变量,即局部作用域开始。
-
如果内部找不到(函数的局部作用域没有),则会向创建函数的作用域(声明函数的作用域)寻找,依次向上。
例子:
var a = 1;//全局作用域变量 function f3() { console.log('f3',a); } function fn() { var a = 2;//函数作用域变量 function f1() { var a = 3;//函数作用域变量 console.log('f1',a); } //因为f2()定义在函数内部属于函数内部的所以输出的2 function f2() { console.log('f2',a); } f1()//3 f2()//2 //因为f3定义在全局,所以它属于全局变量,因此它向上找到的a为1 f3()//1 } fn();//3,2 console.log(a);//1
var color = 'blue' function chageColor() { let athercolor = 'red' console.log('color1', color); console.log('athercolor1', athercolor); function swapColors() { let tempColor = athercolor; color = tempColor; console.log('color2', color); console.log('athercolor2', athercolor); console.log('tempColor2', tempColor); //可以访问到color,athercolor,tempColor } //可以访问到color,athercolor,不可以访问到tempColor swapColors(); } //可以访问到color chageColor()//'color1'bulue; 'athercolor1'red; 'color2'red; 'athercolor2'red; 'tempColor2'red console.log('color3', color);//'color1'red; console.log('athercolor3', athercolor);//报错 console.log('tempColor3', tempColor);//报错
此例子的作用域链:
-
-
词法作用域
作用域的内部可以划分成五个阶段(了解):编译,执行,查询,嵌套,异常。
作用域又可以分为静态作用域和动态作用域。而js语言所采用的的就是静态作用域(又可以说成是词法作用域)规则的。这是否与上方我们常说的全局作用域,局部作用域,块级作用域冲突呢?其实它们并不冲突。因为JavaScript 语言采用的是词法作用域,是语言层面的,而全局作用域等是被包含的。
定义:所谓的词法作用域,就是代码在编写过程就体现出来的作用范围。代码一旦写好,不用执行, 作用范围就已经确定好了,这个就是所谓的词法作用域。
词法作用域的规则:
-
函数允许访问函数外的数据 (也有就近原则)
-
整个代码结构中只有函数可以限定作用域
-
作用域内首先使用变量提升分析
-
如果当前作用域找到所需变量,则停止查找
例子:
var value = 1; function foo() { console.log(value); } function bar() { var value = 2; console.log(value); foo(); } bar(); //2, 1
解析:
执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。
-
4.垃圾回收
a.垃圾回收的原理:
通过自动内存管库实现内存分配和闲置资源回收。基本思路:确定哪个变量不会再使用,然后释放它占用的内存,这个过程是周期性的,即垃圾回收程序每隔一段时间就会自动运行。
b.标记策略
从上方的原理中我们知道要通过标记变量进行跟踪记录变量是否还会使用。因此出现了标记策略。
在浏览器的发展史上,用到过的两种主要的标记策略分别是标记清理和引用计数。
5.js提升
什么是js提升?
定义: JavaScript 中的提升是一种在声明之前可以使用函数或变量的行为
在js中提升可以分为两种分别是变量提升和函数提升。
变量提升
就变量和常量而言,关键字 var 被提升,let 和 const 不允许提升。
例子:
console.log(a);//undefined
var a = 5;
在编译阶段,只有声明被移动到内存中。因此,变量 a 的值是 undefined,因为 a 是在未初始化的情况下打印的。注意:当变量在函数内部使用时,变量仅被提升到函数的顶部。
如果变量与 let (const)关键字一起使用,则不会提升该变量.
例子:
a = 5;
console.log(a);// error
let a;
换一种角度说,变量与let(const)关键字一起使用,变量被提升到暂时性死区(TDZ)中,直到程序执行到该变量的let(const)声明语句的时候才从TDZ中被释放。
暂时死区:在代码块内,使用let和const命令声明变量之前,该变量都是不可用的,语法上被称为暂时性死区。
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
函数提升
由于函数声明和变量声明都会被提升,函数会先被提升,然后才是变量,并且后面的函数声明会覆盖前面的。 代码示例:
<script>
//例一:引导
/* foo();
function foo() {
console.log(a); // undefined
var a = 2;
}
//等价于
function foo() {
var a;
console.log(a); // undefined
a = 2;
}
foo(); */
//例二:错点
/* foo();
bar();
var foo = function bar() {
console.log(a); // undefined
var a = 2;
}
//等价于
var foo;
foo()
bar()
foo = function bar() {
console.log(a); // undefined
var a = 2;
} */
/*
总结: 函数声明会被提升,但是函数表达式却不会被提升。
函数表达式不用提升,函数表达式就是把一个函数赋值给一个变量,
既然都是赋值,那么自然不会提升,只有声明才会提升。
即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用。
*/
//-----------------函数声明和变量声明同时存在--------------------
/* foo();
var foo;
function foo() {
console.log(1);
}
foo = function () {
console.log(2);
};
foo();
//等价于
function foo() {
console.log(1);
}
foo();//1
var foo
foo = function () {
console.log(2);
};
foo()//2 */
/*
总结:函数声明和变量声明都会被提升。但是函数会首先被提升,然后才是变量
*/
/* foo();
function foo() {
console.log(1);
}
var foo = function () {
console.log(2);
};
function foo() {
console.log(3);
}
// foo()//附加
//等价于
function foo(){
console.log(1);
}
function foo() {
console.log(3);
}
var foo
foo()
foo = function () {
console.log(2);
};
// foo()//附加 */
/*
总结:函数声明还是可以覆盖
*/
/*
总结:总结 声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
*/
function a(){}
var a;
console.log(a);
var b;
function b(){}
console.log(b)
var c=1;
function c(){}
console.log(c);
function d(){}
var d=1;
console.log(d);
上面代码开始分别定义了名为a、b的函数和变量,由于定义变量a、b时并未指定其初始值,因此不管这些变量放在同名的函数名之前还是之后,变量都会被函数覆盖;代码接下来定义了名为c、d的函数和变量,由于定义变量时为c、d变量指定了初始值,因此不管这些变量放在同名的函数名之前还是之后,变量都会覆盖函数。