一 作用域
1.1 作用域概述
通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
即:变量在某个范围内起作用和效果
作用:
提高了程序逻辑的局部性,增强了程序的可靠性,减少了命名冲突。
JavaScript(es6前)中的作用域有两种:
- 全局作用域
局部作用域(函数作用域)
<script> //num在全局作用域 var num = 10; console.log(num);//10 function fun(){ //现在这个num在局部作用域 var num = 20; console.log(num);//20 } fun() //这里打印的是全局作用域中的num 因为局部作用域中的num在函数执行完之后就销毁了 console.log(num); </script>
1.2 全局作用域
作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件。
1.3 局部作用域
作用于函数内的代码环境,就是局部作用域。因为跟函数有关系,所以也称为函数作用域。
1.4 块级作用域
块作用域由 { } 包括。
在其他编程语言中(如 java、c#等),在 if 语句、循环语句中创建的变量,仅仅只能在本 if 语句、本循环语句中使用,如下面的Java代码:
java有块级作用域:
以上java代码会报错,是因为代码中 { } 即一块作用域,其中声明的变量 num,在 “{ }” 之外不能使用;
而与之类似的JavaScript代码,则不会报错:
Js中没有块级作用域(在ES6之前)if(true){ int num = 123; system.out.print(num); // 123 } system.out.print(num); // 报错
<script> //es6新增的概念 块级作用域 if(3<5){ var num = 10 //num就在块作用域中 } console.log(num);//es6之前使用var定义的变量是可以访问的 </script>
二 变量的作用域
在JavaScript中,根据作用域的不同,变量可以分为两种:
全局变量
局部变量
<script> //num是全局变量 var num = 10; console.log(num);//10 function fun(a){ //num是局部变量 var num1 = 20; console.log(num1);//20 //全局变量可以在函数内部使用 console.log(num);//10 } fun() //报错,说明函数的参数并不是一个全局变量,而是一个局部变量,出了函数就无法使用 console.log(a); //报错,局部变量出了函数就不能使用 console.log(num1); </script>
2.1 全局变量
在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。
全局变量在代码的任何位置都可以使用
- 在全局作用域下 var 声明的变量 是全局变量
特殊情况下,在函数内不使用 var 声明的变量也是全局变量(不建议使用)
<script> /* 特殊情况下,在函数内不使用 var 声明的变量也是全局变量(不建议使用) */ function fun(){ a = 10 //没有用var修饰的变量,也是全局变量,但是不推荐这么做 console.log(a); } fun()//10 console.log(a);//10 </script>
2.2 局部变量
在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)
局部变量只能在该函数内部使用
- 在函数内部 var 声明的变量是局部变量
-
2.3 二者区别
全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
- 局部变量:只在函数内部使用,当其所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁,因此更节省内存空间
三 作用域链
3.1 概念
内部函数访问外部函数的变量,采取的是链式查找的方式来决定取哪个值,这种结构我们称为作用域链
总结规律: 就近原则
<script>
/*
只要是代码都一个作用域中,
写在函数内部的局部作用域,未写在任何函数内部即在全局作用域中;
如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域;
根据在**[内部函数可以访问外部函数变量]**的这种机制,
用链式查找决定哪些数据能被内部函数访问,就称作作用域链
*/
var num = 10
function fn(){
var num = 20
function fun(){
console.log(num);//就近原则
}
fun()
}
fn()
</script>
3.2 案例分析
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1();
var a = 1;
function fn1() {
var a = 2;
var b = '22';
fn2();
function fn2() {
var a = 3;
fn3();
function fn3() {
var a = 4;
console.log(a); //a的值 ?
console.log(b); //b的值 ?
}
}
}
fn1();
四 预解析(预编译)
JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。
JavaScript 解析器在运行 JavaScript 代码的时候分为两步:
预解析 和 代码执行。
- 预解析:在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中进行提前声明或者定义。
- 代码执行: 从上到下执行JS语句。
预解析会把变量和函数的声明在代码执行之前执行完成。4.1 变量预解析
预解析也叫做变量、函数提升。
变量提升(变量预解析): 变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。
4.2 函数预解析
函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。
4.3 预解析练习
<script>
/*
js引擎运行js的时候 分两步
第一步:预解析(预编译)
第二步:执行代码
预解析:js引擎会把js中所有的var,还有function 提升到当前作用域的最前面
执行代码:按照代码的书写顺序,从上往下执行
预解析又分为变量预解析(变量提升)和函数预解析(函数提升)
变量预解析
就是把所有变量的声明提升到当前作用域的最前面,不会提升赋值操作
函数预解析
就是把所有函数声明提升到当前作用域的最前面, 不会调用函数
*/
//第一个问题
//console.log(num);//报错
//第二个问题
console.log(num1);//undefined
var num1 = 10
console.log(num1);
//执行顺序
//var num1
//console.log(num1);//undefined
//num1 = 10
//第三个问题
fn()//11
function fn(){
console.log(11);
}
//执行顺序
// function fn(){
// console.log(11);
// }
// fun()
//第四个问题
fun()//报错
var fun = function (){//这种写法又称函数表达式,他的调用必须写在表达式的下面
console.log(22);
}
//执行顺序
//var fun
//fun()//这一步就报错了 因为fun是一个undefined,不能调用
//var fun = function (){
// console.log(22);
//}
</script>
<script>
//预解析案例
//案例1
// var num = 10;
// fun();
// function fun() {
// console.log(num);
// var num = 20;
// }
//执行顺序
// var num //undefined
// function fun() {
// console.log(num);//undefined
// var num = 20;
// }
// num = 10
// fun();
// 案例2
// var num = 10;
// function fn() {
// console.log(num);
// var num = 20;
// console.log(num);
// }
// fn();
//执行顺序
// var num //undefined
// function fn() {
// console.log(num);//undefined
// var num = 20;
// console.log(num);//20
// }
// num = 10
// fn()
// 案例3
// var a = 18;
// f1();
// function f1() {
// var b = 9;
// console.log(a);
// console.log(b);
// var a = '123';
// }
//执行顺序
// var a //undefined
// function f1() {
// var b = 9;
// console.log(a);//undefined
// console.log(b);//9
// var a = '123';
// }
// a = 18
// f1()
// 案例4
f1();
console.log(c);
console.log(b);
console.log(a);
function f1() {
var a = b = c = 9;
console.log(a);
console.log(b);
console.log(c);
}
//执行顺序
/* function f1() {
var a = b = c = 9;
//相当于 var a = 9; b=9; c=9 (b和c是直接赋值的9,前面没有var定义)
//这里 b 和 c是全局变量 a是局部变量 所有a出了函数就不能使用了
console.log(a);//9
console.log(b);//9
console.log(c);//9
}
f1()
console.log(c);//9
console.log(b);//9
console.log(a);//不能 因为a是函数内部的局部变量,出了函数就无法使用 */
</script>
五 原型和this指向
5.1 对象的三种创建方式—复习
字面量方式
var obj1 = { uname:'张三', age:23, sing:function(){ console.log('我会唱歌'); } } console.log(obj1);
使用new Object( )
var obj2 = new Object() //在js中称之为基类(祖宗),所有类的父类 obj2.name='李四' obj2.age = 24 obj2.sing=function(){ console.log('我会唱歌'); } console.log(obj2);
构造函数方式
function Person(name,age){ this.name = name; this.age = age; this.sing = function(){//方法 console.log('我会唱歌'); } } var obj = new Person('zs',12);
5.2 静态成员和实例成员
5.2.1实例成员
实例成员就是构造函数内部通过this添加的成员
如下列代码中uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('我会唱歌'); } } var ldh = new Star('刘德华', 18); console.log(ldh.uname);//实例成员只能通过实例化的对象来访问
5.2.2 静态成员
静态成员 是在构造函数本身上添加的成员
如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('我会唱歌'); } } Star.sex = '男'; var ldh = new Star('刘德华', 18); console.log(Star.sex);//静态成员只能通过构造函数来访问
5.3 构造函数的问题
构造函数方法很好用,但是存在浪费内存的问题。
<script> function Star(uname,age){ this.uname = uname//属性 this.age = age this.sing = function(){//方法 console.log('我会唱歌'); } } var ldh = new Star('刘德华',58) var zxy = new Star('张学友',66) console.log(ldh.sing === zxy.sing);//fasle表明这两个不是同一个内存空间 console.dir(Star);//打印构造函数,看他里面有没有一个protoType对象 是有的 /* new 在执行的时候会做4件事情 1,在内存中创建一个空的对象 2,让this指向这个新的对象 3,执行构造函数里面的代码,给这新对象添加属性和方法 4,返回这个新对象 */ //产生的问题:每创建一个对象,都会为里面的方法分配一个内存空间,存在浪费内存的问题 </script>
5.4 构造函数原型prototype
概念
原型是一个对象,每一个构造函数都有一个prototype属性,指向这个对象。
作用
可以把那些不变的方法,直接定义在prototype对象(原型对象)上,这样所有对象的实例就可以共享这些方法。
总结
一般情况下,我们会把属性定义到构造函数里面,公共的方法放在原型对象上面,可以节省内存
<script>
/*
JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
*/
function Star(uname,age){
this.uname = uname//属性
this.age = age
/* this.sing = function(){//方法
console.log('我会唱歌');
} */
}
Star.prototype.sing = function(){
console.log('我会唱歌');
}
var ldh = new Star('刘德华',58)
var zxy = new Star('张学友',66)
console.log(ldh.sing === zxy.sing);//现在就打印的是true.说明对象共享同一个方法
console.dir(Star);//打印构造函数,看他里面有没有一个protoType对象 是有的
</script>
5.5 对象原型 proto
概念
对象都会有一个属性 proto 指向构造函数的 prototype 原型对象
proto就称为对象的原型
作用
为对象的查找机制提供一个方向,对象在调用方法的时候,就会通过proto到原型对象(prototype)里面去查找有没有对应方法,有的话,就执行
<script>
function Star(uname,age){
this.uname = uname//属性
this.age = age
}
//方法定义在原型对象prototype上面
Star.prototype.sing = function(){
console.log('我会唱歌');
}
var ldh = new Star('刘德华',58)
var zxy = new Star('张学友',66)
console.log(ldh);
//说明对象中有一个__proto__属性指向了 构造函数的 原型对象(Star.prototype)
//对象在调用sing方法的时候,先去Star中找,如果没有的话,在去Star的原型对象prototype找
console.log(ldh.__proto__ === Star.prototype);//true
//__proto__就叫做对象的原型 指向 构造函数的原型对象
ldh.sing()
</script>
5.6 constructor构造函数
概念
对象原型(proto)和构造函数原型对象(prototype)里面都有一个属性 constructor 属性 ,constructor我们称为构造函数,因为它指回构造函数本身
作用
主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
<script>
function Star(uname,age){
this.uname = uname//属性
this.age = age
}
//方法定义在原型对象prototype上面
Star.prototype.sing = function(){
console.log('我会唱歌');
}
var ldh = new Star('刘德华',58)
var zxy = new Star('张学友',66)
//验证
//对象的原型( __proto__)和构造函数原型对象(prototype)里面都有一个属性 constructor 属性
console.log(ldh.__proto__);
console.log(Star.prototype);
//验证 __proto__ 和 prototype 他们里面 constructor属性的值 就是当前的构造函数
console.log(ldh.__proto__.constructor);
console.log(Star.prototype.constructor);
</script>
5.7 原型链
概念
每一个实例对象又有一个proto属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有proto属性,这样一层一层往上找就形成了原型链.
作用
当实例对象调用方法的时候,首先通过proto属性找到构造函数的原型对象,看它里面有没有,没有的话再往上一层找,找到最后,如果都没有的话,就报错.
<script>
function Star(uname,age){
this.uname = uname//属性
this.age = age
}
Star.prototype.sing = function(){
console.log('我会唱歌');
}
var ldh = new Star('刘德华',58)
var zxy = new Star('张学友',66)
console.log(Star.prototype);//打印的是原型对象,他也是一个对象,他有没有原型对象
//说明Star.prototype的对象的原型__proto__ 是指向Object的
console.log(Star.prototype.__proto__ == Object.prototype);//true
//在看看Object的原型对象是什么
console.log(Object.prototype.__proto__);//null
</script>
5.8 构造函数实例和原型对象三角关系
1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的proto属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数
5.9 原型链查找机制
查找机制
1,当访问一个对象的属性(包括方法),首先查找这个对象自身有没有该属性(方法)
2,如果没有就查找他的原型(就是proto指向的prototype原型对象)
3,如果还没有就查找原型对象的原型(Object的原型对象)
4,依次类推,一直找到Object为止
5,proto对象的原型的意义就是为对象成员的查找机制提供了一个方向(线路)
<script>
function Star(uname,age){
this.uname = uname
this.age=age
}
Star.prototype.sing=function sing(){
console.log('我会唱歌');
}
var ldh = new Star('刘德华',58)
//只要是对象,就有这个__proto__属性,指向原型对象
console.log(ldh.__proto__);
console.log(ldh.__proto__ === Star.prototype);
//原型对象又是一个对象,他里面也有__proto__属性,这个属性其实是执向的是Object的原型对象
console.log(ldh.__proto__ .__proto__ === Object.prototype);
//Object.prototype也是一个对象,他里面也有__proto__属性,指向null
console.log(Object.prototype.__proto__);
</script>
<script>
/*
查找机制
1,当访问一个对象的属性(包括方法),首先查找这个对象自身有没有该属性(方法)
2,如果没有就查找他的原型(就是__proto__指向的prototype原型对象)
3,如果还没有就查找原型对象的原型(Object的原型对象)
4,依次类推,一直找到Object为止
5,__proto__对象的原型的意义就是为对象成员的查找机制提供了一个方向(线路)
*/
function Star(uname,age){
this.uname = uname
this.age=age
}
Star.prototype.sing=function sing(){
console.log('我会唱歌');
}
//Object.prototype.sex='男'
//Star.prototype.sex='女'
var ldh = new Star('刘德华',58)
//ldh.sex='男'
console.log(ldh.sex);
</script>
5.10 原型对象中this指向
构造函数中的this和原型对象的this,都指向我们new出来的实例对象
构造函数中this指向 —> 对象的实例
原型对象中this指向 —> 对象的实例
<script>
function Star(uname,age){
this.uname = uname
this.age=age
//在构造函数中,里面的this指向对象实例
console.log(this);//这里打印的就是对象的实例
}
Star.prototype.sing=function sing(){
console.log('我会唱歌');
//在原型对象中,里面的this指向对象实例
console.log(this);//这里打印的就是对象的实例
}
var ldh = new Star('刘德华',58)
ldh.sing()
</script>
5.11 通过原型为数组扩展内置方法
通过原型可以为内置对象扩展自定义方法
例如: 为Array内置对象自定义数组求和方法
<script>
console.dir(Array.prototype);
//给数组内置对象添加一个求和的方法(sum)
//将来只需要调用这个方法,就可以获取数组元素的和
Array.prototype.sum=function(){
//定义一个求和变量
var sum = 0
//遍历数组元素进行累加
for(var i=0;i <this.length;i++ ){
sum += this[i]
}
//返回结果
return sum
}
//调用
var arr = [1,2,3]
var sum = arr.sum()
console.log(sum);
var arr1 = new Array(4,5,6)
var sum1 = arr1.sum()
console.log(sum1);
</script>
六 函数进阶
6.1 函数的定义方式
方式1 函数声明方式 function 关键字 (命名函数)
function fn(){}
方式2 函数表达式(匿名函数)
var fn = function(){}
方式3 new Function()
```javascript var f = new Function(‘a’, ‘b’, ‘console.log(a + b)’); f(1, 2);
var fn = new Function(‘参数1’,’参数2’…, ‘函数体’)
注意
/Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用
所有函数都是 Function 的实例(对象)
函数也属于对象
/
<a name="1ebe181f"></a>
### 6.2 函数的调用
```javascript
/* 1. 普通函数 */
function fn() {
console.log('人生的巅峰');
}
fn();
/* 2. 对象的方法 */
var o = {
sayHi: function() {
console.log('人生的巅峰');
}
}
o.sayHi();
/* 3. 构造函数*/
function Star() {};
new Star();
/* 4. 绑定事件函数*/
btn.onclick = function() {}; // 点击了按钮就可以调用这个函数
/* 5. 定时器函数*/
setInterval(function() {}, 1000); 这个函数是定时器自动1秒钟调用一次
/* 6. 立即执行函数(自调用函数)*/
(function() {
console.log('人生的巅峰');
})();
6.3 函数内部的this指向
这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同
一般指向我们的调用者.
<button>按钮</button>
<script>
//函数的不同调用方式,决定了this的指向不同
//1,普通函数 this执行window
function fn(){
console.log('普通函数:'+this);
}
window.fn()
//2,对象方法 this指向对象obj
var obj = {
study:function(){
console.log('对象方法:'+this);
}
}
obj.study()
//3,构造函数 this 执行实例对象 stu
function Student(){}
Student.prototype.sing=function(){
console.log('构造函数:'+this);
}
var stu = new Student()
stu.sing()
//4,绑定事件函数 this 指向的是函数的调用者 btn这个按钮对象
var btn = document.querySelector('button')
btn.onclick=function(){
console.log('绑定事件函数:'+this);
}
//5,定时器函数 this 指向的也是window
window.setTimeout(function(){
console.log('定时器函数:'+this);
},1000);
//6,立即执行函数 this 指向的也是window --- 前面语句一定要加上分号结束
(function(){
console.log('立即调用函数:'+this);
})()
</script>
6.4 改变函数内部 this 指向
call( )方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向.
call的作用:
1,可以改变函数内的this指向
2,可以调用函数 格式: 函数名.call( )
<script>
var obj = {
name:'张三'
}
function fn(a,b){
console.log(this);
console.log(a+b);
}
//fn()
//改变指向 这里this指向对象实例obj
fn.call(obj,1,2)
//call 两个作用
//1,可以调用函数 格式:函数名.call()
//2,可以改变函数内的this指向
</script>
6.5 高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
(或)
函数作为参数或者返回值,这种函数,叫做高阶函数
<script>
/*
函数也是一种数据类型,同样也可以作为参数或者返回值使用
*/
function fn(a,b,f){
console.log(a+b);
f&&f()//如果存在这个函数,就调用它 && 符号在前面为假的时候,就不会执行后面的语句了
}
fn(1,2,function(){
console.log('我是后面调用的');
})
</script>
七 闭包
7.1 变量的作用域复习
变量根据作用域的不同分为两种:全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
-
7.2 什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数。那个变量所在的函数就是闭包.
<script> //闭包指有权访问另一个函数作用域中变量的函数 //fun这个内部函数访问了fn这个外部函数的局部变量num //局部变量num所在的函数就是闭包 function fn(){ var num = 10 function fun(){ console.log(num); } fun() } fn() </script>
7.3闭包的作用
作用:延伸变量的作用范围。
function fn() { var num = 10; function fun() { console.log(num); } return fun; } var f = fn(); f();
<script> //fn外面的作用域 访问 fn内部的局部变量 //作用:延伸了变量的作用范围 之前num只能在fn内部调用,现在我们可以从外面调用num function fn(){ var num = 100 /* function fun(){ console.log(num); } */ //fun() //return fun//第一个操作,将内部的函数返回 return function(){ //简化操作:直接将内部的函数返回 console.log(num); } } var f = fn()//第二步操作,在外部接收返回的内部函数 f()//第三步操作,调用 </script>
八 let和const
8.1 let
let声明的变量只在所处于的块级有效
if (true) {
let a = 10;
}
console.log(a) // a is not defined
注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。
不存在变量提升
console.log(a); // a is not defined
let a = 20;
暂时性死区
利用let声明的变量会绑定在这个块级作用域,不会受外界的影响
var tmp = 123;
if (true) {
tmp = 'abc';
let tmp;
}
经典面试题
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0]();
arr[1]();
小结
- let关键字就是用来声明变量的
- 使用let关键字声明的变量具有块级作用域
- 在一个大括号中 使用let关键字声明的变量才具有块级作用域 var关键字是不具备这个特点的
- 防止循环变量变成全局变量
- 使用let关键字声明的变量没有变量提升
使用let关键字声明的变量具有暂时性死区特性
<script> //1,let关键字就是用来声明变量的 /* let a = 10 console.log(a); */ //2,使用let关键字声明的变量有块级作用域,var关键字就不具备这个特点 /* if(true){ var a = 100; let b = 200; console.log(a); console.log(b); } console.log(a);//100 console.log(b);//报错 */ //4,防止循环变量变成全局变量 /* for(var i =0;i<3;i++){ console.log(i); } console.log(i); */ /* for(let i =0;i<3;i++){ console.log(i); } console.log(i); */ //5,使用let关键字声明的变量没有变量提升 /* console.log(a); let a = 10 */ //6.使用let声明的变量不能重复定义 /* let a = 10 let a = 10 console.log(a); */ //使用let声明的变量具有暂时性死区 //ES6明确规定,如果块级作用域中,存在let和const命令,这个块级作用域声明的变量,从一开始就 //形成了封闭的作用域,凡是在声明之前使用这些变量就会报错 //在代码块内,使用let命令声明变量之前,该变量都是不可使用的,这种语法称之为暂时性死区 var num = 10 if(true){ console.log(num);//这个区域已经被let声明的num控制住了,在num声明之前是不能使用的 let num = 100 } </script>
8.2 const
具有块级作用域
if (true) { const a = 10; } console.log(a) // a is not defined
声明常量时必须赋值
const PI; // Missing initializer in const declaration
常量赋值后,值不能修改
```javascript const PI = 3.14; PI = 100; // Assignment to constant variable.
const ary = [100, 200]; ary[0] = ‘a’; ary[1] = ‘b’; console.log(ary); // [‘a’, ‘b’]; ary = [‘a’, ‘b’]; // Assignment to constant variable.
<a name="5db9fd7c-1"></a>
#### 小结
- const声明的变量是一个常量
- 既然是常量不能重新进行赋值,如果是基本数据类型,不能更改值,如果是复杂数据类型,不能更改地址值
- 声明 const时候必须要给定值
```javascript
<script>
//声明常量,常量就是值(内存地址)不能变化的量
//1,使用const声明的常量具有块级作用域
/* if(true){
const a = 10
console.log(a)
}
console.log(a)//报错 */
//2,使用const声明的常量必须赋初值
/*
var a
let b
const c //报错
*/
//3.常量声明后值不可以更改
/* const a = 100
a = 200//报错 */
const arr = [10,20]
arr[0] = 100 //这里没有报错,因为仅仅是修改了arr里面的内容
arr[1] = 200
console.log(arr);
//arr = [1,2] //报错:改变arr的指向,让他指向了别的数组
const stu = {
name:'张三',
age:23
}
stu.name = '李四'
console.log(stu);
/* stu = {
name:'李四',
age:24
} */
</script>
8.3 let、const、var 的区别及应用场景
区别
- 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
- 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值
应用场景
建议使用优先级: const > let > var
const好处:
1,阅读代码的人看到之后就会认为这个值是不能修改的,防止了无意间修改变量值导致的错误
2,浏览器对const进行了优化,可以提高代码的执行效率
let好处
1.没有变量提升
2,有块级作用域
let一般用于基本数据类型,const一般用于引用数据类型(函数,对象,字符串这些)
如果定义的变量,不能被修改,一定使用const
九 解构赋值
ES6中允许从数组中提取值,按照对应位置,对变量赋值,对象也可以实现解构
9.1 数组解构
let [a, b, c] = [1, 2, 3];
console.log(a)//1
console.log(b)//2
console.log(c)//3
//如果解构不成功,变量的值为undefined
9.2 对象解构
let person = { name: 'zhangsan', age: 20 };
let { name, age } = person;
console.log(name); // 'zhangsan'
console.log(age); // 20
let {name: myName, age: myAge} = person; // myName myAge 属于别名
console.log(myName); // 'zhangsan'
console.log(myAge); // 20
9.3 小结
- 解构赋值就是把数据结构分解,然后给变量进行赋值
- 如果结构不成功,变量跟数值个数不匹配的时候,变量的值为undefined
- 数组解构用中括号包裹,多个变量用逗号隔开,对象解构用花括号包裹,多个变量用逗号隔开
利用解构赋值能够让我们方便的去取对象中的属性跟方法
<script> const p = { name:'小三', age:23, sex:'女' } const{name,age,sex,hight} = p console.log(name); console.log(age); console.log(sex); console.log(hight);//没有东西匹配就是undefined //:后面就是别名 /* const{name:myName,age:myAge,sex:mySex} = p console.log(myName); console.log(myAge); console.log(mySex); */ </script>
十 箭头函数
10.1 语法
ES6中新增的定义函数的方式。
箭头函数简化了函数的写法() => {} //():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体 const fn = () => {}//代表把一个函数赋值给fn
函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
function sum(num1, num2) { return num1 + num2; } //es6写法 const sum = (num1, num2) => num1 + num2;
如果形参只有一个,可以省略小括号
function fn (v) { return v; } //es6写法 const fn = v => v;
箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this
const obj = { name: '张三'} function fn () { console.log(this);//this 指向 是obj对象 return () => { console.log(this);//this 指向 的是箭头函数定义的位置,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象 } } const resFn = fn.call(obj); resFn();
10.2 小结
箭头函数中不绑定this,箭头函数中的this指向是它所定义的位置,可以简单理解成,定义箭头函数中的作用域的this指向谁,它就指向谁
箭头函数的优点在于解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题 ```javascript
//最简单的箭头函数 const fn = () => { console.log('天要下雨')} fn() //函数体里面只有一个语句,{}可以省略 const fn1 = () => console.log('天要下雨') fn1() //加一个参数 const fn2 = (str) => console.log('天要下雨'+str) fn2('娘要嫁人') //如果参数只有一个,参数外面的小括号也可以省略 const fn3 = str => console.log('天要下雨'+str) fn3('娘要嫁人') //写个函数,接收2个参数,返回这两个参数的和 function sum(num1,num2){ return num1 + num2 } const sum1 = (num1,num2) => {return num1 + num2} console.log(sum1(1,2)); const sum2 = (num1,num2) => num1 + num2 //函数体中代码的执行结果就是返回值,那么return也可以省略 console.log(sum1(3,2));
//==================================下面理解就行==============================================/
//箭头函数不绑定this.箭头函数没有自己的this关键字
//如果在箭头函数中使用this,this关键字指向箭头函数定义位置中的this
function fun(){
console.log(this);//{name: '张三'} 指向的是obj对象
return () => {
console.log(this);//{name: '张三'} //指向的是箭头函数定义的位置,箭头函数定义在fun中
//fun指向的是obj对象,所以箭头函数中的this也是指向obj对象
}
}
const obj = {name:'张三'}
var res = fun.call(obj)//fun指向的是obj 让obj调用这个fun方法,返回值是一个函数
res()
</script>
<a name="21250a62"></a>
## 十一 promise
<a name="84a9881a"></a>
### 11.1 为什么需要promise
像写同步代码一样写异步代码
<a name="f000726c"></a>
### 11.2 Promise的属性
Promise是一个构造函数, 通过new关键字实例化对象
> 语法
```javascript
new Promise((resolve, reject)=>{})
- Promise接受一个函数作为参数
- 在参数函数中接受两个参数
- resolve: 成功函数
- reject: 失败函数
promise实例
promise实例有两个属性
- state: 状态
- result: 结果 ```javascript //1,创建 //接收一个函数作为参数 //在参数中,又可以接收两个参数 resolve和reject const p = new Promise((resolve,reject)=>{})
//2,打印promise对象,可以看出他有两个属性 //状态属性 PromiseState //默认是 pending 准备状态.初始状态 //结果属性 PromiseResult //默认是 undefined console.log(p);
<a name="Jkq91"></a>
#### 1) promise的状态
第一种状态: pending(准备, 待解决, 进行中)<br />第二种状态: fulfilled(已完成, 成功)<br />第三种状态: rejected(已拒绝, 失败)
<a name="iQxHb"></a>
#### 2) promise状态的改变
通过调用resolve()和reject()改变当前promise对象的状态
> 示例
```javascript
const p = new Promise((resolve, reject) => {
// resolve(): 调用函数, 使当前promise对象的状态改成fulfilled
resolve()
})
console.dir(p) // fulfilled
示例
const p = new Promise((resolve, reject) => {
// resolve(): 调用函数, 使当前promise对象的状态改成fulfilled
// reject(): 调用函数, 使当前promise对象的状态改成rejected
// resolve()
reject()
})
console.dir(p)
- resolve(): 调用函数, 使当前promise对象的状态改成fulfilled
- reject(): 调用函数, 使当前promise对象的状态改成rejected
promise状态的改变是一次性的, 单向不可逆
3) promise的结果
示例
<script>
//1,创建
//接收一个函数作为参数
//在参数中,又可以接收两个参数 resolve和reject
const p = new Promise((resolve,reject)=>{})
//2,打印promise对象,可以看出他有两个属性
//状态属性 PromiseState
//默认是 pending 准备状态.初始状态
//结果属性 PromiseResult
//默认是 undefined
console.log(p);
//3,如何改变promise的状态属性
//调用resolve()方法,当前promise的状态就从pending改成fulfilled 成功
const p1 = new Promise((resolve,reject)=>{ resolve() })
console.log(p1);
//调用reject()方法,当前promise的状态就从pending改成rejected 失败
const p2 = new Promise((resolve,reject)=>{ reject() })
console.log(p2);
//4,如何改变promise的结果属性
//调用resolve()方法的时候传递参数,它就会将传递的参数保存在结果属性中
const p3 = new Promise((resolve,reject)=>{ resolve('成功') })
console.log(p3);
//调用reject()方法的时候传递参数,它就会将传递的参数保存在结果属性中
const p4 = new Promise((resolve,reject)=>{ reject('失败') })
console.log(p4);
//5,promise的状态属性的改变是不可逆的
const p5 = new Promise((resolve,reject)=>{ resolve(); reject() }) //希望先成功再失败
console.log(p5);//状态还是成功,不可逆的
</script>
11.3 Promise的方法
then方法
概念
本质是一个对象,对象就有属性和方法,他有两个属性,一个是状态属性,一个结果属性
方法就是then方法
创建
new Promise((resolve, reject)=>{})
属性
状态属性 promiseState
- 默认是pending 准备状态
- 调用resolve()方法,就可以将当前的状态改成fulfilled 成功状态
- 调用reject()方法,就可以将当前的状态改成 rejected 失败状态
结果属性 promiseResult
- 默认是undefined
- 调用resolve()方法的时候传递参数,他就会将传递的参数保存在结果属性中
- 调用reject()方法的时候传递参数,他就会将传递的参数保存在结果属性中
方法then
- 接收两个参数,如果p的状态是成功的话,就会调用第一个函数,如果p的状态是失败的话,就会调用第二个函数
- 如果是成功的话,可以给第一个函数添加一个参数value.传递过来的数据就保存在value中
- 如果是失败的话,可以给第二个函数添加一个参数reason.传递过来的数据就保存在reason中
then方法返回值
then方法会返回一个新的promise实例,默认是pending状态,并且可以继续调用then方法
示例
const p = new Promise((resolve, reject)=> {
// 通过调用resolve, 传递参数, 改变 当前promise对象的 结果
resolve('成功的结果')
//reject('失败的结果')
})
// then方法函数
// 参数
// 1. 是一个函数
// 2. 还是一个函数
// 返回值: 是一个promise对象
p.then(()=>{
// 当promise的状态是fulfilled时, 执行
console.log('成功时调用')
}, () => {
// 当promise的状态是rejected时, 执行
console.log('失败时调用')
})
console.dir(p)
示例
const p = new Promise((resolve, reject)=> {
// 通过调用resolve, 传递参数, 改变 当前promise对象的 结果
//resolve('123')
reject('失败的结果')
})
// then方法函数
// 参数
// 1. 是一个函数
// 2. 还是一个函数
// 返回值: 是一个promise对象
p.then((value)=>{
// 当promise的状态是fulfilled时, 执行
console.log('成功时调用', value)
}, (err) => {
// 当promise的状态是rejected时, 执行
console.log('失败时调用', err)
})
console.dir(p)
- 在then方法的参数函数中, 通过形参使用promise对象的结果
then方法返回一个新的promise实例, 状态是pending
const p = new Promise((resolve, reject)=> {
// 通过调用resolve, 传递参数, 改变 当前promise对象的 结果
resolve('123')
//reject('失败的结果')
})
// then方法函数
// 参数
// 1. 是一个函数
// 2. 还是一个函数
// 返回值: 是一个promise对象
const t = p.then((value)=>{
// 当promise的状态是fulfilled时, 执行
console.log('成功时调用', value)
}, (reason) => {
// 当promise的状态是rejected时, 执行
console.log('失败时调用', reason)
})
console.dir(t)
promise的状态不改变, 不会执行then里的方法
// 如果promise的状态不改变, then里的方法不会执行
new Promise((resolve, reject) => {
}).then((value) => {
console.log('成功')
}, (reason) => {
console.log('失败')
})
在then方法中, 通过return将返回的promise实例改为fulfilled状态
// 如果promise的状态不改变, then里的方法不会执行
const p = new Promise((resolve, reject) => {
resolve()
})
const t = p.then((value) => {
console.log('成功')
// 使用return可以将t实例的状态改成fulfilled
return 123
}, (reason) => {
console.log('失败')
})
t.then((value) => {
console.log('成功2', value)
}, (reason) => {
console.log('失败')
})
如果在then方法中, 出现代码错误, 会将返回的promise实例改为rejected状态
// 如果promise的状态不改变, then里的方法不会执行
const p = new Promise((resolve, reject) => {
resolve()
})
const t = p.then((value) => {
console.log('成功')
// 使用return可以将t实例的状态改成fulfilled
//return 123
// 如果这里的代码出错, 会将t实例的状态改成rejected
console.log(a)
}, (reason) => {
console.log('失败')
})
t.then((value) => {
console.log('成功2', value)
}, (reason) => {
console.log('失败', reason)
})
<script>
//then方法,接收两个函数,如果p的状态是成功的时候就会调用第一个函数,如果p的状态是失败就会调用第二个函数
//1,p1的状态是成功
const p1 = new Promise((resolve,reject)=>{ resolve() })//调用resolve(),p1的状态就是成功
p1.then( ()=>{console.log('p1成功');} , ()=>{console.log('p1失败');} )
//2,p2的状态是失败
const p2 = new Promise((resolve,reject)=>{ reject() })//调用reject(),p2的状态就是失败
p2.then( ()=>{console.log('p2成功');} , ()=>{console.log('p2失败');} )
//如何接收p返回的结果(如何拿到p结果属性中的数据呢),就是给then中的函数加一个参数就可以拿到
//3,p3的状态是成功,如何拿到结果中成功的数据
const p3 = new Promise((resolve,reject)=>{ resolve('成功的数据') })//resolve()中的参数就存在结果属性中
p3.then( value=>{console.log('p3成功'+value);} , ()=>{console.log('p3失败');} )
//4,p4的状态是失败,如何拿到结果中失败的数据(原因)
const p4 = new Promise((resolve,reject)=>{ reject('失败的数据') })//reject()中的参数就存在结果属性中
p4.then( value=>{console.log('p4成功'+value);} , reason=>{console.log('p4失败'+reason);} )
</script>
<script>
//1,then方法也是有返回值,返回值是什么??
const p = new Promise((resolve,reject)=>{})
const t = p.then(()=>{},()=>{})
console.log(t);//返回的是promise对象 默认是pending状态
//2,既然then方法的返回值是promise对象.那是不是可以继续调用then方法,可以的
//现在p1的状态是默认的pending状态,只要他的状态不改变,那么后面就无法执行
const p1 = new Promise((resolve,reject)=>{})
const t1 = p1.then(()=>{},()=>{})
t1.then(()=>{console.log('成功');},()=>{console.log('失败');})
//第三层t2,t3打印成功或者失败,跟第一层p2,p3成功或者失败是没有任何关系,只跟第二层有关
//只有中间这一层出现错误的时候,第三层才会打印失败
const p2 = new Promise((resolve,reject)=>{resolve()})
const t2 = p2.then(()=>{},()=>{})//中间都没有出错所有t2会正常执行
t2.then(()=>{console.log('成功');},()=>{console.log('失败');})
const p3 = new Promise((resolve,reject)=>{reject()})
const t3 = p3.then(()=>{},()=>{})//中间都没有出错所有t3会正常执行
t3.then(()=>{console.log('成功');},()=>{console.log('失败');})
//打印失败的情况
const p4 = new Promise((resolve,reject)=>{resolve()})
//因为这个a没有定义,你直接使用就会出错,并且出错的代码要放到第一个参数里面,因为resolve()会执行第一个函数
const t4 = p4.then(()=>{console.log(a);},()=>{})
t4.then(()=>{console.log('成功');},()=>{console.log('失败');})
const p5 = new Promise((resolve,reject)=>{reject()})
//因为这个a没有定义,你直接使用就会出错,并且出错的代码要放到第二个参数里面,因为reject()会执行第二个函数
const t5 = p5.then(()=>{},()=>{console.log(a);})
t5.then(()=>{console.log('成功');},()=>{console.log('失败');})
//then连写
new Promise((resolve,reject)=>{reject()}).then(()=>{},()=>{console.log(a);}).then(()=>{console.log('成功');},()=>{console.log('失败');})
</script>