ES6是为了简化代码,当你发现不能简化的时候,用ES5
# 1. ES6变量声明
# a. var的弊端
var声明的变量存在预解析,可以先使用再声明,容易造成逻辑混乱
var可以重复定义一个变量,容易造成逻辑错误
for循环中使用var存在全局变量污染(无视块级作用域)
# b. let 声明变量
- let没有预解析,必须先定义再使用
- let不能在同一作用域下重复定义一个变量
- let在for循环中不存在全局变量污染问题
- 用let定义的i只能在for循环的内部去使用,for循环结束,变量被回收掉。
- let拥有块级作用域
{}内形成一个作用域就叫做块级作用域
<body>
<h3>点击事件和for循环</h3>
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
</body>
<script>
// 需求:点击按钮,显示对应的索引
let btns = document.querySelectorAll("button");
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
console.log(i); // 5
};
}
</script>
/*
for循环是同步的,回调函数是异步的
点击的时候for循环已经执行完毕了
*/
<script>
let btns = document.querySelectorAll("button");
for (let i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
console.log(i); // 按序点击依次是 0 1 2 3 4
};
}
</script>
/*
为什么let可以???---- let存在块级作用域
每一次for循环都产生一个块级作用域
{ i=0; btns[0] 打印的i==>0 }
{ i=1; btns[1] 打印的i==>1 }
{ i=2; btns[2] 打印的i==>2 }
..........
*/
# c. const 声明常量
- const没有预解析,必须先定义再使用
- const一旦声明,必须赋值,一旦赋值,不能修改
- 引用类型其值可变,因为引用类型绑定的是内存地址
- const拥有块级作用域
- const也是模块化中引入模块的一个关键字,作为模块导入可以用小写
- const express = require('express')
为了区分变量,常量通常首字母大写,或者全部大写
const Data = 10; const DATA = 10;
const和引用数据类型(数组、对象、函数....)
<script>
// 为什么不能修改?因为改变了引用地址
const Arr = [10,20,30]
Arr = [1,2,3]
console.log(Arr) // 报错,复杂数据类型一旦赋值(引用地址)不能修改
</script>
/*
复杂数据类型存储的是内存中的引用地址
栈内存 ==> 基本数据类型
堆内存 ==> 引用数据类型
*/
<script>
// 为什么可以修改?因为引用地址没变过,改的是值
const Arr = [10,20,30]
Arr[0] = 100
console.log(Arr) // [100,20,30]
</script>
# 2. 模版字符串
用反引号(`)对字符串进行包裹,支持换行,可以使用 ${} 嵌入变量
let name="苏东旭"
console.log(`姓名:${name}`)
# 3. 解构语法
# a. 对象解构
使用场景:将对象中的值提取出来,赋值给变量
实现:将对象的key的value值赋值给key同名的变量
注意点:
key要和变量名同名才可以解构
在对象中找不到和变量名同名的key,得到undefined
# 对象的完全解构和部分结构
<script>
let obj = {
name:'苏东旭',
age:26
}
// 正常写法
let name = obj.name
let age = obj['age']
console.log(name,age)
// 对象完全解构
let {name,age} = obj
console.log(name,age)
// 对象部分结构
let {name} = obj
</script>
# 解构重命名
为避免变量名被声明过,起冲突。可以对大括号内的变量起别名
let {name:myName} = obj
# 扩展
开发中常用于解构内置对象或第三方库的方法
let {random} = Math
# b. 数组解构
对象是无序的,解构时变量位置可以随意,但数组是有序的
# 数组完全解构
let arr = [10,20,30]
let [a,b,c] = arr
console.log(a,b,c) // 10 20 30
# 数组部分结构
按索引顺序做一一对应关系,补全 ,
进行占位
let arr = [10,20,30]
let [a] = arr
console.log(a) // 10
let [,b] = arr
console.log(b) // 20
let [,,c] = arr
console.log(c) // 30
# 数组复合解构
let arr = [10,20,30,[40,50,60]]
let [a,b,c,[x,y,z]] = arr
console.log(a,b,c,x,y,z)
# c. 字符串解构
字符串有索引、有长度,不能使用数组方法,本质上是伪数组
字符串不能通过索引修改值
解构字符串本质上和解构数组是一样的
let str = "小苏同学好瘦呀"
//str[1] = "旭" // 修改无效
//演示一下部分结构
let [a,b] = arr
console.log(a,b) //小 苏
# 扩展:交换两个变量值
let a = 10,b = 20;
//等号左边是变量,等号右边是数组
[a,b] = [b,a]
console.log(a,b) // 20 10
# 4. 对象的简化写法
将变量赋值给对象的key时,如果两者同名,可以只写key
let name = "苏东旭"
let age = 26
// 普通写法
let obj = {
name:name,
age:age
}
// 对象简写
let obj = {
name,age
}
# 5. 函数参数默认值和参数解构
# a. 函数形参的默认值
# function fn(形参=默认值),当形参为undefined时候,赋值为默认值
// 函数调用少传参,会出现业务逻辑问题
// 1. ES5 短路法解决
function add(a,b,c,d){
a = a || 0;
b = b || 0;
c = c || 0;
d = d || 0;
return a + b + c + d
}
console.log(add(1,2))
// 2. ES6 函数默认值
function add(a=0,b=0,c=0,d=0){
return a + b + c + d
}
console.log(add(1,2)) //3
# b. 函数参数的解构赋值
什么时候用函数参数的解构赋值呢?
当函数调用中的实参为对象或数组时
# 数组方式(有序)
let arr = [10,20,30]
function fn([x,y,z]){
// 形参不加[] ----- 相当于 let x=arr,y,z
// [10,20,30] undefined undefined
// 形参加了[] ----- 相当于 let [x,y,z] = arr
// 10 20 30
console.log(x,y,z)
}
fn(arr)
fn() // Uncaught TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
# 对象方式(无序)
let obj = {
type:"GET",
url:"https://www.xiaosutongxue.com",
data:{name:"苏东旭"}
}
function fn({url,type,data}){
// 形参不加{} ----- 相当于 let url=obj,type,data
// 形参加了{} ----- 相当于 let {url,type,data} = obj
console.log(url,type,data)
}
fn(obj)
fn() // Uncaught TypeError: Cannot destructure property 'url' of 'undefined' as it is undefined.无法解构未定义的属性url,因为它是undefined
# c. 解构赋值指定参数的默认值
// 不为整体赋默认值报错
// 参数整体设置默认值{} ---- 相当于 let {url,type,data} = {},解决报错,但会得到三个undefined
function fn({url,type,data}={}){
console.log(url, type, data); //undefined undefined undefined
}
fn()
// 解决undefined问题 ---- 给每个参数都设置默认值
function fn({url="xxx.com",type="GET",data={}}={}){
console.log(url, type, data); // xxx.com GET {}
}
fn()
# 6. rest参数和扩展运算符
...rest
的出现就是为了取代 arguments
...rest
主要解决形参实参不对等的问题,用于收集剩余实参,并将其放入一个数组中
# a. rest参数
arguments
是函数内置的方法,用于接取全部的参数,是个伪数组,不能使用数组的方法箭头函数没有
arguments
function fn() {
console.log(arguments) // Arguments(3) [10, 20, 30, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
fn(10, 20, 30);
# rest的使用
- rest得到的是真数组,可以使用数组的方法,如:foreach
- 获取剩余参数时,rest只能放到最后,作为最后一个形参,否则会报错
- rest是个变量,其名可换,重点是...不能多也不能少
function fn(...rest) {
console.log(rest) // (3) [10, 20, 30]
}
fn(10, 20, 30);
# b. 扩展运算符
它的作用就是可以将数组或者对象展开,拆开成为一个一个单独的数据。
使用场景:
使用场景
1.数组中的值作为函数调用的实参使用 2.合并数组 3.合并对象 4.es6中另一个合并对象的方法 Object.assign({}, obj, obj2)
# 扩展运算符使用
// 展开数组
let arr = [1, 2, 3];
console.log(...arr); // 1 2 3
// 展开对象,注意用扩展运算符直接展开对象会报错,js不支持 name:"苏东旭",age:16这种格式
let obj = {
name: "苏东旭",
age: 16,
};
console.log({ ...obj }); // {name: "苏东旭", age: 16}
// 展开字符串
let str = "苏东旭"
console.log(...str); // 苏 东 旭
# 合并数组
合并后产生一个新数组,修改这个新数组的内容,不会对原数组造成影响
let arr = [1, 2, 3];
let brr = ["苏", "东", "旭"];
console.log([...arr, ...brr]); // (6) [1, 2, 3, '苏', '东', '旭']
# 合并对象
注意:合并过程中,两个对象的key不能是重复的,后面的会替换前面的
let obj1 = {
name: "苏东旭",
age: 26,
};
let obj2 = {
name:"小苏",
job: "web前端",
hobby: "coding",
};
console.log({ ...obj1, ...obj2 }); // {name: '小苏', age: 26, job: 'web前端', hobby: 'coding'}
# ES6对象合并
Object.assign({},对象1,对象2)
将第二个以及后面的对象参数合并到第一个对象参数中,如果去掉第一个对象参数{},那么就会将对象2,合并到对象1中,这就使得对象1被修改
注意:合并过程中,两个对象的key不能是重复的,后面的会替换前面的
let obj1 = {
name: "苏东旭",
age: 26,
};
let obj2 = {
name:"小苏",
job: "web前端",
hobby: "coding",
};
console.log(Object.assign({}, obj1, obj2)); // {name: '小苏', age: 26, job: 'web前端', hobby: 'coding'}
# 解构赋值中使用
扩展运算符在解构赋值中,可以获取数组或字符串解构中剩余的值
注意:...rest
在函数形参中使用,用于获取剩余实参,而扩展运算符用在解构赋值中,用于获取剩余参数,这是两者的区别,虽然很像,但是不一样。
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let [a, b, c, ...d] = arr;
console.log(a, b, c, d); // 1 2 3 (6) [4, 5, 6, 7, 8, 9]
# 7. 箭头函数
# a. 函数的几种写法
// 声明式
function fn(a, b) {
return a + b;
}
console.log(fn(1, 2));
// 函数表达式
let fun = function () {
console.log("函数表达式");
};
fun();
// 立即执行函数(只能执行一次)
(function () {
console.log("立即执行函数");
})()
# b. 箭头函数
箭头函数一般用在回调函数中,出现的目的也是为了简化回调函数(将一个函数作为另一个函数的参数使用)
# 基本语法
(形参)=>{函数体}
let fun = () => {
console.log('我是箭头函数')
}
fun()
# 箭头函数简写
- 函数体只有一句话,
{}
可以省略 - 函数体只有一句话(并且要返回),
{}和return
都可以省略 - 函数只有一个参数,可以省略
()
- 多参多行函数体,不能简写
- 返回值是一个对象时,不能简写
- 箭头函数没有arguments,但可以使用rest参数
# 注意点
// 无参无返
let fn1 = () => console.log('无参无返')
fn1()
// 无参有返
let fn2 = () => "无参无返"
console.log(fn2())
// 有参无返
let fn3 = val => console.log(val)
fn3('有参无返')
// 有参有返
let fn4 = val => val
console.log(fn4('有参有返'))
// 多参,多行函数体
let fn5 = (a,b) => {
let sum = a + b;
return sum
}
console.log(fn5(1,2))
// 返回值为对象时
let fn6 = () => {
return {
name:'苏东旭',
age:26
}
}
console.log(fn6())
// 箭头函数没有arguments
let fn7 = () => {
console.log(arguments) // arguments is not defined
}
fn7(1,2,3,4)
// 箭头函数可以使用rest参数
let fn8 = (...rest) => {
console.log(rest);
};
fn8(1, 2, 3, 4); // (4) [1, 2, 3, 4]
# c. this指向总结
this一般写在函数中
this永远指向一个引用地址(内存空间)
this使用场景 | this指向 |
---|---|
全局使用 | window |
定时器中使用 | window |
事件中使用 | 事件源 |
对象方法的调用,方法内部的this | 该对象 |
箭头函数 | 外层作用域 |
// 1.全局使用 ----- window
console.log(this);
function fn() {
console.log(this);
}
fn();
// 2.定时器中使用 ----- window
setTimeout(function(){
console.log(this)
},3000)
// 3.事件中使用 ----- 指向事件源
btn.onclick = function(){
console.log(this)
}
// 4.对象中使用 ----- 该对象
// 对象内的方法不能使用箭头函数,箭头函数的this指向当前作用域的外层作用域(父级作用域)
// 只有对象方法调用,作用域内的this才指向对象
let name = "苏东旭"
let obj = {
name:"小星星",
mythis:this,
say(){
console.log(this.name) // 小星星
}
}
obj.say()
console.log(obj.mythis) // 指向window
# 8. Promise
# a. Promise出现的目的
Promise是一种异步编程的解决方案,它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象
功能:避免了回调地狱,把异步代码改成调用起来像同步代码。(异步代码同步化)
所谓 Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供了统一的API,各种操作可以用同样的方法进行处理。
一个Promise对象有以下几种状态:
- pending:初始状态,既不是成功,也不是失败状态。
- fulfilled:意味着操作成功完成。
- rejected:意味着操作失败。
Promise对象有以下两个特点:
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和 rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法更改。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为 fulfilled
和从 pending
变为 rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就被称为resolved(已定型)。如果改变已经发生了,你再对 Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
# 什么是回调地狱?
ajax本质是同步的,它的success是异步的(ajax本身是个函数,success函数作为其参数,回调)
ajax 请求和响应需要时间,受网络,响应内容大小等等一些环境的影响,同步执行去发起多个ajax请求,就会导致最终获取响应的顺序不可控
回调地狱的目的就是控制响应的顺序,在第一个ajax请求成功的回调中发起第二个ajax请求,在第二个ajax请求成功的回调中发起第三个ajax请求....,以此实现请求的按序响应。
每个成功的回调不可能只用来发起下一个ajax请求,还有对当前请求结果的一些操作,这可能就是若干行代码。在这种回调地狱里编写代码就太难受了,来回找书写代码的位置,太头疼了。
Promise的出现就是为了解决这个问题,异步代码同步化
# b. Promise的基本语法
Promise是一个内置构造函数。内置构造函数主要就是用来创建对象的。
# new Promise( (resolve,reject)=>{} )
首先
new Promise()
,形参是一个回调函数,回调函数有两个参数,分别是:resolve
、reject
,这两个参数也是函数。
Promise
承诺 resolve
坚定信念 reject
拒绝 pending
悬而未决的 fulfilled
满足了 rejected
拒绝
# Promise的三个状态和两个特点
var p = new Promise((resolve,reject)=>{
// 默认状态 --- pending
// 成功状态 --- fulfilled
resolve() // pending ==> fulfilled
// 失败状态 --- rejected
reject() // pending ==> rejected
})
/*
三个状态:
默认一进来Promise是pending状态
当第一个回调resolve被调用时,状态由pending转为fulfilled
如果说第二个回调reject被调用,状态则由pending转为rejected
两个特点:
特点1:状态不受外部影响
特点2:状态一但发生改变,将不再变化(不存在后面覆盖前面一说,就是resolve在前,那就是成功状态,不会受后面reject的影响而改变,反之亦是如此)
*/
# 基本语法
Promise内部不做任何数据处理,交给then和catch
.then()
方法处理成功的回调 --- 也就是resolve打包的数据
.catch()
方法处理失败的回调 --- 也就是catch打包的数据
// 假设 flag 是ajax的返回状态
let flag = true;
let p=new Promise((resolve,reject)=>{
if(flag){
resolve('成功返回的数据');//pending-->fulfilled 异步操作成功的回调函数
}else{
reject('失败抛出的异常'); //pending-->reject 异步操作失败的回调函数
}
})
p.then(data => { //在外面调用then处理成功的逻辑
console.log(data); //fulfilled
}).catch(err=>{ //在外面调用catch处理失败的逻辑
console.log(err); //reject
})
// then方法会在异步成功后调用,catch方法会在异步失败后调用
# c. Promise同步异步?
let p = new Promise((resolve,reject)=>{
if (true) {
resolve();
} else {
reject();
}
console.log('Promise')
})
p.then(data=>{
console.log('then')
})
console.log('全局')
/*
执行顺序
Promise
全局
then
*/
# Promise本身是同步的,
.then
和.catch
是异步的
# d. Promise中then的链式调用
1、第一个then执行完会执行第二个then
2、then里面的函数的返回值,会被下一个then的形参接收
3、如果返回的是一个promise对象,下一个then的形参接收到的不是这个promise对象,而是这个promise对象内部调用resolve时候的实际参数
# e. Promise解决回调地狱
let p1 = new Promise((resolve, reject) => {
$.ajax({
url: "接口地址",
type: "GET",
success(res) {
resolve(res);
}
});
});
let p2 = new Promise((resolve, reject) => {
$.ajax({
url: "接口地址",
type: "GET",
success(res) {
resolve(res);
}
});
});
let p3 = new Promise((resolve, reject) => {
$.ajax({
url: "接口地址",
type: "GET",
success(res) {
resolve(res);
}
});
});
// then链式调用解决回调地狱,将异步代码同步化,看起来像同步的,then是异步的
p1.then((data) => {
console.log(data);
return p2;
}).then((data)=>{
console.log(data);
return p3;
}).then((data)=>{
console.log(data);
});
/*
then链式调用的特点
1. 第一个then执行完会执行第二个then
2. then里面的函数的返回值,会被下一个then的形参接收
3. 如果返回的是一个promise对象,下一个then的形参接收到的不是这个promise对象,而是这个promise对象内部调用resolve时候的实际参数
*/
# f. Promise解决多重请求的函数封装
function getPromiseObj(url,method){
return new Promise((resolve,reject)=>{
$.ajax({
url:'请求路径'+url,
type:method,
success(res){
resolve(res)
}
})
})
}
let p1 = getPromiseObj('/index/index','GET')
p1.then(data=>{
console.log(data)
})
# g.Promise的all()方法和race()方法的使用
# all()方法
Promise.all([多个Promise对象])
统一处理多个异步程序,类似 &&
的关系(一失败就失败,全成功才成功)
# 特点:
如果多个异步程序都是成功状态,p的状态就是成功的,多个异步程序的成功结果会打包成一个数组统一返回
但凡发现一个失败,最快得到失败结果的直接返回
let p1 = new Promise((resolve, reject) => {
setTimeout(function () {
resolve("p1");
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(function () {
resolve("p2");
}, 1000);
});
let p = Promise.all([p1, p2]);
p.then((data) => {
console.log(data); // (2) ['p1', 'p2']
});
# race()方法
Promise.race([多个Promise对象])
# 特点:
谁快返回谁,跟成功失败没关系
let p1 = new Promise((resolve, reject) => {
setTimeout(function () {
resolve("p1");
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(function () {
resolve("p2");
}, 1000);
});
let p = Promise.race([p1, p2]);
p.then((data) => {
console.log(data); // p2
});
# h. async/await的用法
then链式调用解决回调地狱,将异步代码改成看起来像同步代码(方便维护)
链式调用是同步的,但是底层的
.then
是异步的,所以说看起来像同步代码,实际还是异步代码
async
await
一组关键字,真正实现异步代码同步化
async
用来修饰函数的,表示这是一个异步函数
await
在异步函数中使用,表示同步代码(将异步程序变成同步代码),await
后面的异步执行完毕才会执行后续的同步代码
let p1 = new Promise((resolve, reject) => {
setTimeout(function () {
resolve("p1");
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(function () {
resolve("p2");
}, 1000);
});
async function getRes() {
await p1.then((data) => console.log(data));
await p2.then((data) => console.log(data));
console.log("我是同步代码");
}
getRes();
/*
打印结果
p1
p2
我是同步代码
*/
# 9. 类
# 10. 模块化
# 面向对象
# 1. 对象
# a. 对象的成员
万物皆对象 ----- 生活中的万事万物都可用对象描述
对象无非都有描述信息(对象属性),以及行为特征(对象方法)----- 对象的两个成员(属性、方法)
对象是一组无序的键值对集合
函数本身也是对象
js中所有的内容都和对象有关系 ----- js也可叫做 万物皆对象语言
# b. 对象的操作
// 对象方法不要用箭头函数,没意义,并不能达到简写的目的,还会改变this的指向
var obj = {
name = "小苏",
age:26,
gender:"男",
eat:function(){
console.log(this.name + "爱吃番茄炒蛋") // 此时的this是没有指向的,只有当该方法被调用时,this才有指向
}
}
// 函数在调用时才执行
obj.eat() // 此时方法被调用,方法体内的this指向调用者obj
console.log(obj.name)
console.log(obj["name"])
// 对象成员的修改/新增
// 已存在则修改,不存在则新增
obj.age = 18;
obj.email = "sudongxu0917@163.com";
// 对象成员的删除
delete obj.gender // 不建议 (有可能是别人创建的对象,不要删除)
obj.gender = null //建议
# c. 对象安全
对象封装的好处:数据安全,全局容易被篡改,对象内部不容易被修改
创建对象更多的是一种解决方案,一种变成思想
对象封装:
把全局变量、全局函数丢到对象里,这就是对象封装,对象封装解决数据安全的问题
函数封装:
把全局代码丢到函数里,这就是函数封装
# 2. 面向过程和面向对象
面向过程编程就是分析出解决问题的步骤,然后使用函数把这些步骤一步步实现,重心放在完成的每个过程上,C就是面向过程的,(面向过程就是凡事亲力亲为)
面向对象则是以封装的思想,重点放在解决问题需要的对象身上,然后通过对对象的操作来完成相应的功能,Java面向对象的,(面向对象就是找专业的人干专业的事)
两者比较:
面向过程性能比面向对象高,适合跟硬件联系很紧密的东西,但是不易维护,不易复用,不易扩展
面向对象易维护、易复用、易扩展,但是更耗资源,性能比面向过程低。
至于以后使用哪一种,这就需要看我们的具体需求,根据不同的需求做不同的选择
# 3. 创建对象
# a. 字面量方式创建对象
直接使用字面量的方式创建对象比较方便,以键值对的格式来定义数据
优点:方便直观,可以直接访问里面的属性方法
缺点:创建大量相似对象时,会出现代码重复,只适合创建单个对象
var book = {
name:"JavaScript入门到放弃",
price:100,
author:"小苏",
showInfo(){
console.log("买它")
}
}
console.log(book)
# b. 内置构造函数创建对象
使用new关键字+内置的构造函数创建对象
缺点:只能创建单个对象
var book = new Object();
book.name:"JavaScript入门到放弃",
book.price:100,
book.author:"小苏",
showInfo(){
console.log("买它")
}
# c. 简单工厂创建对象
优点:可以批量创建对象
缺点:无法判断对象的类型(打印出来都一样,都是Object类型)
// 使用工厂函数创建对象
function createPerson(name,age,gender){
// 1. 内部创建空对象
var p = new Object();
// 2. 给空对象添加属性
p.name = name;
p.age = age;
p.gender = gender;
// 3. 返回这个对象
return p;
}
var p1 = createPerson('张三',20,'男')
var p2 = createPerson('李四',24,'女')
var p3 = createPerson('王五',18,'男')
console.log(p1) // Object {name: '王五', age: 20, gender: '男'}
# d. 自定义构造函数创建对象
构造函数本质上还是一个函数
- 构造函数首字母大写(约定俗成)
- 不需要手动创建对象,会自动创建
- 给this身上添加成员
- 不需要手动返回,会自动返回
- 必须搭配new关键字才有意义(创建对象),只有写了new,上述几条才会生效
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
var p1 = new Person('张三',20,'男')
var p2 = new Person('李四',18,'女')
var p3 = new Person('王五',20,'男')
console.log(p1) // Person {name: '王五', age: 20, gender: '男'}
# 4. new关键字做了什么事情
构造函数是如何创建对象的
a. 内部会默认创建一个空对象
b. 将创建好的空对象赋值给this
c. 给this添加成员
d. 自动返回this
// 伪代码
var obj = {} // a
this = obj // b
this.xxx = xxx // c
return this // d
# 5. 构造器
类:泛指一类事物
将对象的公共特征抽取出来,变成一个类 ----- 抽象
对象:特指某个具体的事物
js中没有严格的类的概念,一定要说有 ----- 构造函数模拟面向对象语言
ts也不是面向对象的语言,但是写面向对象会更严谨
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
var p1 = new Person('张三',20,'男')
// 如何分类
// 1. constructor属性获取 构造函数(又叫构造器)
console.log(p1.constructor === Person) // true
// 2. instanceof关键字,判断类型
// a instanceof b 判断a的类型是不是b,返回布尔值
console.log(p1 instanceof Person) // true
# 6. this指针
a. 全局使用 --- window(没有意义,甚至可以说是bug)
b. 对象.方法() --- 包括事件 --- 指定调用的对象
c. 箭头函数 --- 指向外层作用域
d. 构造函数中的this指向创建出来的新对象
// 1. 全局使用 --- window
console.log(this) // window
function fn(){
console.log(this)
}
fn() // window
// 2. 对象.方法() --- 指定调用的对象
var obj = {
name:"张三",
fn
}
obj.fn() // obj
// 3. 箭头函数 --- 指向外层作用域
var obj = {
name: "张三",
eat: () => {
console.log(this);
},
};
obj.eat(); // window
// 4. 构造函数 --- 指向创建的新对象
let _this = null;
function Person(name, age) {
_this = this;
this.name = name;
this.age = age;
this.eat = function () {};
console.log(this);
}
var p = new Person("张三", 20);
p.eat(); // Person {name: '张三', age: 20, eat: ƒ}
console.log(_this); // Person {name: '张三', age: 20, eat: ƒ}
# 7. 原型对象
# 面试题
# 1. 立即执行函数
# 2. 闭包
# 函数中返回一个函数的结构,就称为闭包
# a. 函数外部如何访问局部变量
// 局部变量拿到外部访问
/*
function fn(){
var a = 10;
// 直接返回变量a ----- 返回的是值 10
return a;
}
var a = fn();
console.log(a())
*/
function fn(){
var a = 10;
// 返回的是一个函数 ----- 返回的是一个执行环境
return function(){
return a;
}
}
var a = fn(); // 函数fn调用完的时候,局部变量a还没有被销毁,并且永远不会被销毁
console.log(a())
特点:
防止全局变量的污染问题
保护了私有变量的安全,不会被修改
让函数外部访问局部变量成为可能,打破了作用域的限制,延长了变量的生命周期
会造成内存泄漏的问题(有一块内存永远被占用不会释放)
# b. 定时器事件和闭包的执行
# c. DOM事件和闭包的执行
# 3. 递归
# a. 递归的基本结构
# b. 拷贝
# 浅拷贝实现
# 深拷贝实现
# c. 排序
# 冒泡排序法
# 快速排序法
# 4. 设计模式
# a. 单例模式
保证一个构造函数有且只有一个实例
var instance = null
function Persion(name){
if(instance){
// 第二次实例化 ------ p2 李四
// instance = p1 ------ 此时已经产生实例了
return instance;
}else{
// 第一次实例化 ------ p1 张三
// instance = null
this.name = name
instance = this
// 此时 instance = p1
}
}
var p1 = new Persion('张三')
var p2 = new Persion('李四')
// 正常来说创建出来的是两个实例 结果是false
console.log(p1===p2) // true
# b. 观察者模式
定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变,所有依赖它的对象都将得到通知
观察者模式一般有两个角色,被观察者和观察者,被观察者状态发生改变,观察者会做出不同的状态
// 1.创建一个被观察者类
class Subject{
constructor(name){
this.name=name;
this.state = 0; // 初始化状态
// 7.2 两个类的实例产生关系
this.obs = []
}
// 3. 设置状态
setState(val){
this.state = val;
// 9. 被观察者状态改变,观察者立刻得到通知
this.obs.forEach(item=>{
item.updateState();
})
}
// 4. 获取状态
getState(){
return this.state
}
// 7.3 两个类的实例产生关系
appendObs(obsInstance){
this.obs.push(obsInstance)
}
}
// 2. 创建一个观察者类
class Observer{
constructor(name,sub){
this.name=name
// 10. 状态值改为
this.stateVal = ['初始状态','状态改变']
// 7.1 两个类的实例产生关系
this.sub = sub
this.sub.appendObs(this)
}
// 8. 接收状态改变
updateState(){
console.log(this.sub.name+"告诉"+this.name+"状态改变了"+this.stateVal[this.sub.state])
}
}
// 5. 创建被观察者实例
let s = new Subject('被观察者')
// 6. 创建多个观察者
let o1 = new Observer('张三',s)
let o2 = new Observer('李四',s)
let o3 = new Observer('王五',s)
s.setState(1)
# c. 发布订阅者模式
# 5. 原生ajax
# a. 兼容处理
# b. 响应处理和响应流程
# c. 使用ajax发送get请求
# d. 使用ajax发送post请求
# 6. JSON数据处理
# a. eval函数的基本使用
# b. JSON数据格式
# 7. 正则表达式
# a. 正则的创建
# b. 正则表达式的规则
# 8. 异步任务
# a. 执行栈
执行栈的调用规则:先入后出(包括栈内存的存储顺序)
也叫执行环境的入栈(压栈)和出栈(弹栈)
console.log("全局环境开始")
let a = 10;
function first (){
console.log("函数1")
second()
console.log("再次回到函数1")
}
function second(){
console.log("函数2")
}
first();
console.log("全局函数结束")
/*
全局环境开始
函数1
函数2
再次回到函数1
全局函数结束
*/
- 首先是全局的执行环境入栈
- 在全局环境下调用了first函数,再把first函数的环境压入栈中
- 在first函数里面调用了second函数,再把second函数的环境压入栈中
- second执行完毕,于是把second的执行环境从栈中移除(先进后出,后入先出)
- 回到first的执行环境,再把first的代码执行完成,从执行栈中再移除
- 最后把全局的执行环境也出栈,整个程序执行完成
# b. EventLoop
# c. Task宏任务和MicroTask微任务
# 9. 编程题
ES7 →