ES6是为了简化代码,当你发现不能简化的时候,用ES5

# 1. ES6变量声明

# a. var的弊端

  1. var声明的变量存在预解析,可以先使用再声明,容易造成逻辑混乱

  2. var可以重复定义一个变量,容易造成逻辑错误

  3. for循环中使用var存在全局变量污染(无视块级作用域)

# b. let 声明变量

  1. let没有预解析,必须先定义再使用
  2. let不能在同一作用域下重复定义一个变量
  3. let在for循环中不存在全局变量污染问题
    • 用let定义的i只能在for循环的内部去使用,for循环结束,变量被回收掉。
  4. 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 声明常量

  1. const没有预解析,必须先定义再使用
  2. const一旦声明,必须赋值,一旦赋值,不能修改
    • 引用类型其值可变,因为引用类型绑定的是内存地址
  3. const拥有块级作用域
  4. 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的使用

  1. rest得到的是真数组,可以使用数组的方法,如:foreach
  2. 获取剩余参数时,rest只能放到最后,作为最后一个形参,否则会报错
  3. 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()

# 箭头函数简写

  1. 函数体只有一句话,{} 可以省略
  2. 函数体只有一句话(并且要返回),{}和return 都可以省略
  3. 函数只有一个参数,可以省略 ()
  4. 多参多行函数体,不能简写
  5. 返回值是一个对象时,不能简写
  6. 箭头函数没有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() ,形参是一个回调函数,回调函数有两个参数,分别是:resolvereject,这两个参数也是函数。

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对象])

统一处理多个异步程序,类似 && 的关系(一失败就失败,全成功才成功)

# 特点:
  1. 如果多个异步程序都是成功状态,p的状态就是成功的,多个异步程序的成功结果会打包成一个数组统一返回

  2. 但凡发现一个失败,最快得到失败结果的直接返回

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. 自定义构造函数创建对象

构造函数本质上还是一个函数

  1. 构造函数首字母大写(约定俗成)
  2. 不需要手动创建对象,会自动创建
  3. 给this身上添加成员
  4. 不需要手动返回,会自动返回
  5. 必须搭配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())

特点:

  1. 防止全局变量的污染问题

  2. 保护了私有变量的安全,不会被修改

  3. 让函数外部访问局部变量成为可能,打破了作用域的限制,延长了变量的生命周期

  4. 会造成内存泄漏的问题(有一块内存永远被占用不会释放)

# 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
	全局函数结束
*/
  1. 首先是全局的执行环境入栈
  2. 在全局环境下调用了first函数,再把first函数的环境压入栈中
  3. 在first函数里面调用了second函数,再把second函数的环境压入栈中
  4. second执行完毕,于是把second的执行环境从栈中移除(先进后出,后入先出)
  5. 回到first的执行环境,再把first的代码执行完成,从执行栈中再移除
  6. 最后把全局的执行环境也出栈,整个程序执行完成

# b. EventLoop

# c. Task宏任务和MicroTask微任务

# 9. 编程题