1 - 作用域
1.1 作用域概述
通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
JavaScript(es6前)中的作用域有两种:
- 全局作用域
-
1.2 全局作用域
作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件。
1.3 局部作用域
作用于函数内的代码环境,就是局部作用域。 因为跟函数有关系,所以也称为函数作用域。
1.4 块级作用域
块作用域由 { } 包括。
- 在其他编程语言中(如 java、c#等),在 if 语句、循环语句中创建的变量,仅仅只能在本 if 语句、本循环语句中使用,如下面的Java代码: java有块级作用域:if(true){
int num = 123;
system.out.print(num); // 123
}
system.out.print(num); // 报错以上java代码会报错,是因为代码中 { } 即一块作用域,其中声明的变量 num,在 “{ }” 之外不能使用;而与之类似的JavaScript代码,则不会报错:
Js中没有块级作用域(在ES6之前)
if(true){
var num = 123;
console.log(123); //123
}
console.log(123); //123
2 - 变量的作用域
在JavaScript中,根据作用域的不同,变量可以分为两种:
- 全局变量
-
2.1 全局变量
在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。
全局变量在代码的任何位置都可以使用
- 在全局作用域下 var 声明的变量 是全局变量
特殊情况下,在函数内不使用 var 声明的变量也是全局变量(不建议使用)
2.2 局部变量
在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)
局部变量只能在该函数内部使用
- 在函数内部 var 声明的变量是局部变量
-
2.3 二者区别
全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
局部变量:只在函数内部使用,当其所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁,因此更节省内存空间
3 - 作用域链
3.1 概念
只要是代码都一个作用域中,写在函数内部的局部作用域,未写在任何函数内部即在全局作用域中;如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域;根据在[内部函数可以访问外部函数变量]的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链
3.2 案例分析1
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1();3.3 案例分析2
作用域链:采取就近原则的方式来查找变量最终的值。
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();4 - 预解析(预编译)
4.1 预解析的相关概念
JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行。
预解析:在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中进行提前声明或者定义。
- 代码执行: 从上到下执行JS语句。预解析会把变量和函数的声明在代码执行之前执行完成。
4.2 变量预解析
预解析也叫做变量、函数提升。
变量提升(变量预解析): 变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。
console.log(num); // 结果是多少?
var num = 10; // ?
结果:undefined
4.3 函数预解析
函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。
fn();
function fn() {
console.log(‘打印’);
}
结果:控制台打印字符串 —- ”打印“
注意:函数声明代表函数整体,所以函数提升后,函数名代表整个函数,但是函数并没有被调用!
4.4 函数表达式声明函数问题
函数表达式创建函数,会执行变量提升,此时接收函数的变量名无法正确的调用:
fn();
var fn = function() {
console.log(‘想不到吧’);
}
结果:报错提示 ”fn is not a function”
解释:该段代码执行之前,会做变量声明提升,fn在提升之后的值是undefined;而fn调用是在fn被赋值为函数体之前,此时fn的值是undefined,所以无法正确调用
5 - 原型和this指向
5.1对象的三种创建方式—复习
- 字面量方式var obj = {};
- new关键字var obj = new Object();
构造函数方式function Person(name,age){
this.name = name;
this.age = age;
}
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构造函数的问题
5.4构造函数原型prototype
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log(‘我会唱歌’);
}
var ldh = new Star(‘刘德华’, 18);
var zxy = new Star(‘张学友’, 19);
ldh.sing();//我会唱歌
zxy.sing();//我会唱歌5.5对象原型
对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。
proto对象原型和原型对象 prototype 是等价的
proto对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype5.6constructor构造函数
对象原型( proto)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。5.7原型链
每一个实例对象又有一个proto属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有proto属性,这样一层一层往上找就形成了原型链。
5.8构造函数实例和原型对象三角关系
1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的proto属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数5.9原型链和成员的查找机制
任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,该对象就有proto属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;
当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
如果没有就查找它的原型(也就是 proto指向的 prototype 原型对象)。
如果还没有就查找原型对象的原型(Object的原型对象)。
依此类推一直找到 Object 为止(null)。
proto对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。5.10原型对象中this指向
构造函数中的this和原型对象的this,都指向我们new出来的实例对象
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
var that;
Star.prototype.sing = function() {
console.log(‘我会唱歌’);
that = this;
}
var ldh = new Star(‘刘德华’, 18);
// 1. 在构造函数中,里面this指向的是对象实例 ldh
console.log(that === ldh);//true
// 2.原型对象函数里面的this 指向的是 实例对象 ldh5.11通过原型为数组扩展内置方法
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
//此时数组对象中已经存在sum()方法了 可以始终 数组.sum()进行数据的求6 - 函数进阶
6.1函数的定义方式
方式1 函数声明方式 function 关键字 (命名函数)function fn(){}
- 方式2 函数表达式(匿名函数)var fn = function(){}
- 方式3 new Function() var f = new Function(‘a’, ‘b’, ‘console.log(a + b)’);
f(1, 2);
var fn = new Function(‘参数1’,’参数2’…, ‘函数体’)
注意
/Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用
所有函数都是 Function 的实例(对象)
函数也属于对象
/
6.2函数的调用
/ 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 的指向不同
一般指向我们的调用者.
6.4改变函数内部 this 指向
call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向
应用场景: 经常做继承.
var o = {
name: ‘andy’
}
function fn(a, b) {
console.log(this);
console.log(a+b)
};
fn(1,2)// 此时的this指向的是window 运行结果为3
fn.call(o,1,2)//此时的this指向的是对象o,参数使用逗号隔开,运行结果为3
以上代码运行结果为:
6.5.高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
此时fn 就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
同理函数也可以作为返回值传递回来
7 - 闭包
7.1变量的作用域复习
变量根据作用域的不同分为两种:全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
7.2什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。7.3闭包的作用
作用:延伸变量的作用范围。
function fn() {
var num = 10;
function fun() {
console.log(num);
}
return fun;
}
var f = fn();
f();7.4闭包的案例
闭包应用-3秒钟之后,打印所有li元素的内容
for (var i = 0; i < lis.length; i++) {
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(i);
}8 - let和const
8.1 let
ES6中新增了用于声明变量的关键字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);
}
}
arr0;
arr1;
经典面试题图解:此题的关键点在于变量i是全局的,函数执行时输出的都是全局作用域下的i值。
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr0;
arr1;
经典面试题图解:此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值.
小结
- let关键字就是用来声明变量的
- 使用let关键字声明的变量具有块级作用域
- 在一个大括号中 使用let关键字声明的变量才具有块级作用域 var关键字是不具备这个特点的
- 防止循环变量变成全局变量
- 使用let关键字声明的变量没有变量提升
- 使用let关键字声明的变量具有暂时性死区特性
8.2 const
声明常量,常量就是值(内存地址)不能变化的量具有块级作用域
if (true) {
const a = 10;
}
console.log(a) // a is not defined声明常量时必须赋值
const PI; // Missing initializer in const declaration常量赋值后,值不能修改
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.
小结
- const声明的变量是一个常量
- 既然是常量不能重新进行赋值,如果是基本数据类型,不能更改值,如果是复杂数据类型,不能更改地址值
-
8.3 let、const、var 的区别
使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值
9 - 解构赋值
ES6中允许从数组中提取值,按照对应位置,对变量赋值,对象也可以实现解构
9.1 数组解构
let [a, b, c] = [1, 2, 3];
console.log(a)//1
console.log(b)//2
console.log(c)//3
//如果解构不成功,变量的值为undefined9.2 对象解构
let person = { name: ‘zhangsan’, age: 20 };
let { name, age } = person;
console.log(name); // ‘zhangsan’
console.log(age); // 20let {name: myName, age: myAge} = person; // myName myAge 属于别名
console.log(myName); // ‘zhangsan’
console.log(myAge); // 20
9.3 小结
- 解构赋值就是把数据结构分解,然后给变量进行赋值
- 如果结构不成功,变量跟数值个数不匹配的时候,变量的值为undefined
- 数组解构用中括号包裹,多个变量用逗号隔开,对象解构用花括号包裹,多个变量用逗号隔开
- 利用解构赋值能够让我们方便的去取对象中的属性跟方法
10 - 箭头函数
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所造成的问题
10.3 面试题
var age = 100;
var obj = {
age: 20,
say: () => {
alert(this.age)
}
}
obj.say();//箭头函数this指向的是被声明的作用域里面,而对象没有作用域的,所以箭头函数虽然在对象中被定义,但是this指向的是全局作用域
11 promise
11.1 为什么需要promise
11.2 Promise的基本使用
Promise是一个构造函数, 通过new关键字实例化对象
语法
new Promise((resolve, reject)=>{})
- Promise接受一个函数作为参数
- 在参数函数中接受两个参数
- resolve: 成功函数
- reject: 失败函数
promise实例
promise实例有两个属性
- state: 状态
-
1) promise的状态
第一种状态: pending(准备, 待解决, 进行中)
第二种状态: fulfilled(已完成, 成功)
第三种状态: rejected(已拒绝, 失败)2) promise状态的改变
通过调用resolve()和reject()改变当前promise对象的状态
示例
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
3) promise的结果
示例
const p = new Promise((resolve, reject)=> {
// 通过调用resolve, 传递参数, 改变 当前promise对象的 结果
resolve(‘成功的结果’)
//reject(‘失败的结果’)
})
console.dir(p)
11.3 Promise的方法
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)
})