大纲
JavaScript
数据类型 代码运行 类型转换 作用域等
- 基本数据类型(原始值类型)
1. number -> NaN/Infinity2. string -> 单引号/双引号/反引号3. boolean -> true / false4. undefined5. null6. symbol -> 创建唯一值let val = Symbol('00')console.log(val === val) trueconsole.log( Symbol('00') === Symbol('00')) false7. bigint -> 在一个数值后面加n 就是bigint
- 引用数据类型(复杂类型)
1. object {}普通对象 []数组对象 // 正则对象 日期对象 等等2. function
- 代码的运行
1.基本数据类型
1. 浏览器 之所以能够运行代码 是因为提供了一个供代码运行环境2. 这个运行环境 -> 叫 栈内存 ECStack(Execution Context Stack)3. 栈内存 就是在计算机中内存中分配出来的一块内存 用来执行我们的代码4. 代码的执行 分为 全局代码 函数中代码 私有块中的代码 不同的代码执行 都有自己的上下文(环境) 这个上下文环境 叫EC(Execution Context)5. 当我们的代码开始执行的时候 一开始 就会生成一个 EC(G)的全局执行上下文环境 用来执行我们的全局代码 之后 这个环境会进栈 因为代码都是在我们的栈内存中执行的6. 进栈之后 我们会创建VO(G) 全局变量对象 代码在当前上下文中执行的时候 创建的变量会存储在当前上下文中指定的变量对象中 所以变量对象 就是用来存储当前上下文中创建的变量的7. 以上环境都好了 开始执行代码之前 要变量提升 / 词法解析 等一些列 然后才开始执行 代码8. 赋值的详细步骤 var a = 12+ 创建一个值12 把它存储起来(基本数据类型是存储在栈内存中)+ 生命一个变量 把它存储到当前上下文所属的变量对象中+ 最后进行等号赋值(定义) 本质上也是一个指针指向的过程

2.复杂数据类型
1. 基本上前面几个步骤还是一样的 创建执行环境栈(GO) 创建全局变量对象 然后代码执行2. 这里只不过 我们的数据类型是引用数据类型 不会直接存储在栈内存中 会开辟一个堆内存 用来存储自己的键值对 每一个堆内存都有一个16进制的地址3. 在堆内存中分别存储键值对4. 把16进制地址放到栈中 供变量调用

- 全局对象
1. 浏览器在最开始加载代码的时候 不仅提供了一个栈内存 供代码执行 而且还默认开辟了一个堆内存 存储一些内置的属性和方法(GO Global Object)全局对象 这堆内存的地址给了window2. 也就是说我们的全局变量对象上(VO)在刚开是的时候就有一个叫window的变量 指向了我们的GO 也就是我们上面说的那个堆内存3. 我们刚刚知道在占执行期上下文的时候 还产生一个全局变量对象 用来存放我们的全局代码的变量 这个 跟我们的全局对象是两个概念 全局对象指的是我们的WINDOW
- 声明变量都做了什么
1. 在全局上下文中 基于 var 和 function 声明的全局变量 也会给GO(window)中新增一个对象的私有属性(这里切记 基于var 和 function才会这样 如果是let const 声明的 就不会给window中添加私有属性)2. 这个window中新增的私有属性 会和我们刚刚声明的全局变量 有 映射机制 一个变 另外一个也会变
var a = 10//声明了一个全局变量a 给我们的window上也增加了一个私有属性aconsole.log(a)// 首先看看a是否为全局变量 如果是 按照全局变量处理 如果不是全局变量 再看看 是否是window中的一个属性 如果也不是 则报错 a is not defined
- 函数底层运行机制
var x = [12, 23];function fn(y) {y[0] = 100;y = [100];y[1] = 200;console.log(y); //输出[100,200]}fn(x);console.log(x); //[100,23]
- 创建函数
1. 这里因为函数也是引用类型的数据 所以 我们在创建函数的时候 也就是声明函数的时候 还会开辟一块 堆内存 然后把地址存放在栈内存中2. 堆内存中 存放的是函数体里面的代码 会当作字符串 这里当然也有键值对的情况 创建好了之后就等着函数被调用了3. 在创建的函数的时候 我们就已经声明了函数的作用域 指就是我们函数所在的上下文4. 比如我们上面的的代码 我们的函数 所处的上下文 就是EC(G)5. 函数创建了 单步执行 就是相当于存放了一大堆的字符串 一点用都没有

- 执行函数
1. 函数执行的目的是 把之前创建的函数时候 在函数体中存储的代码字符串去执行2. 首先会形成一个全新的私有的上下文 比如我们在全局代码执行的时候 会形成一个EC(G)的全局上下文 这里也是一样的3. 自己私有的上下文 创建好了之后 还会生成一个自己私有的 私有变量对象控件 叫AO 这就就是存放我们的内部的私有变量 比如 形参 比如 在函数体内部 声明的变量4. 然后开始进栈 准备执行 执行之前我还有好多事情去做5. 初始化作用域链 作用域两是一个两端的结构 左边是当前函数执行的时候自己生成的上下文 右端是你函数创建的时候 所在的作用域 这样就可以说明 当我们查找变量的时候 先去自己的执行上下文 中去找 如果没有 就看看作用域链 去自己的作用域中去找 如果在没有 就接着向上找6. 初始化 this指向 初始化 arguments 形参赋值 变量提升 等7. 然后执行8. 执行完毕之后 为了优化栈内存 会把形成的私有上下文 出栈释放掉(GC浏览器垃圾回收机制)

数据类型转换规则
把其他数据类型转换为number
1. 特定需要转换为number的+Number([val])+parseInt/parseFloat([val])2. 隐式转换(浏览器内部默认要先转为Number在进行计算)+isNaN([val])+数学运算
- 把其他数据类型转换为字符串
1.能使用的方法 toString() String()2.隐式转换 加号运算的时候 如果某一边出现字符串 则式字符串拼接把对象转换为数字 需要先toString()转换为字符串 在去转换为数字
- 把其他数据转换为布尔
1. 基于以下方式可以把其他数据类型转换为布尔 !转换为布尔值后取反 boolean([val])2. 隐式转换 在循环或者条件判断中 条件处理的结果就式布尔类型值3. 规则 只有 0 NaN null undefined 空字符串 五个之转换会变成布尔
- 在==比较过程中 数据转换的规则
1.类型一样的几个特点{} == {} false 对象比较的是堆内存中的地址[] == [] falseNaN == NaN false
2. 类型不一样的转换规则null == undefined true 但是 null ==== undefined 结果是false(因为类型不一样)剩下的null / undefined 和其他任何数据类型值都不相等
3.字符串 == 对象 要先把对象转换为字符串
4. 剩下的如果 == 两边数据类型不一样 都是要转换为 数字在进行比较
//示例[] == false对象 == 布尔 两边类型不相等 都转换为数字对象转换为数字 先转换为字符串(先基于valueOf获得原始值 如果没有在去toString) 在转换为数字 空数组变成字符串 为 空字符串 ''字符串转成数字为0 false转换为数字为0
- 对象转换为字符串
1. {} 在转换为字符串的时候 为 "[object object]" 其余的都是用双引号包裹起来2. 因为对象在转为字符串的时候 调取的toString方法 是调取Object.prototype.toString 而这个方法 是检测数据类型的
- 把其他类型转换为数字
Number('') 0 Number('10') 10 Number('10px') NaNNumber(true) 1 Number(false) 0 Number(null) 0Number(undefined) NaNNumber(Symbol(10)) //转不了symbol 报错//对象变为数字 应该先valueOf获取原始值 如果没有原始值 在toString 变为字符串 最后把字符串变为数字
- parseInt机制
1. 把转换的值先转换为字符串2. 机制就从字符串的左侧第一个字符串开始 查找有效数字字符 遇到非有效数字 就停止不在找了 把找到的有效数字字符转换为数字 一个都没有找到 结果是NaN3. parseFloat 比 parseInt 多检测一个小数点
//示例parseInt('1.2px') // 1parseInt('') //NaN 先从字符串左侧开始找到有限数字 如果找不到 就返回NaN 所以结果是NaNNumber('') //0isNaN('') // false这个会隐式转换为Number 是 0 0 不是NaN 所以是fanlseParseInt(null)// NaN 先转换为字符串为 'null'然后从左侧第一个开始查找 找不到有限数字 结果是NaNNumber(null) // 0isNaN(null) //falseparseInt('12px') // 12Number('12px') //NaNisNaN('12px')// trueisNaN(Number(!!Number(parseInt('0.8')))) //false//parseInt('0.8') -> 0//!!Number(0) -> false//Number(false) -> 0// isNaN(0) -> falsetypeof !parseInt(null) + !isNaN(null)//!parseInt(null) -> !NaN -> true//typeof true -> 'boolean'// !isNaN(null) -> isNaN(null) -> !false -> true// 'booleantrue'
//示例let result = 10 + false + undefined + [] + 'Tencent' + true + {}10 + false => 10 + 0 =>1010 + undefined => 10 + NaN => NaNNaN + [] => NaN + '' => 'NaN'//后面的 就都是字符串拼接了'NaNTencentTencentTencenttrue[object object]'
- 小知识
+ 号 即使一边出现字符串 或者对象 也不一定是字符串拼接 ++/+ 这种情况console.log(++'10') //11console.log(+'10') //number 10{} + 0 //0 会把左边的{} 认为是一个代码块 不参与运算 只算+00 + {} //"0 [object object]" 这是数学运算
- 变量声明
1. 变量声明一共有5种方案 var function let const import2. let 和 const 声明的都是变量 const 声明的变量是不允许指针重新指向的(但是存储的值是可以改变的 比如 存储的值是引用数据类型 可以基于地址改变堆内存种的信息)3. 我们赋值本身 就是指针关联或者指针指向的过程
// var 和 let 的区别+ var fuction 存在变量提升 let 和 const 不存在+ 在相同的上下文种 let 是不允许重复声明的 (而且检测是否重复声明 是发生在词法解析阶段) 词法解析 -> 变量提升 -> 代码执行+ 暂时性死区 (浏览器遗留的BUG) 基于typeof 检测一个未被声明的变量 不会报错 结果是undefined+ 除了函数或者对象的大括号之外 如果括号中 出现let / const / function 则会产生块级私有上下文 当前块级上下文也只是对 let / const / function 他们声明的变量有作用
//变量提升阶段console.log(fn) // undefinedif( 1== 1){console.log(fn) //函数 私有的function fn() { console.log('ok')}fn = 12console.log(fn) //12}console.log(fn) //函数老版本浏览器 不管条件是否成立 变量 和 函数都会提升 函数也会定义新版本 不管条件是否成立 函数都会提升 但不会赋值(比如在判断体 循环体 代码块 ) 他只会进行声明 不会赋值和定义如果以上的大括号种出现了function xxx(){} 此时当前大括号会形成一个私有的上下文 第一件事情 就是变量提升 他会把函数声明 加定义

- 变量提升机制
1. 变量提升发生在 当前上下文中 (全局/私有/块级) js代码自上而下执行之前浏览器会提前处理一些事情2. 带var的 只会提前声明 带function的会 提前声明加定义 就是函数整体提升3. 函数表达式的方式 var fn = function(){} 只是会吧fn的变量 提升4. let 和 const 是不进行变量提升的 只有带var 和functiond的才会5. 基于全局上下文中声明的变量 和函数 会映射到全局变量 window上一份 作为它的属性 一个修改 另外一个也修改6. 不论条件是否成立 都要进行变量提升
//具名话 针对于函数表达式1. 我们原本 函数表达式 后面会写一个 匿名函数 但是现在 我们写了一个AAA的名字var func = function AAA(){//但是这个具名的话的函数名字 可以在自己的当前上下文执行//可以递归使用AAA()}2. 但是这个名字 在全局 是没有作用的AAA() //AAA调用 会报错
// let 和 const 没有 变量提升 所以 报错也不一样console.log(a) // Cannot access 'a' before initializationlet a = 12
// 不论条件是否成立 都要进行变量提升(细节点:条件中带function的在新版本浏览器中只会提前声明 不会在提前赋值了)1. 这里 就是 你下面不管条件是否成立不成立 都要提升一个a变量if(!('a' in window)){var a = 1function fn(){}}console.log(a)[老版本 浏览器]var a fn = 函数[新版本 浏览器(IE10以上)]var a fn
fn()function fn(){console.log(1)}fn()function fn(){console.log(2)}fn()var fn = function (){console.log(3)}fn()function fn(){console.log(4)}fn()function fn(){console.log(5)}fn()/* EC(G)* 1. 首先函数整体提升 fn()都上去了* 2. 当走到 var fn = function (){console.log(3)} 发现已经有fn的函数了 就先不处理* 3. 然后代码开始执行 前三个fn调用输出 都是5* 4. 当走到 fn = function (){console.log(3)} 开始给fn函数重新赋值了 所以后三个输出都是3*/
- 块级作用域的变量提升机制
1. 如果我们的代码 出现在了除函数 和 对象意外的花括号里面 那么就会产生一个块级上下文2. 新版本浏览器中 如果出现在块级作用域里面的代码 那么变量只声明不复制3. 当我们执行代码的时候 会产生映射机制 也就是出现在块级作用域里的变量声明 或者函数执行的 之前的代码 都会映射到全局 而之后的代码 都会是私有的

- 形参默认值
1. 如果我们的函数形参有默认值了 而且 我们函数里有var声明的了变量 那么这个声明的变量将会形成一个块级作用域2. 如果是let 声明的变量 那么就会报错3. 并且块级私有上下文的上级上下文 就是函数的私有上下文

- 闭包作用域和高级编程技巧
1. 一般情况下 函数执行完成之后 所有形成的私有上下文 都会出栈释放 私有上下文中的一切内容都会被销毁 主要是为了优化栈内存控件2. 特殊情况 如果函数执行所形成上下文中 有一个东西(引用类型的空间地址) 被当前上下文 以外的事物所占用 那么当前上下文 是不能被释放的 上下文中的信息保留下来了(包含私有变量) 导致栈内存空间变大 如果返回的地址还会被执行一次 所以也临时不会释放 当 外部用完 就会释放3. 函数每次执行 都会形成一个全新的私有上下文
let x = 5function fn (x){//这里的return 就当是在堆内存中又开辟了一个空间 把函数的地址返回了return function(y){console.log(y + (++x))}}let f = fn(6) //这里其实就是返回了一个地址 而这种就是我们上面所说的 特殊情况f(7)fn(8)(9)f(10)console.log(x)

let a = 0 b =0function A (a) {//这种地址也会被引用 因为在大A是全局的变量 执行到这里的时候 我们相当于把A的地址又重新赋值了A = function(b){alert(a + b ++)}alert(a++)}A(1) // 弹框1A(2) // 弹框4

//示例var buttonList = document.querySelectorAll('button');/*// 实现不了的,为啥?// + 循环中的i是全局的,每一轮循环给对应元素的click绑定方法(创建函数 [存储代码字符串],此时函数没有执行)// + 循环结束的时候,全局的i=5// + 点击某个按钮,执行之前绑定的函数:此时形成一个全新的私有上下文,它的上级上下文是全局上下文,函数代码执行的过程中,遇到变量i,但是i不是自己的私有变量,找到的是全局的i,全局的i是5*/for (var i = 0; i < buttonList.length; i++) {buttonList[i].onclick = function () {console.log(i, `我是第${i+1}个按钮~`);};}//最后每次点击按钮之后 打印出来的都是5

//解决// 解决问题的思路:当点击事件触发,执行对应的函数,用到的i不要再向全局查找了;相当于自己形成一个上下文,而自己的上下文中,存储了你需要的i,存储的值是指定的索引即可 =>闭包的保存机制// 弊端:循环多少次,就产生多少个闭包,非常消耗内存for (var i = 0; i < buttonList.length; i++) {// 每一轮循环都会把自执行函数执行,形成一个全新的私有上下文(一共形成了5个)// + 把当前这一轮全局i的值作为实参,传递给当前形成的私有上下文中的形参n[私有变量]// + 第一个私有上下文中的n=0,第二个私有上下文中的n=1 ....// 每一个形成的上下文中,创建的函数都被外部的元素对象的onclick占用了,所以形成了5个闭包// 当点击按钮执行函数的时候,遇到一个变量n,不是自己私有的,则找上级上下文(闭包)中的n,而n存储的值就是它的索引(function (n) {buttonList[n].onclick = function () {console.log(`我是第${n+1}个按钮~`);};})(i);}
for (var i = 0; i < buttonList.length; i++) {buttonList[i].onclick = (function (i) {// i是每一轮形成的闭包中的私有变量,五个闭包中存储的值分别是0~4[索引]// 每一次都是把小函数返回,赋值给元素的点击事件,当点击元素的时候,执行返回的小函数return function () {console.log(`我是第${i+1}个按钮~`);};})(i);}
// 还是基于“闭包的机制”,但是不是自己去执行函数构建,而是利用ES6中let产生的私有上下文实现for (let i = 0; i < buttonList.length; i++) {// 第一轮循环 私有块1// + 私有变量 i = 0// + 当前私有上下文中的创建的一个函数被全局的元素对象的onclick占用了(闭包)// ....buttonList[i].onclick = function () {console.log(`我是第${i+1}个按钮~`);};}
- let 的 for 循环机制
1. 循环第一轮 会形成一个父级的私有块上下文 循环结束后就会销毁2. 父级块上下文 都会把变量 传送给下面的子集上下文3. 然后开始循环 每一轮都会形成一个私有块上下文 由于被我们的click 占用 所以不会销毁
- 事件绑定解决方案
1. 无论闭包还是let 我们都是用闭包解决的2. 还有第二种解决方案 给每个DOM对象加个自定义属性3. 利用事件代理的机制(性能提高>=40%)
// 方案二:自定义属性(事先把一些信息存储到元素的身上,后期在一些其他的操作中,想要获取这些信息,直接基于元素的属性访问就可以拿到这些值) =>操作DOM的时代下,这种方案非常常用for (var i = 0; i < buttonList.length; i++) {// 把当前按钮的索引存储在它的自定义属性上(每个按钮都是一个元素对象)buttonList[i].myIndex = i;buttonList[i].onclick = function () {// 给当前元素的某个事件绑定方法,当事件触发,方法执行的时候,方法中的this是当前点击的按钮console.log(`我是第${this.myIndex+1}个按钮~`);};}
// 方案三:利用事件代理的机制(性能提高>=40%)<button index="1">按钮1</button><button index="2">按钮2</button><button index="3">按钮3</button><button index="4">按钮4</button>document.body.onclick = function (ev) {let target = ev.target;// 点击的是按钮if (target.tagName === "BUTTON") {let index = target.getAttribute('index');console.log(`我是第${+index+1}个按钮~`);}};
- 短路语句
A || B A的值是真 返回A的值 否则返回B的值A && B A的值是真 返回B的值 否则返回A的值如果同时存在 &&高于||
- This
1. this 函数的执行主体 和执行上下文不是一个概念2. 全局的this 是 window 我们研究中的都是函数中的this
- this 的执行规律
1. 给当前元素的某个事件行为绑定方法 事件触发 执行对应的方法 方法中的this 是当前事件本身
document.body.click = function () { // this-> body }// IE 6/7/8 基于 attachEvent 绑定的不是this 是 window
2. 函数执行 首先看函数名称之前 是否有 点 有点 点前面是谁 this 就是谁 如果没有 点 this 就是window(严格模式下 没有 点 this 是 undefined)
3. 自调用函数/回调函数 中的this 一般都是window 或者 undefined h
4. 构造函数中的this 是当前类的实例5. 箭头函数中没有自己的this 用到的this 都是上下文中的this6. 基于call / apply / bind 可以强制改变this指向
- arguments 形参映射机制
// EC(G)var = 4function b (x,y,a) {// 形成自己的私有执行上下文 EC(b)// 初始化作用域链 <EC(b),EC(G)>//初始化this:window//初始化 arguments:{0:1,1:2,2:3,length:3} 左侧是真实结构 为了方便看我们携程数组[1,2,3] 这里还会和我们的形参形成映射机制// 形参赋值 x =1 y =2 a =3//变量提升console.log(a) // 3arguments[2] = 10console.log(a) //10}a = b(1,2,3)console.log(a)
- 模块化 / 单列模式设计模式
1. 在很早以前没有引用类型 比如对象 和 函数的时候 想描述个人信息的时候 就需要一个变量一个变量的去定义 这个时候 就会造成全局变量的冲突
//比如 我们描述 欧阳花的个人信息let name = "欧阳花" let age = 21 let sex = '男'//还想描述 吴家乐的个人信息let name = '吴家乐' let age = 25 let sex = '女'//以上我们看到 变量就冲突了
2. 所以我们开始用闭包的方式 去描述 因为 闭包里面的变量都是私有变量 不会跟外部产生冲突
//闭包的方式去描述(function(){let name = "欧阳花"let age = 21let sex = '男'})()(function(){let name = '吴家乐'let age = 25let sex = '女'})()
3. 但是以上 我们如果要在吴家乐那的作用域下 用到 欧阳花里面的变量 只能添加到window上 这种方式 如果变量不是很多还好
// 真实业务开发 B 里面可能要用到 A 的方法 就需要把A的方法暴漏到windows上去A(function(){let set = 0function fn(){}function query () {}window.query = query})()B 里面要用到A 里面的query方法(function(){let set = 0window.query()})()
//所以这里 我们就用暴漏对象的方法 把要使用的方法 return 出去 这种方式就叫单例模式let AModule = (function(){let set = 0function fn(){}function query () {}return {query,set}})()// B 模块(function(){let set = 0AModule.query()})()
4. 在我们自己编写类库/插件/UI组件/框架的时候 为了防止全局变量污染 我们需要基于闭包的机制进行 "私有化" 处理
(function(){function Banner(){}//利用暂时性死区 如果一个变量没有声明 用typeof检测 是undefined(浏览器可以typeof 可以检测到window 是 'object') 我们利用这个特点可以区分 是浏览器执行js 还是node执行jsif(typeof window !=='undefined'){window.Banner = Banner}//如果是CommonJS / ES6Module 就会检测到下面两个东西if(typeof module !== 'undefined' && typeof module.exports !== 'undefined'){module.exports = Banner}})()
- 惰性函数
1. 能执行一次 绝对不会执行第二次
// 我们在获取元素样式的时候 用到的API 有 getComputedStyle 和 currentStyle(这个方法在IE6-8)function getCss(elemnt,attr){if(window.getComputedStyle){return window.getComputedStyle(elemnt)[attr]}return elemnt.currentStyle[attr]}//然后我们开始获取元素的样式console.log(getCss(document.body,'width'))console.log(getCss(document.body,'padding'))console.log(getCss(document.body,'margin'))//总结 这样好么 因为我们每执行一次函数 都需要处理兼容性问题 其实没必要 因为在第一检测时候就已经知道兼容情况了
function getCss(elemnt,attr){if(window.getComputedStyle){//走到判断 就可以判断我们是哪个浏览器的兼容了 然后改变getCss的指针getCss = function (elemnt,attr) {return window.getComputedStyle(elemnt)[attr]}}else{getCss = function(elemnt,attr){return elemnt.currentStyle[attr]}}// 改变之后 要保证我们的第一个函数要执行一次 拿到结果return getCss(element,attr)}//第一次执行函数console.log(getCss(document.body,'width'))、// 第二次走的时候 就直接走到 我们改变指针的那个函数了console.log(getCss(document.body,'padding'))console.log(getCss(document.body,'margin'))//以上我们 再次执行的是时候 是重构后的小函数 告别了 兼容校验的操作 只执行一次
2. 惰性函数 就是函数重构的问题 第一次执行 会产生闭包
- 柯里化函数
1. 区别于惰性函数 是一个预处理的思想 应用的也是闭包的机制
let res = fun(1,2)(3)console.log(res) //求和结果 为6//实现以上函数 首先第一函数执行的时候 我们不知道传进来几个参数 第二次也不知道 所以我们用剩余形参去接收//然后把两个函数的参数 连接在一起 在最总进行求和function fn (...outerArgs){//剩余参数 会把你的实参放在一个数组中 而arguments是一个伪数组return function anonymous(...innerArgs){let arr = outerArgs.concat(innerArgs)// 如果reduce里面 没有传第二个形参 那么total 就是数组中的第一项 item是第二项 然后相加就变成3 然后把3作为total 在和 后面的一项相加return arr.reduce((total,item) => {return total + item},这里还可以传第二个形参)}}
2. 第一次执行大函数 形成一个闭包(原因:返回了一个小函数) 把一些信息存储到了闭包中(传递的实参信息或者当前闭包中声明的一些私有变量等信息) 等到后面需要把返回的小函数anonymous执行 遇到一些非自己私有变量 则向其上级上下文中查找
- 组合函数
1. 把处理数据的函数像管道一样连接起来 然后让数据传过管道得到 最终的结果2. 以下 就是我们把每个函数的运行结果 当作形参 传递给了下一个函数 参与了运算
const add1 = (x) => x + 1const mul3 = (x) => x * 3const div2 = (x) => x / 2div2(mul3(add1(add1(0)))) // => 3
2. 上面的写法可读性明显很差 我们可以构建一个compose函数 它接收任意多个函数(而这些函数都只能接收一个参数) 然后compose 返回也是一个函数const operate = compose(div2,mul3,add1,add1)operate(0) 相当于 div2(mul3(add1(add1(0))))operate(2) 相当于 div2(mul3(add1(add1(2))))
function compose(...func){return function operate(x){//如果你一个数据处理函数也没有传进来 那么 就直接返回你的参数if(func.length === 0) return x//如果你只传进来一个事件处理函数 那么 我们就执行if(func.length === 1) return func[0](x)//这里我们要知道 我需要最先执行 func数组里的最后面的函数 然后结果当作参数传给倒数第二个函数里 一次类推 所以这里我们会用到reduce 所以先要把数组反转以下let n = 0func.reverse()return func.reduce((result,item) => {n++//说明这是第一次 result是add1函数 item是你传进来的第二次个add1函数if( n === 1){return item(result(x))}return item(result)})}}//下面是几种情况let operate = compose(div2,mul3,add1,add1)console.log(operate(0)) => 3//一个数据函数都没有传 就直接返回你的形参let operate = compose(div2,mul3,add1,add1)operate(0) => 0//如果只传进来一个数据处理函数 就 直接执行let operate = compose(add1)operate(0) => 0
- 函数的防抖
1. 对于频繁触发的某个操作 我们只识别一次
function handle() {setTimeout(() => {console.log('ok')},1000)}//如果我频繁点击submit这个按钮 他就会一直触发handle这个函数 一直输出OK 如果我们控制让它在300MS 无论你点几次 只触发一次submit.onclick = handle
2. 在当前点击完成之后 我们等wait这么长的时间 看是否还会触发第二次 如果没有触发第二次 属于频繁操作 我们直接执行想要执行的函数func 如果触发了第二次 则以前的不算 从当前这次再开始等待
// func 就是你要点击执行的那个函数 wait 就是我们控制在多少时间只内 只执行一次 inmmediate 就是我们让func这个函数 执行 你第一次触发 还是最后一次触发/** @params* func[function] 最后执行的函数* wait[number] 频繁设定的界限* inmmediate 默认多次操作 我们识别的是最后一次 但是inmmediate =true 让其识别第一次* @return 可以被调用执行的函数*/function debounce (func,wait = 300, inmmediate =false){//为什么return 因我们 submit.onclick = function (e) {} 也是执行一个匿名函数// 这里 我们可能还有事件对象 e 还有 事件源 this 也需要改变一下指向let timer = null //主要是为了清除定时器用return function anonymous(...params){//我在第一次点击的时候 先清除一下定时器 因为第一没有 所以函数接着往下走//第二次的时候 我们清除了之后 定时器函数里面就不执行了 然后又重新开启了一个定时器 把上一次的取消了clearTimeout(timer)timer = setTimeout(() => {//执行你传进来的函数 并且把this的指向指向你的事件源 并且把事件对象传进去func.call(this,...params)},wait)}}// 以上 就触发 了最后一次 基本的防抖 就已经实现了submit.onclick = function () {}
// 下面 我们要根据 inmmediate 来控制 只执行触发的第一次function debounce (func,wait = 300, inmmediate =false){let timer = nullreturn function anonymous(...params){let now = inmmediate && !timerclearTimeout(timer)//我第一次进来 还是跟以前一样 设置一个定时器timer = setTimeout(() => {//3. 手动让定时器回归到初始状态timer = null//2. 所以这里也要看看 如果是让我第一次执行 那我定时器里面的就都不执行了!inmmediate?func.call(this,...params) : null},wait)//1. 看看你是否让我立即执行 如果是 我就立即执行 但是同时你等了wait以后 定时器里面的也执行了now?func.call(this,...params):null}}// 以上 就触发 了最后一次 基本的防抖 就已经实现了submit.onclick = function () {}
- 函数的节流
1. 让函数 在一定时间内 执行相应的次数
function handle(){console.log('ok')}// 我们每一次的浏览器滚动过程中 都会触发上面那个函数 频率非常大 浏览器有最快反应时间(5~6ms 13-17ms) z只要反应过来了 就会触发函数window.onscroll = handle
2. 需要知道 我上一次触发的时间 到我这次的时间 小于我的频率时间 如果小于就不执行函数
// 每300ms触发一次/** @params* func[function]: 最后要触发执行的函数* wait[number]: 触发的频率* @return 可以被调用的执行函数*/function throttle (func,wait = 300){let timer = nullprevious = 0 //记录上一次操作的时间return function anonymous(...params){let now = new Date() //记录当前函数执行的时间remaining = wait - (now - previous) //记录还差多就达到我们一次触发的频率if(remaining <= 0){clearTimeout(timer)timer = null//等于我们的上一次触发时间previous = now//如果这个小于0 就说明我们已经过了间隔时间 可以触发第二次了func.call(this,...params)}else if(!timer){// 如果你第二次点击发现没有设置定时器我才设置 如果设置了我就不设置了 因为你第二次进来的时候发现我等的那个函数还没执行 所以我就不用设置定时器 只有你没有 我才设置// 这里就说明 我们还在我们频率时间内点击了第二次 所以不能触发 我们要等timer = setTimerout(() => {timer = nullprevious = new Date()func.call(this,...params)},remaining)}}}window.onscroll = throttle(handle)
面向对象
- 编程思想
1. OOP 面向对象 js java php python2. POP 面向过程 c语言
- 面向对象的概念
1. 我们js 所有的东西都可以理解为一个类2. 比如我们的对象 数组 它上面所有的方法 都是从上面继承下来的
「数据类型类:我们见到的数据值都是所属类的一个实例」+ Number+ String+ Boolean+ Symbol+ BigInt+ Object+ Object+ Array+ RegExp+ Date+ ...+ Function「DOM对象/节点或元素集合/样式对象或者集合等」+ HTMLDivElement / HTMLAnchorElement / HTMLUListElement ... 每一种元素对象都有一个自己所属的类+ HTMLElement / XMLElement ...+ Element / Text / Document...+ Node 节点类+ EventTarget+ HTMLCollection / NodeList ...+ CSSStyleDeclaration+ ......---------------学习JS基础知识,尤其是API层面的+ 类.prototype:存放的是给当前类的实例调用的公共属性方法+ 类.xxx:把其当作对象设定的静态私有属性方法+ ...document.getElementById('xxx')+ 获取上下文只能是document?+ getElementById是在Document类原型上提供的方法,只有Document它的实例才可以调用(document是它的实例)[context].getElementsByTagName('*')+ Element类的原型上提供getElementsByTagName方法+ 每一个元素标签都是Element类的实例+ Document的原型上也有这个方法,所以document也可以用+ ...
- 自定义类
1. 上面的那些和方法 都是自己去写的类 也就是js 已经你写好的2. 但是除了以上那些 还远远不够 所以 我们自己就又了自定义类 new class
//普通函数执行function sum (x,y){let total = x + ythis.total = totalreturn total}let res = sum(10,20)console.log(res) //输出30

// 当我们加了new 之后 执行的方式就不一样了function sum (x,y){let total = x + ythis.total = totalreturn total}let res = new sum(10,20)console.log(res) // {total:30} 执行结果
- 带 new 的函数执行都干了什么
1. new 的函数执行 首先会和普通函数一样 形成上下文EC(XXX) AO(xxx) 初始化作用域链 形参赋值 变量提升 等等2. new的函数执行 在代码执行之前 私有上下文之前 会创建一个空对象3. 然后初始化this的时候 就会让我们的this 指向我们创建的对象4. 后期在代码执行过程中 如果遇到this.xxx 都是给我们上面创建的那个实例对象创建私有的属性和方法5. 如果没有些 return 默认是把我们的创建的对象返回 如果自己写了 return 自己返回的是基本类型值 写也没用 还是返回我们上面创建的那个对象 如果返回的是引用类型值 返回的是你自己写的引用类型6. new Fn 也相当于把函数执行了 不带列表的 new 最后 Fn 都一定会执行 而且都会创造这个类的实例 区别 是否传递实参 以及运算优先级不一样7. 所有的类都是一个函数数据类型值 内置类/自定义类8. 每一次new 类创造的实例都是 独立的实例对象 这种模式在js中叫做 构造函数模式

- 面向对象 原型prototype 和 原型链proto
1. 每一个类(函数) 都具备 prototype(隐式原型) 并且属性值是一个对象2. prototype 对象上天生具备一个属性 constructor 指向类本身3. 每一个对象(普通对象、prototype、实例、函数等)都具备__proto__(显式原型) 属性值是当前实例所属的原型4. 所有的类都是函数数据类型(包括内置类) 所有的函数都天生自带一个属性 prototype 属性值默认是一个对象数据类型 其存储的是 供实例能调用的公共属性和方法5. 所有的对象数据类型值也天生自带一个属性__proto__原型链 它的属性值:当前类的原型prototype 实例.__proto__ = 类.prototype6. 如果我们调取了实例中的一个方法 它先看自己的实例上有没有 如果没有就去原型上找 这个就是原型链查找机制

//总结1. 首先我们每个函数上面都有一个 prototype(原型)属性 是一个对象 对象里面有我们的constructor 指向我们的函数本身2. 当new 执行函数的时候 初始化的this的时候 我们会单独开辟一块空间 创建一个实例对象 只要是对象就会有一个属性__proto__3. 每个对象上面 都有 一个__proto__ (原型链)也是一个对象 如果这个函数是new 执行的 那么 __proto__指向的就是我们函数的 prototype4. 也就是说 实例对象(new 执行函数的时候初始化this出来的那个对象).__proto__ = 类(也就是我们的构造函数).prototype5. 我们函数的 prototype(原型) 也是一个对象 那么它也有一个__proto__ 指向应该是构造我们的prototype的函数 那么这个函数就是 new Object()6. 所以 当我们一个类上的方法没有的时候 它就会去我们的原型上找 如果没有 就顺着原型链 一步一步的向上查找 最终找到我们的 Object 函数 这就是 原型链查找机制
- 原型重定向
1. 原型重定向 就是把 prototype 的指针指向一个新的对象2. 原型为啥要重定向 为了方便给原型上扩充属性和方法3. 原型重定向 带来的问题是 新定向的原型对象上 没有constructor属性 结构不完整 我们浏览器默认生成的原型对象因为缺少引用 会被释放掉 可能导致原始加入的属性和方法丢失掉
function Fn (){this.x = 100this.y = 200}// 原始的原型对象Fn.prototype.getX = function(){}//这个时候 就相当于给我们的原型重定向了 那么我们之前在prototype上的getX 这个方法也没有了 constructor也没有了 这种重定向 可以批量添加属性和方法Fn.prototype = {//如果原始的原型对象不存在其他的属性和方法 我们只需要手动添加constructor 指向我们的类constructor:FngetY:function(){},getX:function(){},getZ:function(){},}//如果我们的原型对象上有别的属性 我们就需要合并进来/** Fn.prototype = Object.assgin(Fn.prototype,{* getY:function(){},* getX:function(){},* getZ:function(){},* })**/let f1 = new Fn

- 原型重写 new
1. 我们自己来实现一个_new函数2. 这里就需要知道new的时候 都干了什么3. 首先 new 函数执行 和普通函数的执行一样 形成私有上下文 初始作用域链 形参赋值 变量提升 代码执行4. 创建了一个空对象 this 指向了它 对象.__proto__ = Dog.prototype5. 把this 指向 指向这个我们创建的空对象 如果没有 return 或者返回的是基本类型值 则返回都是实例对象 否则返回我们自己定义的对象
function Dog(name,age){this.name = name}Dog.prototype.bark = function () {console.log('wangwang')}Dog.prototype.sanName = function () {console.log('my name is' + this.name)}function _new (func,...params){//1. 首先我们要创建一个实例对象let obj = {}obj.__proto__ = func.prototype//2. 把类当作普通函数执行 并且让this 指向我们的实例对象let res = func.call(obj,...params)//3. 根据返回结果 决定返回啥if(res !== null && (typeof res === 'object' || typeof res === 'function')) return resreturn obj}let sanmao = _new(Dog,'dahuang',23)sanmao.bark()samao.sayName()// IE 我们使用不了__proto__ 所以就要用到下面的方法// 创建了一个空对象 并且把对象A作为它的原型 空对象.__proto__ = 对象AObject.create(obj)
- 基于内置类原型扩充方法
1. 我们知道内置类 比如 Array Object 都是函数数据类型 就会有一个自己所属的堆内存 但是里面的代码我们看不到 就会有一个 native code2. 每一个函数 都会自带一个属性 叫 prototype 属性值是一个对象 自带一个属性constructor 指向自己构造函数本身3. 像内置类的原型上扩充方法 最好设置私有前缀 而且扩充方法里面的this 就是我们的当前实例4. 必须要保证 this 是我们当前要操作的实例
// 我们用 数组去重举例//我们arr 都是我们 Array的实例 所以我们可以调用Array.prototype上的方法let arr = [1,2,3,3,1,8]Array.prototype.myUnique = function () {//这里面的this 就是我们实例本身 所以首先我们要保证 this 是个数组if(!Array.isArray(this)) throw new TypeError('不是一个数组')return Array.from(new Set(this))}arr.myUnique() //直接就可以去重
// 我们现在 实现一个加减 的方法let n = 10let m = n.plus(10).minus(5)console.log(m) // (10+10-5) 15//如果我们基于原型上扩充方法Number.prototype.plus = function(num) {//这里面我们输出this 发现并不是10 而是引用类型 Number(10) typeof this => 'object'//这里所有的this 都不是基础类型的值 都是引用类型的值// 如果我们想变成基础类型值 我们需要用call apply bind 改变this 指向 变成基础类型值console.log(this) // Number(10)// 我们知道 上面的就是个对象 对象 相加 就会变成 '[obejct object]10'// 但是我们这个方法是有原始值 会先调用valueOf获取原始值(number/string/boolean/Date)// 如果有原始值 就获取到结果即可并且参与到运算 如果没有就转换称字符串return this + num}Number.prototype.minus = function(num) {return this - num}
//基于以上的总结1. JS创建一个值有两种方法 一种是字面量 一种是构造函数let n = 10 //字面量let n = new Number(10) // number(10)2. 上面不论哪种方法 创建出来的都是一个实例3. 基础数据类型 上面的那辆创建方式 输出结果是不一样的 字面量 就是 10 构造函数创造出来的就是引用类型 Number(10)4. 我们字面量方式创造的值 在调用方法的时候 会默认 把字面量的方式 变成引用类型值 在去原型上调方法
- 函数的三种角色
1. 我们要知道 所有的函数 都是也都是 Function 的内置类 置换出来的2. 但是函数的内置类的原型 prototype 不是一个对象 是一个匿名空函数 但是操作上和其他类的原型一摸一样 没有啥却别3. 函数的三种角色普通函数执行 闭包作用域构造函数执行 new 类和实例普通对象 键值对4. 这三种角色之间没有必然的联系5. 这里我们知道 我们的内置类Array Object Number 这些也都是函数 那么函数也是一个对象 所以他们作为普函数来说 都会有__proto__ 都会指向我们 Function 的prototype

//实例function Foo() {getName = function () {console.log(1);};return this;}Foo.getName = function () {console.log(2);};Foo.prototype.getName = function () {console.log(3);};var getName = function () {console.log(4);};function getName() {console.log(5);}// 首先 函数记得有三种角色 第一种是 当作普通函数执行 第二种是 new 执行 第三种是 作为对象// 这里就相当于 我们把Foo当作一个对象 访问了它的getName属性执行 所所以输出结果是 2Foo.getName(); // 2// 这里就是执行全局的函数getName() 全局我们可以看到 第一次函数是5 但是 后来 有重新把函数赋值了 所以输出 是4getName(); // 4// 这里是当作普通函数执行 普通函数执行return this 这里的this 是window 但是 函数执行的时候 又把全局的getName()重新写了 所以就相当于调window.getName() 是1Foo().getName(); // 1//因为全局的函数已经被重新了 所以输出是1getName(); // 1// 这就存在优先级的问题 是new 函数执行的优先级搞 还是 成员访问的优先级高// obj.xxx 访问的优先级 是 19 new Fn() 带参数 是19 new Fn 是18 如果优先级相同那么就从左向右一次执行// 所以我们要先执行Foo.getName() 就相当于 执行了 new function(){console.log(2)} new的时候函数也会执行 所以 输出的是2new Foo.getName(); //2// 这里 由于两个优先级 都是相同的 所以 从左向右一次执行 所以这里new Foo()执行 创建了实例对象 this 指向了这个实例对象 那么这个实例对象上没有getName() 就要像原型上找 也就是Foo.prototype.getName() //所以输出结果是3new Foo().getName(); //3//这里从上面一样 只不过最后当普通函数执行了 输出结果也是3new new Foo().getName(); //3

- THIS种的五种情况
1. 给当前元素的某个事件行为 绑定方法 当事件行为触发 方法执行 方法种的this 一般都是 当前操作元素 排除IE6~8中 基于 attachEvent进行的DOM2事件绑定 方法中的 this 是 window
document.body.onclick = function () { console.log(this) } //输出是bodydocument.body.addEventListenner('click',function(){console.log(this)}) //输出是body
2. 函数执行(看函数前面是否右点) 点前面是谁 this 就是谁 没有点 this 是window 严格模式下 是undefined 匿名函数(自调用函数/回调函数) 一般this 就是window
const fun = function () { console.log(this) }let obj = {namF:'obj',fn:fun}fun() //输出this 是 window 严格模式下 use strict undefinedobj.fn() // this 是obj(obj.fn)() //同上//括号表达式 括号里面有多项的情况下 只取最后一项(10,20,obj.fn)() // this 是window//自调用函数(function(){console.log(this)})() // window//数组的回调函数[1,2].sort(() => {console.log(this)}) // window// forEach[1,2,3].forEach((item)=>console.log(this),obj) //这里forEach处理了改变了this指向 就是obj
3. 构造函数 new 执行 函数体中的this 是当前的实例
function Fn(){this.x = 100}//普通函数执行 this 是windowFn()// new 执行 this 是实例本身 也就是fconst f = new Fn()Fn.prototype.say = function () { console.log(this.x) }// 本身私有属性上没有这个方法 就去原型prototype去找 由于是f的实例调取的 所以this 是ff.say()
4. 箭头函数中 或者 基于{} 形成的块级上下文 里面没有 this 如果 代码中有this 也不是函数自己的 而是 自己所在的上下文中的
let obj = {namF:'obj',fn(){setTimeout(function(){this.name = 'xxx'},1000)}}obj.fn() //这里面的定时器 this 是window// 如果是箭头函数 他就会去找上一层的this 也就是我们函数定义的上下文 上一层的this 取决于谁调用let obj = {namF:'obj',fn(){//或者我们也可以 把this 先存期来 let _that = thissetTimeout(() => {//_that.name = xxxthis.name = 'xxx'},1000)}}
5. 我们可以基于function.prototype 上的call / apply /bind 去改变this 指向 但是对于箭头函数无用
const fn = function fn (){console.log(this)}window.name = 'xxx'let obj = {namF:'obj',fn:fn}fn() // 输出 是 window.nameobj.fn() // 输出是 obj.name// 如果 obj 里面没有fn 属性 但是我们还想让fn的this 指向 obj 那么就要用到call appply bindfn.call(obj,这里面还可以传参)
// call 的执行逻辑fn.call(obj,10,20)首先 fn 基于原型链__proto__ 找到 Function.prototype.call 方法 并且把call 方法执行call 方法中的this 就是 当前操作的实例fn 传递给call 方法的第一个实参 是未来改变fn中的this 指向 剩余参数 都是未来要一次传递给fn的参数信息call 方法执行的过程中 这样处理 把fn[call中的this]执行 让fn 中的this 指向obj 并且把参数也传递进去call 如果第一个参数 如果不传 或者传 null / undefined 在js 非严格模式下 最后fn 中的this 都是window (严格模式下 不传this 是undefined) fn.call(10,20) //fn this -> 10// apply和call 一样 只不过传参的时候 后面的 要以数组的形式 fn.apply(obj,[10,20])// bind 是预先处理 执行bind 先把函数中需要改变的this 等存储起来 但是此时函数不会被执行 执行bind的时候 会返回一个匿名函数 当后期执行匿名函数的死后 再去把之前需要执行的函数执行 并且改变this 为预设的值
// 定时器中的calllet obj = {namF:'obj.name'}window.name = 'window.name'function fn(){console.log(this.name)}// 这样是不行的 我们call 方法 在改变this 的时候 是立即执行的 这样是不可以的1000之后就不在执行了setTimeout(fn.call(obj),1000)//解决方法 我们把它写在一个函数里setTiomeout(function(){fn.call(obj)},1000)//解决方法 用bind 因为bind 是返回一个柯里化函数setTimeout(fn.bind(obj),1000)
手写call / apply / bind 源码
手写 bind
function fn (x,y) {this.total = x + y}let obj = {namF:'obj的名字'}//我们普通的点击事件 就要执行一个函数 里面还有事件对象的参数document.body.onclick = function (e) {//这里面的this是我们的body}// 所以 我们用 bind 的时候 首先也要返回一个函数 并且要传递事件对象 并且要改变this 指向documnet.body.onclick = fn.myBind(obj,10,20)Function.prototype.myBind = function (context,...params) {//首先这里面的this 是我们的fn 函数 因为是 fn 调取的myBind 函数//context -> 你传进来的obj对象//params -> 是 你传进来的是10 20// 这里面我们要retrun 一个匿名函数 anonymous 也就是点击body的时候要执行的匿名函数let _that = thisreturn function anonymous (...arg) {//首先这里面的this 因为是body调取的这个函数 所以 this 是body//2. 所以就需要我们拿到外面缓存的that_that.call(obj,...params.concat(arg))}}//当然 这里我们也可以写成箭头函数的形式 这样就不用缓存this 了 它会去它所所以的上下文中的上层去找thisFunction.prototype.myBind = function (context,...params) {return (...arg) => {//首先这里面的this 因为是body调取的这个函数 所以 this 是body//2. 所以就需要我们拿到外面缓存的that_that.call(obj,...params.concat(arg))}}
- 手写call
// call 和 apply 是立即执行 不会像bind 一样 赶回一个匿名函数Function.prototype.myCall = function (context,...params){//context -> 就是你传进来的obj// ...params 就是你传进来的参数// 这里面的this 就是你的fn 函数 因为是fn 调用的 myCall// 首先 我们要让我们fn 和我们的obj 产生关系 比如 obj.fn = fn 这样再掉用fn的时候 this 就是我们的obj了//其次 我们在obj 上添加了一个fn 的属性 需要注意两点 第一个 fn是你自己后添加的所以用完需要删除 第二 如果人家obj上原本就有一个fn的属性 你就把人家的覆盖了 所以我们要用Symbol来确定唯一//如果你传进来的是个undefined 或者null 我们都要把this 指向window 这里 在两个等于 == undfined 和 null 是相等 如果是三个等于就不等了context == null ? context = window : null// 如果你的context 不是一个引用类型的值 而是基础类型的值 比如数字10 我们就需要先把它处理成 引用类型值 比如传进来的是个10 我们就处理称 Number(10) 这样就可以添加this// 如果你的类型不是object 和 function 就说明 你是基础类型值 那么我们就需要处理 如果是我们就什么也不干!/^(object|function)$/i.test(typeof context) ? context = object(context) : nulllet result//这里我们需要创建一个唯一的属性值 来避免你原来obj上的属性值冲突key = Symbol('key')//让fn 函数 和我们的obj 家里关系context[key] = thisresult = context[key](...params)// 用完之后 把我们新增的删除delete context[key]// 如果有返回值 就返回return result}
- js中的继承
1. JS 本身是基于面向对象开发的 编程语言 我们学习 就是学习的它的类2. 那么类 就具备 封装 继承 多态3. 封装 类也是一个函数 把实现一个功能的代码进行封装 实现 低耦合高内聚4. 多态 又分为 重写 和 重载重写 子类 可以重写 父类上的方法(伴随这继承)重载 相同的方法 由于参数或者返回值不同 具备了不同的功能(js 不具备严格意义上的重载)5. 子类可以继承父类中的方法
- 原型继承
1. 原型继承 其实就是让 子类的原型 prototype 指向 父类的实例2. 把实例上的方法 和属性 都变成子类共有的3. 父类中私有和共有的属性方法最后都变成子类实例共有的 但是和其他语言不同的是 原型继承并不会把父类的属性方法 拷贝 给子类 而是让子类实例基于__proto__ 原型链找到自己定义的属性和方法
function Parent() { this.x = 100 }Parent.prototype.getX = function () { return this.x }function Child () { this.y = 200 }//现在CHild 想用 Parent的方法 所以我们就让 Child 的 原型 prototype = new ParentChild.prototype = new ParentChild.prototype.getY = function () { return this.y }let c1 = new Child

- call继承
1. 我们刚刚看到原型继承 父类 私有的 和父类公有的 属性和方法 都变成子类实列公有的了 这样不是我们期待的2. 所以有了call 继承 但是 只是把父类私有的 变成子类公有的 但是并没有 把父类公有的 变成子类公有的
function Parent () { this.x = 100 }Parent.prototype.getX = function () { return this.x }function Child () {//这里面的this 就是 Child 我们需要把Parent 里面的this 指向成我们 Child 所以就用了callParent.call(this)this.y = 200}//以上 我们就把Paret里面的 x 变成 我们 Child 里面 的 y 了 这样就把 我们的 Parent里的y 变成我们Child 里私有的了 但是Parent 里面的 公有的方法 却没有继承过来
- 寄生组合式继承
1. 就是 把我们的原型继承 和我们的call 继承 组合其来2. 这样的好处 就是 把父类私有的 变成 子类 私有的 父类公共的 变成 子类中公有的
function Parent() { this.x = 100 }Parent.prototype.getX = function () { return this.x }function Child () { this.y = 200 }//这里就是 把我们的基于我们原型链上去 查找 我们的 这样我们的父类的私有属性 就 就带入不到 我们的子类里面去了Child.prototype.__proto__ = Parent.prototypeChild.prototype.getY = function () { return this.y }let c1 = new Child
// 但是以上的方法 在IE中这个方法并不可用 所以我们需要是用Object.create()// Object.create() 就是创建一个空对象 并且 把这个空对象的原型链 __proto__ 指向 我们的传进来的 这个对象function Parent() { this.x = 100 }Parent.prototype.getX = function () { return this.x }function Child () { this.y = 200 }//直接 让我们一个 prototype = {} 这个 对象里面__proto__ 指向了我们的 Parent.prototypeChild.prototype = Object.create(Parent.prototype)Child.prototype.constructor = ChildChild.prototype.getY = function () { return this.y }let c1 = new Child
//实例function C1() { if (name){this.name = name}}function C2() { this.name = name}function C3() { this.name = name || 'join'}C1.prototype.name = 'Tom'C2.prototype.name = 'Tom'C3.prototype.name = 'Tom'new C1().name //有条件判断 没有走if 所以name 属性就要去自己原型上找 是Tomnew C2().name //name 作为形参 没有传 所以值是undefinednew C3().name // name 没传 但是走了或者的逻辑 所以 值是 join
//示例function Fn() {let a = 1this.a = a}Fn.prototype.say = function () {this.a = 2}//这里我们Fn函数的原型prototype 就指向一个对象 里面有 constructor:Fn 这里 我们重新new Fn就相当于把 Fn.prototype指向了 new出来的fn的实例对象 但是Fn的原来的原型并没有销毁还在内存中 实例对象有一个__proto__ 它指向了Fn的原型Fn.prototype = new Fn// new Fn 的实例let f1 = new FnFn.prototype.b = function () { this.a = 3}console.log(f1.a) // f1上的a 是1 因为 new 的时候也当普通函数执行了console.log(f1.prototype) //undefined f1的实例上没有prototype这个属性 只有在类的函数上才有console.log(f1.b) //是个函数console.log(f1.hasOwnProperty('b'))// false 这个是判断b 是不是 f1的私有属性 b函数并没在私有上 而是在 公有上 所以是falseconsole.log( 'b' in f1) // true 这个不一样 只要私有 原型上都能找到啊console.log(f1.constructor == fn) // true 会从原型链上一点一点向上找

//实例let obj = {2:33:4length:2push:Array.prototype.push}obj.push(1)obj.push(2)console.log(obj)//以上我们要了解一下数组中的push 是怎么实现的let arr = [10,20]arr.push(30)Array.prototype.push = function (val) {//这里面的this 是arr//这里面的代码 执行如下 相当于把代码的最后一位等于你添加进来的值this[this.length] = val// arr[arr.length] = valthis.length++}//我们知道以上的方法执行再来看我们的objobj.push(1)//这里执行的就是我们上面数组里面写的方法obj.push = function (val) {//这里的this 就是objthis[this.length] = val//obj[obj.length = 2] = 1obj[2] = 1obj.length++}//以上下的步骤都是如此 所以最后输出objlet obj = {2:13:2length:4 // 由于每次执行push方法之后 length都会++ 先从2 加加 成3 有从 3 加加到 4}
// 实例我们知道 如果 == 两边类型不一样的话 都是转换成字符串 在转换成数字那么 比如对象 {} [] 转换成 数字的话 都是调取的原始值浏览器底层机制 会预先处理 使用 [Symbol.toPrimitive] 这方法去获取原始值在转换成数字可是这个方法如果不存在 就会去调用valueOf方法(基本数据类型值上 比如 number string date)都会有这个方法 如果没有 就会调 toString 去转换为字符串 在把字符串 变换成数字// 所以如下提let a = ? // a等于多少的时候 能输出okif(a==1 && a == 2 && a ==3 ){console.log('ok')}//所以我们知道 根据以上规则 它肯定回去调[Symbol.toPrimitive] 这个方法 但是 没有所以我们要自己写一个let a = {valuF:0,[Symbol.toPrimitive](hint){// 浏览器调取这个方法的时候 会传递一个hint 存储当前对象即将转换什么值return ++this.value}}//这个时候 两个等号比较的时候 如果类型不一样会先转换成数字 基于以上的规律获取原始值 则会调用 Symbol.toPrimitive 这个方法 这个方法是我们写的(因为自己写了) a==1 的时候 会调取一次 value就++了 a==2 还会调取一次if( a== 1 && a == 2 && a ==3){console.log('ok')}//还有第二种写法let a = [1,2,3]// 代码逻辑 a.shift 删除 1 然后在转换为字符串 返回的就是1 然后 继续删除2在转换为字符串 也可以走进上面的那个if里面去a.toString = a.shift// 方案2 用数据劫持let i = 0//看看a 是不是window的一个属性Object.defineProperty(window,'a',{get(){return ++i}})if(a == 1 && a == 2 && a == 3){console.log('ok')}
数组对象的深克隆/浅克隆
浅克隆
1. 浅克隆 就是 把一级对象 拷贝一份 如果对象里面还有引用类型的值 是拷贝不了的
let obj = {a:100,b:[10,20],}//浅克隆let newObj = {...obj}newObj === obj // 输出 false 因为地址不一样了newObj.b === obj.b // 输出 true 因为地址没有拷贝过来//for in 循环拷贝for(let key in obj){//这里注意 for in 遍历 会把当前对象上可枚举(列举)的属性 就是你能看到的都会拷贝 原型上的也会// 但是除了一些内置属性是不可枚举的 比如 数组的length 虽然可以看到 但是 不会被拷贝//公有属性 大部分都是不可枚举的 但是自己在内置类原型上扩展的 是可枚举的 也就是可以拷贝if(!obj.hasOwnProoerty(key)) breaknewObj[key] = obj[key]}for of 循环 只会拷贝 私有的
- 深克隆
1. 方案一 都变成字符串 在重新变成对象 这样浏览器会重新开辟全套的内存空间存储信息 比如 JSON.Stringify / JSON.parse
// 缺点 值如果是 正则 Symbol('AA') BigInt 函数 日期对象(会变成字符串) 都会拷贝不过来let obj = {a:100,b:[10,20,30],c:{x:10},d:/^\d+s/, //正则在用JSON 拷贝的时候 会变成{} 空对象F:Symbol('AA') // 不会被拷贝 直接没有e这个属性f:function(){} // 函数同上}
2. 方案二 自己单独一层一层处理
function cloneDeep (obj) {let type = typeof objif(obj == null) return null//如果传递的不是对象类型 就直接返回对应的值if(type !== 'object') return obj//知道 传进来的是什么类型let constructor = obj.constructor//如果是正则 或者 日期 对象 constructor 就是你的构造函数 可能是 new RegExp | Dateif(/^(RegExp|Date)$/i.test(constructor.name)) return new constructor(obj)// 这里如果你传进来 是数组 那么 就是 Array 对象 就是 Object 如果是函数 就是 Function 如果是 数字 那么就是 Number// 然后我们进创建一个新实例 可能是新数组 也可能是新对象let clone = new constructorfor(let key in obj){//只有私有的我们才处理if(!obj.hasOwnProperty(key)) breakclone[key] = cloneDeep(obj[key])}return clone}
- 数据类型检测
1. typeof 返回结果是一个字符串 字符串包含了对应的数据类型 number string boolean bigint undefined symbol object function 原理是 按照计算机底层存储的二进制来进行检测的 也无法区分 是哪个构造函数 搞出来的实例typeof null -> 'object'2. instancof 并不是用来检测数据类型 是用来检测当前实例是否属于这个类 不支持基本数据类型值3.constructor 支持基本数里类型值4.Object.prototype.toString.call 专门用来检测数据类型的 (很强大很暴力的一种版本,基本没有缺陷)
- instancof
//instancoflet arr = []console.log(arr instanceof Array) //trueconsole.log(arr instanceof Object) //true 绝对不能证明XX instancof Object 是 true 就是普通对象console.log(arr instanceof RegExp) //false// 检测字面量方式的时候 会出错let n = 10let m = new Number(10)console.log(n instanceof Number) // falseconsole.log(m instanceof Number) // true// 如果把原型改了 检测的值也会出错function Person(){}Person.prototype = Array.prototypelet p1 = new Personconsole.log(p1 instanceof Person) //输出为true// 原理arr instancof Array//其实相当于调取了 Array 上的一个方法Symbol.hasInstanceArray[Symbol.hasInstance](arr)// 而我们知道 Array 是 Function 的实例 所以这个方法 在Funcion的 原型上 prototypearr.__proto__ === Array.prototype => arr instanceof Arrayarr.__proto__.__proto__ === Object.prototype => arr instanceof Object
//重写 instancof// obj是要检测的值// constructor 检测的 是不是 你传进来的function instance_of(obj,constructor){//参数校验 如果你传进来的 不是引用类型 比如是字面量的10 或这 字符串 null 我就直接falseif(obj === null || /^(object|function)$/i.test(typeof obj)) return falseif(typeof constructor !== 'function') throw new Error('类型错误')//这一步 就是 obj 的原型连let proto = Object.getPrototypeOf(obj)//获取 你传进来的函数的原型let prototype = constructor.prototype//开始循环while(true){// 顺着原型链一层一层向上找 如果找到最后 找到头了 就返回nullif(proto === null) return false// 如果是一个自定义类 我们就返回trueif(proto === prototype) return true//以上都没有的就继续一层一层 向上找proto = Object.getPrototypeOf(proto)}}
- constructor
// 如果constructor 在不修改的情况下let arr = []console.log(arr.constructor === Array) //trueconsole.log(arr.constructor === Object) //falseconsole.log(arr.constructor === RegExp) //false// 而且 也支持 基本数据类型值let n = 10n.construtor === Number // true
- Object.prototype.toString.call
// 基本上所有类的原型上 都有toString方法 Number String Boolean Array Function RegExp Symbol Date Object// 这些方法里面 除了 object.prototype.toString 是用来转检测数据类型的 其余都是用来转换为字符串的({}).toString() // 调取的就是 object.prototype.toString 方法 返回就是它的类型'[object Object]'document.querySelectorAll('*').toString() //调用的也是object.prototype.toString 方法 返回的是 '[object NodeList]'//以上的返回结果 后半段的值 取的是 Symbol.toStringTag这个值'[object [Symbol.toStringTag]]'//所以我们使用的时候let arr = []//使用这个方法 来检测 arrObject.prototype.toString.call(arr) //输出 [object Array]
同步异步编程
- 进程/线程
1. 进程代表一个程序 (浏览器开一个页卡就是一个进程)2. 线程是用了处理进程种的具体事物的 如果一个程序种需要 同时做好多事 就需要开启多个线程3. 浏览器是多线程的 -> CUI渲染线程 HTTP网络线程 JS渲染线程 监听线程4. js 是单线程的 因为浏览器只分配了一个线程用来处理渲染JS代码 一个线程同时只能做一个事情5. js种代码 大部分都是 同步编程 上面任务没有处理完成 下面的任务是无法处理的6. 但是js中 利用浏览器的多线程机制 可以规划出异步编程效果
- 异步编程有哪些
定时器 ajax/Fetch/跨域(HTTP网络请求) 事件绑定 Promise Generator yeild async await
底层机制
计算程序执行事件(预估)
//大O表示法 提前预估的方法 O1 < O//运行监控 console.time/timeEnd (受当前电脑运行的影响)console.time('AAA')for(let i = 0; i < 9999999; i++){//同步编程}console.timeEnd('AAA') //这里就会输出程序运行的时间
- 定时器的异步编程
// 首先设置定时器任务是同步的 间隔 interval这么长时间 执行定时器绑定的函数(这个任务是异步的)遇到异步任务 浏览器不会等待它执行完 则会继续渲染下面的代码当等到下面代码运行完 时间也到了执行条件 才执行setTimeout(() => {console.log('ok') // 1S之后输出},1000)console.log('NO') // 最先输出// interval 为 0 也不是立即执行 浏览器都有最快反应时间 谷歌(5~6ms) IE(13~17ms) 即使设置为零 最快也要等 5mssetTimeout(() => {console.log('ok')},0)console.log('NO')
- 异步任务执行机制
1. 首先我们都是在代码执行的时候 会开辟一个栈内存 代码执行生成上下文(EC(G)) 然后由渲染进程去执行这段代码2. 代码分为同步代码 和 异步代码(比如我们的定时器 事件处理函数 ... 等等 )3. 为了存放这些异步的代码(任务) 浏览器初始化加载页面的时候 会开启一个 任务队列(优先级队列) 用来存放我们的异步任务 就是你写在定时器里 或者 ajax promise .. 等等代码 然后由 浏览器开启一个监听线程 去监听你的异步任务 比如定时器到没到事件 请求的数据回来没回来4. 这个任务队列(event queue) 分为两块 一个是微任务队列(microtask) 一个是宏任务队列(macrotask) 宏任务队列用来存放我们的定时器里面的任务代码 微任务队列存放我们的promise等等把5. 然后开始从上到下执行代码 遇到一段代码 一看 是一个定时器 那么就把他存放宏任务队列里 并且开始计时6. 等我们的同步代码 执行完毕 或者其他代码执行完毕 我们的js渲染线程空闲下来 才会去我们的任务队列看看 还有没有没执行的代码 反过来讲只要我们的同步代码没有执行完毕 哪怕你定时器已经到时间了也不会执行7. 同步代码执行完毕 就去任务队列看 还有没有 没有执行的代码 拿出来执行 首先回去看任务队列中的微任务队列 里有没有代码没执行 如果有 拿出来 当同步代码执行 等到执行完毕 在去看看 微任务队列里还有没有没执行的代码 如果有 在拿出来 当同步代码执行 如果没有 就去宏任务队列去看 如此反复的循环 这个方式叫做 eventLoop 事件循环8. 这里定时器的宏任务执行顺序是 最先到时间的去执行
//实例setTimeout(() => {console.log(1); // 8.输出1}, 20);console.log(2); //1.输出2setTimeout(() => {console.log(3); // 7.输出3}, 10);console.log(4); //2.输出4console.time('AA');for (let i = 0; i < 90000000; i++) {// do soming}console.timeEnd('AA'); //=>AA: 79ms 左右 //3.输出80msconsole.log(5); //4.输出5setTimeout(() => {console.log(6); // 9.出书6}, 8);console.log(7); //5.输出7setTimeout(() => {console.log(8); // 10.出书8}, 15);console.log(9); //6.输出8

以上代码解析首先就去执行我们的同步代码 但在执行过程之后 有个循环 总过经过了 80ms 在循环的过程中 我们的 第二个定时器 第一个定时器 已经到时间 但是我们的主线程还没有执行完毕 所以js渲染线程没有空下来 即使我们 定时器到时间 也不会执行循环结束之后 代码往下执行 遇到两个定时器 接着 放在我们的宏任务里 代码执行完毕然后我们js渲染线程就空下来了 此时就去任务队列里看 还有没有任务没执行 看到宏任务里 由于 第二个定时器是先到时间的 所以就先执行 然后执行 第一个定时器 由于我们第三个定时器 和第四个定时 是在我们循环结束之后 才开始开启的 所以要最后执行
基础数据结构之队列和栈
栈结构
1. 栈结构(LIFO last in first out)的特点 先进后出 或者 后进先出2. 所有的操作只能在一端操作(顶端) 包括增加(进栈) 和 删除(出栈)3. 递归算法中的无限递归会出现栈溢出

- 实现一个栈结构
1. 以后创建一个栈 就是创建一个栈的实例2. 要有个容器
class Stack {container = []//进栈的方法enter(valye) {//把元素插入到最顶端this.container.unshift(value)}// 出栈leave() {// 从最顶端删除元素return this.container.shift()}// 尺寸size(){return this.container.length}// 栈里有哪些东西value(){// 把元素都克隆一份return this.container.slice(0)}}
- 十进制转二进制
1. Number.prototype.toString('进制数') 可以取的转换后的进制数2. 第二个方法 就是短除法
// 短除法 比如求 63 的 二进制63 / 2 商 31 剩 131 / 2 商 15 剩 115 / 2 商 7 剩 17 / 2 商 3 剩 13 / 2 商 1 剩 11 / 2 商 0 剩 1// 然后把我们的剩数 从下向上 相加 就是二进制63的二进制数 是 111111
// 我们利用短除法 写代码Number.prototypt.decimal2binary = function () {// 这里面的this 就是我们的num 是个引用类型 new Number// 这里我们可以先用valueOf()获取原始值 也可以不用 因为在参与运算的时候 自己就会转换let decimal = this//这个是我们上面写的栈结构的类sk = new Stackif(decimal === 0) return '0'//然后开始循环 思想就是我们上面的短除法 每次的商 在除2 每次取的余数 都进栈while(decimal > 0) {//每次的商let n = Math.floor(decimal / 2)//每次的余数m = decimal % 2//把余数进栈sk.enter(m)// 然后让我们下次的除2的数 等于我们 上次的商数decimal = n}// 最后把我们的进栈的的元素 变成字符串 因为是栈结构 所以我们最先进去的余数在最下面 最后进去的余数 在最上面return sk.value().join('')}let num = 187956num.decimal2binary()
- 队列结构
1. 队列结构 和我们的栈结构相反 是先进先出 分进入队列 和移除队列2. 允许在前端 删除 允许在后端插入3. 特点 优先级队列4. 可以想象成 从 尾巴进入 从 头中出去5. 队列的特点 也是有优先级的 并不是 谁最后进来 谁进放在末尾 而是谁的优先级最高 谁就放在前面

// 队列类class Queue {container = []//进栈的方法enter(valye) {//把元素插入到最顶端this.container.push(value)}// 出栈leave() {// 从最顶端删除元素return this.container.shift()}// 尺寸size(){return this.container.length}// 栈里有哪些东西value(){// 把元素都克隆一份return this.container.slice(0)}}
// 比较优先级// 队列类class Queue {container = []//进栈的方法enter(value,priority = 0) {// priority 默认优先级为 0let obj = {value,priority}// 如果你的优先级是0 了 那么我就直接把你放在容器的末尾if(priority === 0) {this.container.push(obj)return}let flag = false//如果不是 我就和我最前面的比 如果比他高就放他前面for(let i = this.container.length - 1; i >= 0; i--){let item = this.container[i]// 对比所有的优先级 找到 比你高一级的元素if(item.priority >= priority){// 找到了 就放在比你高一级元素的后面this.container.splice(i,0,obj)//然后证明找到flag = true//结束循环break}}//如果没找到 就说明你优先级是最低的 就放在最后面!flag? this.container.push(obj) : null}// 出栈leave() {// 从最顶端删除元素return this.container.shift()}// 尺寸size(){return this.container.length}// 栈里有哪些东西value(){// 把元素都克隆一份return this.container.slice(0)}}
- 击鼓传花
N 个人一起玩游戏 围成一圈 从 1 开始数树 数到M的人 自动淘汰 最后剩下的人 会取的胜利 问最后剩下的是谁比如有8个人 开始数数 数到 5 的人淘汰 然后在开始下一轮 数到5的人 在淘汰 看最后剩下谁
// 原理1 2 3 4 5 6 7 8开始数数 第一个人数1 数完之后 排到 8 后面 第2个数完 排到 1 后面 依次类推 第 5 个人数到 5 了淘汰第二轮 6 是第一个 数1 数完之后 拍到4后面 7 数 2 拍到 6 后面 然后数到5的淘汰... 以上 依次类推 看看 最后剩下谁
function game (n,m) {let qe = new Queue//先让八个人都进入队列for(let i = 0,i < n; i++){qe.ennter(n)}//然后开始数数 结束条件就是 队列里最后只剩一个人了while(qe.size() > 1){for(let i = 0; i < m -1; i++){qe.enter(qe.leave())}qe.leave()}return qe.value().toString()}// 一共 8个人 数到5 的淘汰game(8,5)
Vue
基础API
- 基础操作
//首先还是要引进vue.js<script type="text/javascript" src="vue-2.4.0.js"></script>//然后常规操作var vm = new Vue({el: "#app",data: {msg: '你好是我vue',}methods: {show(){console.log('我是vue')}}})//这里就new出来一个vue的实力对象el 代表你所控制的区域 这里可以写选择器比如id 类. 但是不能把body和html作为区域去控制data 代表的模拟数据 后面一定是跟个对象的写法methods 代表你方法存放的地方 后面也是跟个对象的洗发//以上是固定写法不可改变<div id="app" >{{ msg }} //这里双花括号就是差值表达式 我们在new完了Vue之后 就可以通过双花括号把你的值渲染到里面</div>{{}}差值表带是里面可以写预算符 比如加减乘除var vm = new Vue({el: '#app',data: {msg: 3},methods: {show(){}}})<div id="app" >{{ msg + 1 }}</div>渲染的结果是4
指令
v-text指令
<script type="text/javascript">var vm = new Vue({el: '#app',data: {msg: '我是v-text指令渲染的'},methods: {show(){alert('你好我是vue')}}})</script>//以下就是通过v-text指令的方式 把msg渲染到了页面中 注意这里是覆盖是的渲染 如果你原来p标签里有内容也会被覆盖<div id="app"><p v-text='msg'></p></div>//v-text指令不会解析标签
- 指令
<div id="app"><p v-html='msg'></p></div><script type="text/javascript" src="vue-2.4.0.js"></script><script type="text/javascript">var vm = new Vue({el: '#app',data: {msg: '<h1>我是v-text指令渲染的</h1>'},methods: {show(){alert('你好我是vue')}}})//v-html指令会解析标签 把标签也会渲染页面中 和v-text一样 也会覆盖原有的内容
条件渲染
v-if
v-if 条件渲染后面跟着是布尔值 会有隐式类型转换<div id="app"><p v-html='msg' v-if='true'></p></div>//如果以上修v-if="" 双引号里的值是布尔值 如果是true就会传进标签如果不是则不会创建//这里面可以写三元表达式 还有一些可以隐式类型转换成布尔值的类型<div id="app"><p v-html='msg' v-if=' '></p></div>//比如这样就不会创建这个标签 因为你三元表达式返回来的结果是false//这里也可以用变量去控制<div id="app"><p v-html='msg' v-if='flag'></p></div><script type="text/javascript" src="vue-2.4.0.js"></script><script type="text/javascript">var vm = new Vue({el: '#app',data: {msg: '<h1>我是v-text指令渲染的</h1>',flag: true},methods: {show(){alert('你好我是vue')}}})</script>//以上就是在data里添加了一个变量 值可以写成false或者true 可以隐式类型转换的都可以 v-if里可以写这个变量
- v-show
v-show和v-if的用法一样v-if是添加和移除标签v-show是控制元素的display none 和block//那么我们什么时候用v-show和v-if呢1. 当标签切换频繁时:使用v-show2. 当标签切换不频繁时:使用v-if
- v-on
v-on用来给元素绑定事件基础语法<div id="app"><p v-html='msg' v-on:click='show'></p></div><script type="text/javascript" src="vue-2.4.0.js"></script><script type="text/javascript">var vm = new Vue({el: '#app',data: {msg: '<h1>我是v-text指令渲染的</h1>',flag: false},methods: {show(){alert('你好我是vue')}}})//这里给 p标签用v-on:click="show" 绑定一个点击事件 当触发这个点击事件的时候 执行methods里面的方法//简写<div id="app"><p v-html='msg' @click='show'></p></div>
生命周期
- 什么是生命周期
//从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件、这些事件、统称为声明周期!//生命周期钩子:就是生命周期事件的别名而已
- Vue实例创建到渲染到页面经过的步骤
//这里我们要清楚一个概念 Vue这个实例对象 既然是对象那么是复杂的数据类型 创建的时候是在内存中 然后在渲染到页面 也就是说一个Vue的实力对象从创建到内存 内存在到页面 是要经过一些列的函数去处理才能实现 而这些函数就叫做生命周期函数 这些生命周期函数分为三大类
创建阶段
在init Events & Lifecycle阶段 通过 beforeCreate这个生命周期函数 只是初始化了了一个空的Vue实例对象 这里只有一些默认的声明周期函数,但是其他的比如data methods这些都没有 访问不了
//new Vue() 表示开始创建了一个Vue实力对象 那么此时它只是一个空的对象 只有一些默认的声明周期函数和默认事件 那么你里面的数据和一些方法要经过这些默认的声明周期函数才能渲染到页面//在 init Events&Lifecycle 这个阶段 通过beforeCreate这个函数//初始化了一个Vue的空对象 这里只有一些默认的声明周期函数和默认的事件,其他的东西都未能创建 比如你的data,methods都为能创建 所以在这个阶段 你是不能读取你data和使用你methods里面的方法的
- 在init injections & reactivity阶段 通过 Create这个生命周期函数 初始化你的data和methods 这个或阶段可以通过你的created声明周期函数 可以访问你的data和调用你methods里面的方法
//在上一个阶段我们已经有了一个Vue的空实例对象 里面只是通过beforeCreate这个生命周期函数把这个Vue这个空的实力对象里面的一些默认的周明周期函数和一些默认的事件初始化了出来 然而这里面并没有初始data和methods 所以下一步我们要开始初始data methods这些//经过上一个声明周期的函数初始化过后 我们现在要在这个Vue的空对象中 添加 数据data 和 方法 methods 那么我们就要通过create这个声明周期函数去添加 也可以说我们要通过create这个声明周期函数去初始化data数据 和 methods方法
- 在内存中编译我们初始化好的指令 和一些模板字符串 渲染在内存中的DOM
//我们已经通过以上两个不同的声明周期函数 初始化好了我们的Vue空的实例对象和一些data methods 不过因为是对象 而对象又是复杂的数据类型所以这些并没有直接渲染到浏览器中 而是在内存中 但是浏览器要想识别的话我们就需要编译一下 所以我们需要经过beforeMount这个声明周期函数 在内存中把我们刚刚初始化好的那些编译成一个最终的模板字符串等待着下一个声明周期函数把这些编译好的模板字符串渲染到浏览器的页面中去
- 把内存中通过beforeMount编译好的这些模板字符串通过Mounted这个声明周期函数真正的渲染到页面中去 在这个生命周期 你才能操作DOM
//在beforeMount这个生命周期函数里面 我们所有的Vue里面的数据和方法 并没有渲染到浏览器中 而是在内存中编译 所以你在这个生命周期函数里面访问的只是你写固定的模板字符串 比如插值表达式 {{ msg }} 这个时候你访问的只是 {{ msg }} 因为并没有把我们用的这些数据渲染到页面中去//所以在经过这个声明周期的函数之后 我们要经过Mounted这个声明周期的函数去把数据渲染到页面中去 所以也就说我们可以在Mounted这个周期函数里面访问到渲染到页面上的数据
运行阶段
经过以上四个声明周期函数 此时就已经表示我们所有的Vue实例对象就已经初始化完毕了 也就说创建阶段的已经完毕 开始进入运行阶段
在运行阶段我们只经过两个声明周期的函数beforeUpdate和update
//在运行阶段我们无非就是更改数据 触发方法 那么在更改数据的时候我们要通过beforeUpdate这个声明周期函数 先把data里的数据更新 此时在这个声明周期函数里页面并没有被更新 在这个声明周期函数里数据是最新的 而页面不是最新的//而在update这个声明周期函数里我们才会把更新好的date从虚拟的DOM中渲染到页面中 也就是说在这个声明周期函数里 页面和数据多是同步的//以上两个声明周期的函数会随着你data的更新或者改变 触发零次或者无数次
- 销毁阶段
- 当页面已关闭的时候我们就开始执行销毁阶段
//销毁阶段会执行两个声明周期函数//当执行了beforeDestroy这个声明周期函数的时候 Vue就开始从运行阶段进入到了销毁阶段 这个时候实例身上所有的data和所有的methods过滤器指令都还处在可用的状态//当执行到destroyed的时候所有的指令和方法数据都会被销毁不可用
生命周期的函数不同分为三大类
第一类 创建期间的声明周期函数
beforeCreate() { // 这是我们遇到的第一个生命周期函数,表示实例完全被创建出来之前,会执行它// console.log(this.msg)// this.show()// 注意: 在 beforeCreate 生命周期函数执行的时候,data 和 methods 中的 数据都还没有没初始化},created() { // 这是遇到的第二个生命周期函数// console.log(this.msg)// this.show()// 在 created 中,data 和 methods 都已经被初始化好了!// 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作},beforeMount() { // 这是遇到的第3个生命周期函数,表示 模板已经在内存中编辑完成了,但是尚未把 模板渲染到 页面中// console.log(document.getElementById('h3').innerText)// 在 beforeMount 执行的时候,页面中的元素,还没有被真正替换过来,只是之前写的一些模板字符串},mounted() { // 这是遇到的第4个生命周期函数,表示,内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了// console.log(document.getElementById('h3').innerText)// 注意: mounted 是 实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,此时,如果没有其它操作的话,这个实例,就静静的 躺在我们的内存中,一动不动},
- 第二类 运行期间的生命周期函数
// 接下来的是运行中的两个事件beforeUpdate() { // 这时候,表示 我们的界面还没有被更新【数据被更新了吗? 数据肯定被更新了】/* console.log('界面上元素的内容:' + document.getElementById('h3').innerText)console.log('data 中的 msg 数据是:' + this.msg) */// 得出结论: 当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时 data 数据是最新的,页面尚未和 最新的数据保持同步},updated() {console.log('界面上元素的内容:' + document.getElementById('h3').innerText)console.log('data 中的 msg 数据是:' + this.msg)// updated 事件执行的时候,页面和 data 数据已经保持同步了,都是最新的}
组件
- 第一种使用Vue.extend来创建全局的vue组件
//关键字 Vue.extend() 括号里面是一个对象 写在 new Vue()上面//第一步 使用Vue.extend()来定义组件的html结构 可以用一个返回值来接收他var com1 = Vue.extend({template:"<h3>这是使用extend创建的组件</h3>" //template里面放的是html的页面结构})//用Vue.component('组件的名称',你定义组件的html结构)Vue.component('mycom',com1) //第一个参数就是你组件的名称 你二个参数是你上面定义的组件的html结构//这个时候就可以在html里展示他了 用标签的形式 但是这里注意 组件只能有一个根节点 就是外面必须有一个标签包裹他//<组件的名字></组件的名字><div id="app"><mycom>这里面显示的是你定义好的HTML结构</mycom></div>//这里组件的名称定义 采用 abc 或者abc-d的命名规则Vue.component('my-com',com1)<div id="app"><my-com>这里面显示的是你定义好的HTML结构</my-com></div>//也可以这样写Vue.component('my-com',Vue.extend({template:"<h3>这是使用extend创建的组件</h3>"}))
- 第二种方式使用Vue.component(‘组件名称’,{})里定义组件
//关键字 Vue.component('组件的名称'{里面是组件的模板和数据还有方法})Vue.component('my-com',{ //第一个参数还是组件的名称 第二个参数是个对象的形式 里面可以写模板templatetemplate: "<h3>这是用component来创建的组件的第二种方式</h3>",data(){},methods:{}})//调用的时候还是要写组件的名称的标签形式<div id="app"><my-com></my-com></div>//注意你的template里的标签只能有一个根元素Vue.component('my-com',{template: "<h3>这是用component来创建的组件的第二种方式</h3><span>123</span>"})//上面写就会会报错 因为h3和span标签是同级 你template就有两个根元素了Vue.component('my-com',{template: "<h3>这是用component来创建的组件的第二种方式<span>123</span></h3>"}) //可以这样写//或者这样写Vue.component('my-com',{template: `<div><h3>这是用component来创建的组件的第二种方式</h3><span>123</span></div>`})
第三种方式 可以把template里的html结构拿出来
//可以把template的html结构拿到外面去写 然后通过id去套进去<div id="app"><my-com></my-com></div><template id="com1"><div>我是拿出来写的组件</div></template>//注意一定要写在vue实例对象的外面 而且你的template标签一定要给id//这个时候就可以在Vue.component()里的tenmplate里通过id去把这个模板穿进去了Vue.component('my-com',{template: "#com1"})
局部私有组件的创建
在vue实例对象里面直接使用component注册
//这里可以在vue实例对象里面使用compontents 注意尾部带S 注册组件var vm = new Vue({el: "#app",components:{ //是个对象的形式login:{ //login是组件的名称 后面跟着是对象 然后 写的是你的template 和data methodstemplate: '<h1>我是私有组件</h1>' //html结构}}})
- 组件中的data 是个函数function的结构 而且必须有返回值 返回值是个对象的形式
Vue.component('login',{template:`<h3>我来试验一下data</h3>`,data(){ //这里组件中的data必须是个function的结构形式 而且 必须返回值是个个对象return {} //返回值必须是个对象}})
- 组件中的数据使用方式
//可以在retrun返回的对象中定义数据 然后通过差值表达式渲染到标签中去Vue.component('login',{template:`<h3>我来试验一下data {{ msg }}</h3>`,data(){return { msg: '我是组件中的return数据'}}})
点击按钮切换组件
v-if v-else切换组件 确定只能切换两个组件之间的显示隐藏
首先还是要给登录按钮和注册按钮注册点击使劲 然后让flag去控制一下 组件的显示v-if 如果后面是true就是显示 false为不显示 v-else 如果后面是false就是显示 true为不显示//然后就是定义两个组件<div id="app"><a href="" @click.prevent="flag=true">登录</a><a href="" @click.prevent="flag=false">注册</a><login v-if="flag"></login>//v-if如果为true就是显示 false就是不显示<reg v-else="flag"></reg>//v-false false就是显示 true就是不显示</div><template id="login"><h1>登录</h1></template><template id="reg"><h1>注册</h1></template><script type="text/javascript" src="vue-2.4.0.js" ></script><script type="text/javascript">Vue.component('login',{template:'#login'})Vue.component('reg',{template:'#reg'})var vm = new Vue({el: "#app",data:{flag:true}})
- component标签来切换组件
//我们定义好了组件之后一般都可以在vue实例控制的区域直接写标签就好 然后你的template写的标签和内容就会直接渲染到页面上//但是vue给我们提供了一个component的标签他里面有个:is属性可以直接控制我们要显示的组件<div id="app"><a href="" @click.prevent="flag=true">登录</a><a href="" @click.prevent="flag=false">注册</a><component :is="'reg'"></component>//这里的:is="" 跟的是你组件的名称 这里你组件的名称一定要用单引号包裹起来</div><template id="login"><h1>登录</h1></template><template id="reg"><h1>注册</h1></template><script type="text/javascript" src="vue-2.4.0.js" ></script><script type="text/javascript">Vue.component('login',{template:'#login'})Vue.component('reg',{template:'#reg'})
通过component标签点击按钮切换多个组件
//首先还是要准备三个按钮每当点击的时候让这个公共的变量变成你组件的名称<div id="app"><a href="" @click.prevent="flag='login'">登录</a>//点击注册的时候把flag变成lonin的组件名称<a href="" @click.prevent="flag='reg'">注册</a>//点击注册的时候把flag变成reg的组件名称<a href="" @click.prevent="flag='password'">密码</a>//点击注册的时候把flag变成password的组件名称<component :is="flag"></component>//这里有component标签的:is属性来显示你的组件 后面是哪个组件的名字就显示哪个组件</div>Vue.component('login',{template:'#login'})Vue.component('reg',{template:'#reg'})Vue.component('password',{template:'#password'})var vm = new Vue({el: "#app",data:{flag:'login'}})//然后这里在vue实力上定义变量flag 来决定 你的组件显示的名称 这样可以切换多个组件
- 组件的动画切换
//首先我们还是要用transition标签把组件包裹器来 然后在style里还是要设置动画//这里还是要用transition标签把组件包裹器来<transition><component :is="flag"></component></transition>//然后设置动画样式<style type="text/css">.v-enter,.v-leave-to{opacity: 0;transform: translateX(300px);}.v-enter-active,.v-leave-active{transition: all , 0.4s;}</style>//这里切换的效果 是还没等第一个组件动画完全消失呢 第二个组件动画就进来了 所以transition标签里有个mode的属性可以设置先手的顺序<transition mode="out-in">//理解就为先出去在进来<component :is="flag"></component></transition>
- 组件嵌套
//首先你要有第一个组件//每个组件都还有自己的components 这里还可以写一些templateVue.component('parent',{template: '<h2>我是父组件<child></child></h2>', //这里就是使用了字组件的名字的标签data(){return {}},components:{ //这里就是父组件的components 在这里我们又写了一个组件 名字叫child 他有自己的template'child':{template:'<h1>我是子组件</h1>'}}})//分离写法 可以把 后面的对象拿出去单独定义成一个一个变量var child = {template:'<p3>我是一个子组件</p3>'}var parent = {template: '<p2>我是父组件<child></child></p2>', //这里在parent里注册的 只能在parent里使用 使用的方式如上components:{'child':child}}Vue.component('parent',parent) //然后在页面中使用组件名称的标签//嵌套的组件只能在上一层使用
- 父组件向子组件传递数据得方法 通过子组件的props里选项可以获取到父组件的数据是数组的形式 这里的数据只能读取不能改变
//首先你父组件的里的数据要通过属性绑定的方式 绑定到你的子组件的标签名字上 这里绑定的属性为自定义属性 自己怎么起名都可以var parent = {template: '<p>我是父组件<child :data="msg"></child></p>',//这里通过属性绑定属性名 属性名自己定义 值是你父组件中的数据data(){return { msg: '我是父组件中的数据'}},components:{'child':child}}//然后在子组件里通过通过props来接收 接收的是你绑定的自定义属性名var child = {template:'<p>我是一个子组件{{data}}</p>', //然后这里通过差值表达式 在括号里是你自定义属性的名字 这里的数据就是父组件的数据props: ['data'] //这里的通过props来接收一下 接收的是你自定义绑定的属性名 他里面存放的值是你父组件的数据 注意是一个数组的形式}
首先在你组件的名的标签上 绑定一个自定义的属性 值是你的父组件的数据<parent :datas='flag'></parent>//然后要在props里要接收一下你的自定义的属性名 这个props是个数组的形式var parent = {template: '<p>我是父组件 {{ datas }} </p>', //然后用差值表达式 就可以用你父组件里的数据了props:['datas']}var vm = new Vue({el: "#app",data:{flag:'login'},components:{'parent': parent,}})
- 子组件调用父组件上的方法 通过关键字$emit来监听
//首先我们要在父组件上定义方法var vm = new Vue({el: "#app",data:{flag:'login'},methods: {show(){console.log('我是父组件上的方法')}},components:{'parent': parent,}})//然后通过给组件的名称的标签名绑定一个自定的事件 值是父组件上的方法<parent @pashow="show"></parent>//这里通过给子组件的标签名通过绑定一个自定义的事件 就可以通过$emit来获取这个事件var parent = {template: '<div><p>我是父组件</p><input type="button" @click=son value="子组件按钮"></div>',methods:{son(){this.$emit('pashow',给父组件传递的参数)//这里通过给子组件点击按钮绑定一个事件 然后点击是之后通过 $emit这个方法来获取父组件上的时间//第一个参数是你的自己定绑定事件的名字注意要加引号 第二个参数第三个参数是你要回传的值的参数}}}//这里$emit的第二个参数就是 子组件 向父组件里面传递的值 但是这个只能在子组件里调用var son ={template:'#temp',data(){return {obj:{name:'卡卡西',age:18}}},methods:{sonshow(){this.$emit('paren',this.obj) //这里就是子组件给父组件传递的参数}},props:['data']}//然后父组件可以通过方法的形参来接收一下var vm = new Vue({el:'#app',data:{msg: '我来插入 我是父组件的内容',msg1: ''},methods: {show(data){ //这里就是子组件通过$emit的来接收到的参数this.msg = dataconsole.log(this.msg)}},components: {son}})
父组件传递给子组件一个数组 然后又循环的两种方式
第一种 传递整个数组 然后循环标签
//首先在父组件中定义数组数据var vm = new Vue({el: '#app',data: {list: ['北京', '上海', '天津']},components:{son}})//然后通过在组件名称的标签中把list整个传递过去<div id="app"><son :title="list"></son></div>//然后通过props属性接收到这个父组件传递过来的数组var son = {template:'#temp',props:['title']}//然后通过v-for来循环标签<template id="temp"><div><p v-for="item in title" :key="item">{{ item }}</p></div></template>
- 第二种 通过先循环组件名的标签 然后把父组件中的每一个数组元素 动态绑定给子组件的标签名
//先循环子组件的名称的标签 然后把父组件传递过来的数组的每一位元素 绑定给子组件<div id="app"><son v-for="item in list" :title="item" :key="item"></son></div>//然后这里用 props来接收一下传递过来的数据var son = {template:'#temp',props:['title']}//最后在用插值表达式 传到子组件中的标签里面去<template id="temp"><div><p>{{ title }}</p></div></template>
- 父组件传递子组件信息 子组件传递父组件信息练习
1. 上一小节基础上 实现 点击子组件的城市时,将当前点击的城市传递给父组件,//这里要给你的城市注册点击事件 然后通过绑定方法 用$emit()监听 来吧值传递给父组件<template id="temp"><div><p @click='select'>{{ title }}</p></div></template>//以上子组件的每一项注册了点击事件 然后 给一个方法methods:{select(){this.$emit('show',this.title) //这里通过$emit 把子组件里的数值传递给父组件}},<son @show='pshow'></son>//然后父组件同构一个函数来接收一下子组件传递过来的值methods:{pshow(data){ //这里的data就是子组件传递过来的值this.current = data //然后父组件通过一个变量来保存这个值}},//然后父组件通过一个变量来保存这个值var vm = new Vue({el: '#app',data: {list: ['北京', '上海', '天津'],current:'null'},2. 父组件 将 当前点击城市 通过props再次传递给子组件//然后在通过属性绑定子组件名字的标签 来把父组件的值在此传递过来通过 props来接收<div id="app">//子组件在通过属性绑定来接收父组件传递过来的值<son :currentcity="current"></son></div>//然后通过props来接收var son = {template:'#temp',props:['title','currentcity'], //再次通过props来接收}3. 子组件 根据当前选中和循环项目比对 得出 哪个城市 得到 select class//然后通过计算属性来判断 是否相等 如果相等 就返回truecomputed:{isload(){return this.title === this.currentcity}}4. 对 active class进行样式赋值,使其 字体大小为40px 字体颜色为红色//然后对通过绑定class来 决定标签的样式是否显示<template id="temp"><div><p @click='select' :class={active:isload}>{{ title }}</p></div></template>
- render方法解析
//在我们new Vue的实例对象中 还有一个render的方法var login = {template:"<h1>我是登录组件</h1>"}const vue = new Vue({el:'#app',render: function(createElements){//createElements 是一个回调函数 他能把你传入的组件模板 渲染成html页面 替换你el选项的指定位置}})//简写格式var vm = new Vue({el: '#app',render: createElements => createElements(login)})//和传统方式 把组件名字的以标签的形式放在Vue的el控制区域的区别只保留自己 替换你的<div id="app"></div>
组件通信
- Vue父子组件传值
1.父组件想要传递的值 用动态绑定的方式在子组件的标签上指定一个自定义属性 然后值是你要传递的值 给子组件2.在子组件内部文件里用props注册一下 这里注册的名字 是你绑在子组件标签上自定义属性的名字 然后就可以使用了3.子组件想要给父组件传值 首先你需要在子组件的标签上绑定一个自定义事件 指向父组件文件的一个函数 这个函数的参数就是子组件传递过来的值4.子组件文件内部需要有一个方法 去触发你在自己标签身上绑定的这个自定义事件 this.$emit5.以上接是单向数据流
//父组件文件<template><el-card class="box-card"><div style="margin-bottom:20px;">我是父组件 最外层的页面 在我文件里有个值叫fatherdata 我现在要传给子组件</div><div style="margin-bottom:20px;">{{receivesond}}</div>//1.在子组件的标签上 动态绑定了一个属性 值指向想要传递的值 2步骤查看子组件文件//3.子组件给父组件传值 需要绑定一个自定义事件 指向自己的一个方法 这个方法的参数就是传递过来的值 4步骤查看子组件<son :sondata="fatherdata" @updatafather="receiveson"></son></el-card></template><script>import son from '@/views/component/son'export default {components: {son},data () {return {fatherdata: '我是父组件faterdata的值',receivesond: '我在父组件里 我等着接收son组件给我传过来的值 只要son组件一点击按钮我就接收'}},methods: {receiveson (res) {this.receivesond = res}}}
//子组件文件<template><div>我是son组件 也就是在father文件里的son标签 我在自己的文件接收了father给我传过来的值 fatherdata<div>{{sondata}}</div>//4.子组件需要触发这个方法 所以需要绑定这个自定义事件<el-button type="primary" @click="updatafather">我是son组件的按钮 我现在想把值传给父组件</el-button></div></template><script>export default {//2.子组件用props属性注册一下 名字是在子组件身上绑定的自定义属性 这里就可以直接使用了 注意只能是使用不能更改props: {sondata: {type: String,default: () => {return 11}}},data () {return {sonselfdata: '我是子组件本身的值'}},methods: {updatafather () {//在自定义事件里用这种方式 把值传递给父组件this.$emit('updatafather', this.sonselfdata)}}}</script>
//子组件传递父组件值原理<son :sondata="fatherdata" @updatafather="receiveson"></son>当我们绑定自定义事件的时候 在Vue模板解析的时候 会解析出来一个对象{updatafather:receiveson}子组件拿到这个对象之后会循环 然后挂到自己的实例上this.$on('updatafather',receiveson) 这种形式所以我们在自己子组件用this.$eimt触发
- Vue子传父的简化写法
1.上面我们已经说了子组件给父组件传值的原理 其实就是传递过去了一个事件和对应的函数过去2.那么我们就可以写成行内函数3.子组件在触发的时候 用this.$emit 触发这个行内函数即可4. .sync 适合父子组件值 同步的时候
//父组件<template><el-card class="box-card"><div style="margin-bottom:20px;">我是父组件 最外层的页面 在我文件里有个值叫fatherdata 我现在要传给子组件</div><div style="margin-bottom:20px;">{{receivesond}}</div>//这里就是写成了行内函数 我要传给子组件一个事件 函数是receiveson<son :sondata="fatherdata" @updatafather:receiveson="(value) => this.receivesond=vaue"></son></el-card></template><script>import son from '@/views/component/son'export default {components: {son},data () {return {fatherdata: '我是父组件faterdata的值',receivesond: '我在父组件里 我等着接收son组件给我传过来的值 只要son组件一点击按钮我就接收'}},methods: {}}</script><style>.box-card{margin: 50px;}</style>
//子组件触发<template><div>我是son1组件 也就是在father文件里的son标签 我在自己的文件接收了father给我传过来的值 fatherdata<div>{{sondata}}</div><el-button type="primary" @click="gofather">我是son组件的按钮 我现在想把值传给父组件</el-button></div></template><script>export default {props: {sondata: {type: String,default: () => {return 11}}},data () {return {sonselfdata: '我是子组件本身的值'}},methods: {gofather () {//这里触发行内函数的即可this.$emit('updatafather:receiveson', this.sonselfdata)}}}</script><style></style>
- Vue .sync 同步值
1.上面已经分析了父传子 子传父 以上都是父传子 自己的值 子传父改的不是父传过来的值2.当 你子组件要修改父组件传过来的值的时候 就可以用.sync3.这里在子组件触发的事件一定是update:值 才可以 这个是写死的
//父组件的值<template><el-card class="box-card"><div style="margin-bottom:30px;">我是父组件</div><div style="margin-bottom:30px;">{{fatherdata}}</div>//这样就是点.sync<son2 :sondata.sync="fatherdata"></son2></el-card></template><script>import son2 from '@/views/component/son2'export default {components: {son2},data () {return {fatherdata: '我是父组件fatherdata的值'}},methods: {}}</script><style>.box-card{margin: 50px;}</style>
//子组件<template><div><div style="margin-bottom:30px;">我是son2组件</div><div style="margin-bottom:30px;">我是son2组件我接收了父组件传过来的值------{{sondata}}</div><div style="margin-bottom:30px;">我是son2组件自己本身值------{{sontofatherdata}}</div><el-button type="primary" @click="goFahter">son2组件的按钮要修改父组件给我传过来的值</el-button></div></template><script>export default {props: {sondata: {type: String}},data () {return {sontofatherdata: '我是son子组件的值'}},methods: {goFahter () {//这里是固定写法 一定要写updata 加上你传过来的值this.$emit('update:sondata', this.sontofatherdata)}}}</script><style></style>
.sync 和 v-model的区别.sync的 值你可以自己随意的去定 而v-model只能用value
- Vue子孙通信(传统方式)
1.传统方式就是用动态绑定的方式一层一层往下传2.然后孙子组件向父亲传值的时候 只要触发父亲身上绑定的方法即可
//父亲<template><el-card class="box-card"><div style="margin-bottom:30px;">我是爷爷组件</div><div style="margin-bottom:30px;">我是爷爷组件的值----{{grendeFather}}</div>//首先向儿子组件传送了值 并且在自己身上绑定了事件<son :fatherwoson="grendeFather" @fatherto="change"></son></el-card></template><script>import son from '@/views/page/threecomponents/son'export default {components: {son},data () {return {grendeFather: '我是爷爷的数据'}},methods: {change (res) {this.grendeFather = res}}}</script>
//儿子组件<template><div><div style="margin-bottom:30px;">我是儿子</div><div style="margin-bottom:30px;">我是儿子组件 我接收了 父组件传过来的值------ {{fatherwoson}}</div>//孙子组件 又接着往下传了值<grendeson :fathertoson="fatherwoson"></grendeson></div></template><script>import grendeson from '@/views/page/threecomponents/grendeson'export default {components: {grendeson},props: {//这里儿子从父亲哪里注册了父亲传过来的值 并且向孙子传了下去fatherwoson: {type: String}},data () {return {}}}</script><style></style>
//孙子组件<template><div><div style="margin-bottom:30px;" >我是孙子组件 我在儿子组件里</div><div style="margin-bottom:30px;">我是孙子组件 我接收了爷爷传过来的值-----{{fathertoson}}</div><el-button type="primary" @click="tofather">向爷爷发送值</el-button></div></template><script>export default {props: {//这里孙子从父亲那里接收来的值fathertoson: {type: String}},data () {return {grendson: '我是孙子的数据'}},methods: {tofather () {//开始向爷爷传值 用this.$parent.$emit 触发了父亲身上的方法this.$parent.$emit('fatherto', this.grendson)}}}</script>
- Vue子孙通信(向上派发)
1.上面我们展示了传统的方式 这样只是嵌套了三层 如果我们上面还有组件怎么办2.所以就要写向上派发3.跟eventBus不同的 他是局部 而eventBus 是全局 所有组件都可以触发4.如果 事件都绑定了相同的条件 就要 在写一些条件了
//首先我们需要把方法挂在到Vue的全局上 或者自己写个工具函数也可以 这里我们就写在main.js初始化的时候 就挂在上Vue.prototype.$dispatch = function (eventName, value) {// 当前的组件触发自己的父亲let parent = this.$parentwhile (parent) {// 如果我的父亲存在 我就去触发事件parent.$emit(eventName, value)parent = parent.$parent}}
//然后孙子组件直接调用即可<template><div><div style="margin-bottom:30px;" >我是孙子组件 我在儿子组件里</div><div style="margin-bottom:30px;">我是孙子组件 我接收了爷爷传过来的值-----{{fathertoson}}</div><el-button type="primary" @click="tofather">向爷爷发送值</el-button></div></template><script>export default {props: {fathertoson: {type: String}},data () {return {grendson: '我是孙子的数据'}},methods: {tofather () {// this.$parent.$emit('fatherto', this.grendson)//他就会不停的向上查找this.$dispatch('fatherto', this.grendson)}}}</script>
- Vue子孙通信(向下广播)
1.解决的问题 就是触发 儿子或者儿子的儿子身上绑定的方法2.这里和向上派发的原理差不多 用于爷爷级的组件 向下传值
//main.js里 写在原型链上Vue.prototype.$broadcast = function (eventName, value) {const broadcast = (children) => {children.forEach(child => {console.log(child)child.$emit(eventName, value)if (child.$children) {broadcast(child.$children)}})}broadcast(this.$children)}//然后我们随意在某个组件里调用一下 这里必须是触发方法的前面mounted () {this.$broadcast('say', '我在爷爷组件里向下传递了值')},//只要组件帮了自定义say的标签上都可以触发<grendeson :fathertoson="fatherwoson" @say="sayhi"></grendeson>
- Vue组件通信 v-bind=”$attrs “ / v-on=”$listeners”
1.我们从父级组件传过的数据都在$attrs身上 他们以对象的形式展示出来2.只要你用了$attrs来接收值或者显示 这些值会被挂在到DOM节点上 在标签上就可以看到3.如果你用props注册了某个值 那么$attrs对应的那个值也不会显示4.如果不希望值在标签上显示 可以加上 inheritAttrs: false,5.如果你希望这些值接着向下传递 就可以用v-bind=$attrs 绑定在接收值的组件标签上6.如果要传递方法就可以使用v-on="$listeners" 这里的回调可以用来传值
//父亲页面<template><el-card class="box-card"><div style="margin-bottom:30px;">我是爷爷组件</div><div style="margin-bottom:30px;">我是爷爷组件的值----{{grendeFather}}</div>//传给了儿子值 这里在我们的html标签上可以看到你传递的值都被写在标签上了 这里绑定了一个事件传递给儿子组件//但是儿子组件不接收<son :one="grendeFather" :two="testdataone" :three="testdatatwo" @say="fathersay"></son></el-card></template><script>import son from '@/views/page/five/son'export default {components: {son},methods:{fathersay(res){console.log('我是父亲的方法我被打印了')console.log(res)}}data () {return {grendeFather: '我是爷爷文件里的数据',testdataone: '我是测试数据一',testdatatwo: '我是测试数据二'}}}</script>
//儿子接收<template><div><div style="margin-bottom:30px;">我是儿子</div>//这里$attr获取的是一个对象里面就是你传过来的所有的值的键值对<div style="margin-bottom:30px;">我是爸爸传过来的数据----{{$attrs}}</div>//这样就接着下向传给了孙子组件 儿子没有触发 我就传给了孙子<grandeson v-bind="$attrs" v-on="$listeners"></grandeson></div></template><script>import grandeson from '@/views/page/five/grandeson'export default {//加上这个属性 就不会显示了//inheritAttrs: false,components: {grandeson},porps: {//如果这里我注册了 标签上就不会显示了 而且$attrs里也没有值了one: {type: String},two: {type: String},three: {type: String}}}</script>
//孙子组件<template><div><div style="margin-bottom:30px;">我是孙子</div><div>{{$attrs}}</div><el-button type="primary" @click="btn">我要触发listenter</el-button></div></template><script>export default {methods: {btn () {//这里就触发了爷爷刚才的那个方法 也可以把值传过去this.$listeners.say('我是孙子传过来的值')}}}</script>
- Vue 组件通信 provide inject
1.provide 把当前组件暴露出去 可以是整个组件 也可以组件里的某个值2.inject 要使用暴露的值 就要用inject注册3.任何注册了的组件都可以调用父亲的方法和数据 也可以修改
//爷爷组件<template><el-card class="box-card"><div style="margin-bottom:30px;">我是爷爷组件</div><div style="margin-bottom:30px;">我是爷爷组件的值----{{issomke}}</div><son></son></el-card></template><script>import son from '@/views/page/six/son'export default {components: {son},data () {return {issomke: '我是爷爷的值'}},methods: {say () {console.log('我是父亲的方法')}},//这就是把自己本身暴露出去了 当然你也可以暴露某一个属性和 方法provide () {return {parent: this}}}</script>
//儿子组件<template><div><div style="margin-bottom:30px;">我是儿子</div>//这里就拿到了父亲的值<div style="margin-bottom:30px;">暴露爸爸了拿到里面了的东西了-----{{this.parent.issomke}}</div><el-button type="primary" @click="update">我要修改值了</el-button><grandeson></grandeson></div></template><script>import grandeson from '@/views/page/six/grandeson'export default {// inheritAttrs: false,components: {grandeson},methods: {//也可以调用暴露组件的方法update () {this.parent.issomke = '1111'this.parent.say()}},//注册了父亲的组件inject: ['parent']}</script>
//孙子组件<template><div><div style="margin-bottom:30px;">我是孙子</div><div>我是孙子我也调用爷爷的值------{{this.parent.issomke}}</div></div></template><script>export default {//这里也注入了inject: ['parent'],methods: {}}</script>
- Vue组件通信 ref $refs
1. ref $refs 主要用来调子组件中的方法 在这些方法里你就可以些逻辑 比如改变子组件上大data
//父文件<template><el-card class="box-card"><div style="margin-bottom:30px;">我是爷爷组件</div><div style="margin-bottom:30px;">我是爷爷组件的值----{{issomke}}</div><div style="margin-bottom:30px;"><el-button type="primary" @click="getson">我想调用子组件中的方法</el-button></div>//这里绑定了ref<son ref="sondom"></son></el-card></template><script>import son from '@/views/page/seven/son'export default {components: {son},data () {return {issomke: '我是爷爷的值'}},methods: {getson () {//这里我们就可以通过refs调用sondom中的方法 也就是调用子组件中的方法this.$refs.sondom._sonsay(this.issomke)},fathersay () {console.log('我是父亲的方法')}}}</script>
<template><div><div style="margin-bottom:30px;">我是儿子------{{son}}</div><el-button type="primary" @click="update">我要修改值了</el-button><grandeson></grandeson></div></template><script>import grandeson from '@/views/page/seven/grandeson'export default {// inheritAttrs: false,components: {grandeson},data () {return {son: '爸爸快来修改我'}},methods: {update () {// console.log('我也是子组件中的方法')},_sonsay (res) {//这样就把爷爷的值传过去了 也可以对自己组件的值修改this.son = resconsole.log('我是son中的方法')}}}</script>
- Vue组件通信 eventBus
1.这是个全局的 使用的时候 需用用this.$on('事件名称',() => {}) 来注册2.触发的时候 要使用this.$emit('事件名称','要传递的值')
//我们在全局 Vue的原型上 挂在了busVue.prototype.$bus = new Vue() //这样随时用即可
//父亲组件 挂在了2个儿子<template><el-card class="box-card"><div style="margin-bottom:10px">父亲组件</div><son1 style="margin-bottom:10px"></son1><son2 style="margin-bottom:10px"></son2></el-card></template><script>import son1 from '@/views/page/eight/son1'import son2 from '@/views/page/eight/son2'export default {components: {son1,son2}}</script>
//son1组件挂在了一个孙子<template><div><div>我是儿子一</div><grandeson1></grandeson1></div></template><script>import grandeson1 from '@/views/page/eight/grandeson1'export default {components: {grandeson1}}</script>
//grandeson1<template><div>我是孙子一 在我的mouted里 调用son2中的方法</div></template><script>export default {mounted () {this.$nextTick(() => {//这里触发this.$bus.$emit('xxxx')})}}</script>
//son2<template><div>我是儿子二</div></template><script>export default {mounted () {//这里注册this.$bus.$on('xxxx', function () {console.log(111)})}}</script>
//this.$nextTick 作用我们有子孙组件嵌套的时候 会有加载的顺序从上面的实例我们看出来 我们是先挂在 grandeson1 这里触发了方法 但是那个时候 我们的son2组件还没有开始初始化出来而 this.$nextTick 作用 就是延迟执行
- Vue组件挂载顺序
1.加载渲染过程父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
2.子组件更新过程父beforeUpdate->子beforeUpdate->子updated->父updated
3.父组件更新过程父beforeUpdate->父updated
4.销毁过程父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
如果有个多个同级别的组件 一定是从上向下去加载的 由外到内 在油外到内 详细见下面https://blog.csdn.net/michael8512/article/details/79031656?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase
Vuex
- vuex是什么?
我们在前面进行了非父子组件之中的传值 但是这样比较麻烦 如果涉及的组件比较多 会很复杂 vuex就帮我们解决了这个问题 他可以把数据统一存放在vuex的容器中 然后把数据共享出来 每个组件都可以访问到
- vuex的安装
首先我们还是用 vue create 脚手架搭建一个项目 然后在项目里面npm i vuex直接安装就好
- vuex的配置
//首先在项目文件里建设一个store的文件夹 然后里面新建一个index.js 然后在这个js文件里引入vuex挂载到Vue上 然后导出即可import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({//这里面是配置选项})export default store//以上就是我们在index.js文件里的基本配置//以上文件配置好了之后 我们只需要在main.js里导入 然后挂载到Vue的根实例中就好 跟vue-router一样import store from '@/store'new Vue({store,render: h => h(App),}).$mount('#app')
- 如何验证自己的vuex是否配置成功
//我们只需要在任何一个组件里输入this.$store即可 如果在template上就不用加this<script>export default {name:"bar",created(){console.log(this.$store)}}</script>//输出结果如果为undefined就是没有配置成功 配置成功他会打印出实例对象Store {_committing: false, _actions: {…}, _actionSubscribers: Array(0), _mutations: {…}, _wrappedGetters: {…}, …}
vuex的基础配置项
state——- 类似组件中的data 存放数据中的 不属于任何组件 谁都可以使用
//state 的使用const store = new Vuex.Store({state:{ cont:0 }})//如果在template模板中可以直接用插值表达式加上$store.state.cont 进行使用<p>{{$store.state.cont}}</p>//如果在js标签中 要用this.$store.state.cont来进行使用<script>export default {name:"bar",created(){console.log(this.$store.state.cont)}}</script>
- 调试工具的使用
//可以通过vue浏览器插件 那个小时间的图标去调试使用
- mutations—-专门修改数据的状态 只要他可以别的都不行
我们常规的方法就是在组件去注册点击事件 然后 修改store中的数据<script>export default {name:"foo",methods:{count(){this.$store.state.cont++}}}</script>//但是并不建议这样去做 因为 这样调试工具看不到每次修改的记录 也就不能监测我们建议去在 new Vuex.Store({}) 中的mutations 这个配置项中去注册方法 修改state中的数据 然后在组件中用commit去提交mutations 这样在调试工具每次我们都能监测到数据的改动//首先我们在mutations里定义一个方法 这个方法是让数据++ 然后这个方法的形参就是你的数据 我们只需要把state传进去然后用state.出来里面的数据让他++const store = new Vuex.Store({state:{cont:3},mutations:{contas(state){state.cont++}}})//然后我们在组件里 绑定一个事件每次触发这个事件的时候 让我们的this.$store.commit('contas')去提交你mutations里定义的方法<button @click="count">+</button><script>export default {name:"foo",methods:{//每次触发这个事件的时候 我们用this.$store.commit('contas')去提交我们在mutations里定义的数据count(){this.$store.commit('contas')}}}</script>
- Action—-处理异步操作的
//我们尽量在mutations中处理同步操作 不要处理异步操作 如果在mutations中有异步操作的话 我们的调试工具的记录会有问题 所以这里如果要处理异步操作 我们就在Actions中去处理Vue.use(Vuex)const store = new Vuex.Store({state:{cont:3},//这里我们在mutations中用异步操作去让数据++发现调试工具有问题 所以这里我们要Action去完成异步操作mutations:{asyncconts(state){window.setTimeout( () => {state.cont++},1000)}}})//这里注意 我们只能在mutations中去修改数据 不要在别的配置项中去修改数据 即使可以我们也不要这么做new Vuex.Store({state:{cont :3},mutations:{contjj(state){state.cont++}},actions:{//这里的context就相当于store 然后在这个异步操作里 我们去提交我们的方法jj(context){window.setTimeout(()=>{context.commit('contas')})}}})//然后我们怎么执行这个异步操作呢 用dispatch去触发我们在actions里定义的方法<button @click="$store.dispatch('jj')">actions+</button>//切记修改state里的数据 最好是在mutations里去修改 即使别的地方可以也不要去//如果异步操作 就在actions里去完成 然后用context去提交你mutations里的方法 然后在组件中用dispatch去触发
vuex管理流程
同步
首先我们来说同步的管理流程我们在new Vue.Store({})中的state里定义数据 然后通过$store.state.cont在组件中去渲染数据然后我们在new Vue.Store({})中通过mutations的配置项目 去修改数据 这样可以在vue的调试插件中去监测到数据的改变最后我们在component组件中 用$store.commit('函数名字') 去提交这个方法 就可以出发vue插件^|| 修改数据 渲染视图mutations ----------> state --------> component^ || || $store.commit('方法名称') || --------------------------------------|
- 异步
在异步中 我们在组件中通过dispatch去触发actions中的时间 然后actions去 还是通过commit去提交mutations中的方法 然后mutations去修改state中的数据 修改完了 state里去render视图vue插件^|| 修改数据 渲染视图mutations ----------> state --------> component^ || || context.commit $store.dispath |<-----------------Actions<--------------|异步操作完成提交 |||和后台交互//通过上面我们可以发现 从开始到结束 只有mutations去修改数据 Actions去提交mutations然后和后台交互
- state的进阶用法
//如果我们在同一个组件中需要用到很多次相同的数据 我们只能多次写$store.state.cont 这样写//所以我们可以通过计算属性computed{}去把我们的这个数据返回 就可以直接调用了Vue.use(Vuex)const store = new Vuex.Store({state:{cont:1},})//然后我们通过在组件用computed计算属性去把这个数据返回<script>export default {name:"foo",computed:{countt(){return this.$store.state.cont}}}</script>//然后直接用差值表达式 去直接使用就好了<p>{{countt}}</p><p>{{countt}}</p><p>{{countt}}</p><p>{{countt}}</p>//但是这样我们只是使用的一个 如果store.state里有多个数据怎么办 所以我们就用到了他的核心API
- state的核心API Mapstate
//我们在new VuexStore({})的state中定义数据Vue.use(Vuex)const store = new Vuex.Store({state:{cont:1,a:3},})export default store//首先我们要在组件中导入这个APIimport {mapState} from 'vuex'//在计算属性展开这个API 里面可以是一个对象 也可以是一个数组 数组的每一位就是Vuex.Store.state中的数据<script>import {mapState} from 'vuex'export default {name:"foo",computed:{...mapState(['cont','a'])}}</script>//这样写的好处 就是 你可以在计算属性中写你组件自己的计算属性函数
mutations的核心API和参数传递
传参
//首先还是在我们new Vuex.Store({})定义我们的数据和我们的mutationsVue.use(Vuex)const store = new Vuex.Store({state:{cont:1,},mutations:{//只不过这里我们在mutations里定义我们的方法发的时候 要传进去一个形参 然后我们在组件中用commit提交的时候后面跟一个实参即可jj(state,n){state.cont += n}}})export default store//在组件中使用$store.commit('函数名','你要传递的实参') 这样既可<div class="com"><h2>foo component</h2><p>{{$store.state.cont}}</p><button @click="$store.commit('jj',10)">mutations+</button></div>//参数你可以传数值 对象 都可以
- mutations核心API mapMutations
//现在我们的mutations中有两个方法 如果我们一个一个提交又太麻烦 所以我们可以在组件中的methods里用mapMutations去提交多个方法//首先我们还在new Vuex.Store({})的mutations中定义多个方法Vue.use(Vuex)const store = new Vuex.Store({state:{cont:1,},mutations:{jj(state,n){state.cont += n},jjj(state,m){state.cont += m}},})export default store//参数我们还是按照正常的写 然后我们在组件中导入mutations的API 然后在组件的methods中通过mapMutations去提交多个函数方法<script>import {mapMutations} from 'vuex'export default {name:"foo",methods:{showjj(){this.jj(10)},showjjj(){this.jjj(20)},//这里通过在组件的methods里用...mapMutations里去提交多个方法...mapMutations(['jj','jjj'])}}</script>//调用h2>foo component</h2><p>{{$store.state.cont}}</p><button @click="showjj">jj+</button><button @click="showjjj">jjj+</button>
- actions的核心API mapActions
//和我们的mutations一样 如果我要dispatch通过actions里的异步方法话 我们也需要在methods里用它的核心API去提交//首先我们还是在new Vuex.Store({})中去定义我们的mutations和actionsVue.use(Vuex)const store = new Vuex.Store({state:{cont:1,},mutations:{jj(state,n){state.cont += n},jjj(state,m){state.cont += m}},actions:{jj(context){window.setTimeout(()=>{context.commit('jj',10)},1000)},jjj(context){window.setTimeout(()=>{context.commit('jjj',20)},1000)},}})export default store//定义好了之后我们需要在组件中导入我们的apiimport {mapActions} from 'vuex'//然后还是在methods里用mapActions去映射我们的actions里的方法import {mapMutations,mapActions} from 'vuex'export default {name:"foo",methods:{//这里是mutations中的函数showjj(){this.jj(10)},showjjj(){this.jjj(20)},//这里是mutations中的函数...mapMutations(['jj','jjj'])//现在明显actions中的函数和mapmutations中的函数重名了 所以我们就需要下面的方式去改名字...mapActions(['jj',"jjj"])}}</script>//但是这里会有一个问题 因为们的mutions和actions里的方法都映射到了本地会出现重名的现象 所以我们这里要不就在用...mapActions的时候 把名字替换掉 要不就在你new Vuex.Store({})里把名字改掉//这里是用传入对象的方式去改名字...mapActions({actionsshowii:'jj',actionsshowiii:'jjj',})//然后直接调用你改过的这个名字就好了<button @click="actionsshowjj">actions jj+</button><button @click="actionsshowjjj">actions jjj+</button>//然后直接这也样就直接映射到你的组件中了 就可以通过this调用了actionsshowjj(){this.actionsshowii()},actionsshowjjj(){this.actionsshowiii()},
- getters的属性 类似组件中的计算属性cumputed
//这里我们用案例来说明//现在我们在new Vuex.store({}) 有一个todolist 数据 这个数据的done记录着 完成和为完成Vue.use(Vuex)const store = new Vuex.Store({state:{cont:1,todolist:[{id:1, name:'zs',done:false},{id:1, name:'zs',done:false},{id:1, name:'zs',done:true},{id:1, name:'zs',done:true},{id:1, name:'zs',done:true}]},})export default store//现在我们想在每个组件都看到未完成事件的数量 所以我们要在getters里把这个数组循环 然后把结果 渲染到页面上getters:{jilu(state){let count = 0state.todolist.forEach(item=>{if(item.done == false){count++}})return count}}//通过以上的方法 我们就把没有完成事件的数量 计算出来了 那么接下来我们就可以通过$store.getters.jilu去把我们的结果渲染到页面上<p>{{$store.getters.jilu}}</p>
- getters的核心API mapGetters
//我们上面已经讲了getters的使用 和渲染到页面的方式 这里是通过$store.getters.jilu 还是那个问题 一个还好如果是多个怎么办呢 所以我们就有了mapGetters这个API 不过这个和我们state一样要在组件的computed里去映射到组件上getters:{jilu(state){let count = 0state.todolist.forEach(item=>{if(item.done == false){count++}})return count},jiluwc(state){let count = 0state.todolist.forEach(item=>{if(item.done == true){count++}})return count}}//上面我们在getters里定义两个方法 现在想把这两个方法一起映射到组件中去//首先还是要导入mapGetters这个APIimport {mapGetters} from 'vuex'//然后我们在组件的计算属性computed里用mapGetters去映射computed:{...mapGetters(['jilu','jiluwc'])}//然后在页面中直接使用<p>{{jilu}}</p><p>{{jiluwc}}</p>
- module的使用
//如果我们使用多个vuex的时候 代码就会变的很臃肿 所以这里我们可以通过module把我们的state mutations actions getters都分隔出去//vuex允许我们使用mudules把Vuex.Store对象分隔成多个模块 每个模块都有自己的state mutations actions getters
- 基础写法
const moduleA = {state:{...},mutations:{...},getters:{...},actions:{...}}const moduleB = {state:{...},mutations:{...},getters:{...},actions:{...}}const store = new Vuex.Store({modules:{a:moduleA,b:moduleB}})store.state.a ---> moduleA的状态store.state.b ---> moduleB的状态
- module的分隔方法
//这里我们来用案例演示一下 这个案列就是一个购物车 然后分商品详情的列表 和购物的详情列表//首先我们还是要新建一个store的文件夹 里面有个index.js文件 用来初始化我们的vuex.store({})对象import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({state: {},mutations: {},getters: {},actions: {}})export default store//然后我们在main.js入口文件里导入 然后挂载到new Vue({})上import store from '@/store'//挂载到Vue实例对象上new Vue({store,router,render: h => h(App)}).$mount('#app')
- module分隔商品列表 和 购物车的商品列表
//现在知道我们两个组件一个是商品列表 一个是购物车清单列表 都需要用到数据 所以我们单独把他们分出模块去写//products模块 跟我们vuex.store的写法一样 新建一个products.js文件 然后把他导出const state = {}const mutations = {}const getters = {}const actions = {}export default {state,mutations,getters,actions}//这样我们的模块就products模块就编辑好了 然后在我们的index.js 也就是new Vuex.Store({})中导入 用modules配置选项把挂载到上面import Vue from 'vue'import Vuex from 'vuex'import products from './products' //导入你的模块import cart from './cart' //导入你的模块Vue.use(Vuex)const store = new Vuex.Store({state: {},mutations: {},getters: {},actions: {},//然后用moduls把我们的两个模块 挂载到我们的根 new Vuex.Store({})中去modules: {products,cart}})
- 我们分隔的模块中数据的使用 state
//通过以上操作我们就把我们的模块都分隔出去了 那么如果和使用每个模块中的数据呢最简单的粗暴的方法 现在比如我们的products中state有数据// 这里是products中的数据模块const state = {data: 'products 中的 data'}//我们只需要在使用他页面 用原始的方法 $store.state.products.data 即可<p>{{ $store.state.products.data}}</p>
- 我们分隔的模块中mutations的使用
//现在我再products模块中的mutations里定义了方法 如何提交// 这里是products中的数据模块const state = {data: 'products 中的 data'}const mutations = {editdata (state) {state.data = 'hellow'}}//我们以往的写法就是$store.commit('你在mutations里定义的名字') 在模块里我们还是这么写他会自动去寻找那个方法<p>{{ $store.state.products.data}}</p><el-button @click="$store.commit('editdata')">点击</el-button>//这里他会自己寻找那个方法 如果 每个模块中都有相同的方法 他们就会同时执行 顺序按理modules中的排列顺序为准
- 我们分隔的模块中actions的使用
//actions的使用跟我们平时一样 就是用$store.patch('你actions里的函数名字') 规则遵循跟mutations的规则一致// 这里是products中的数据模块const state = {data: 'products 中的 data'}const mutations = {editdata (state) {state.data = 'hellow'}}const getters = {}const actions = {getdata (context) {window.setTimeout(() => {context.commit('editdata')}, 1000)}}//然后我们在页面中用patch去映射<el-button @click="$store.dispatch('getdata')">actions点击</el-button>//这里注意一下如果你两个模块中的在mutations里的函数名字是相同的话 你用actions去dispatch 两个都会去提交 下面是例子// 这里是products中的数据模块const state = {data: 'products 中的 data'}//我再products的mutations中有editdata的方法const mutations = {editdata (state) {state.data = 'hellow'}}//我是cart模块中的const state = {data: 'cart 中的data'}//我再cart模块的mutations中也定义了editdata的方法const mutations = {editdata (state) {state.data = '你好'console.log(1)}}//然后我再products模块中的 actions里执行了异步操作 这个时候两个模块中mutations都会被提交//我是product中的actionsconst actions = {getdata (context) {window.setTimeout(() => {context.commit('editdata')}, 1000)}}//页面中<el-button @click="$store.dispatch('getdata')">actions点击</el-button>
- 命名空间
//通过以上操作我们知道了 如果你每个模块都有相同的mutations里的方法 并且名字一致的时候 我们在提交的时候 会一起都提交上去//但是如果我们只想提交单独的一个模块mutations里的方法 的时候 就需要用到一个配置 namespaced//只需要在导出模块的把他设置为true即可export default {namespaced: true,state,mutations,getters,actions}
- 命名空间里成员的使用
//mutitonsthis.$store.commit('products/editdata') //这里采用路径的方前面是你的模块名称 后面是你要提交的方式//actionsthis.$store.patch('products/editdata') //这里采用路径的方前面是你的模块名称 后面是你要提交的方式
给模块开启命名之后 API 在模块中的使用方式
state的使用
import { mapState } from 'vuex'//首先还和以前一样 不过这里我们自己要指定 是哪个模块的的数据即可computed: {//这里我指定了products中的数据...mapState('products', ['data'])},//多个写法computed: {...mapState('products', ['data']),...mapState('cart', ['cartdata'])},//切记这里 你在每个模块中的state里叫什么名字 这里用API映射得就是什么名字
- mutations、actions核心API的使用
//这里我们的使用还是跟上面state一样 首先还是要导入我们的核心apiimport { mapMutations, mapActions} from 'vuex'...mapMutations('你的命名空间的模块的名字',你要提交的函数)...mapActions('你的命名空间的模块的名字',你要提交的函数)//如下methods: {...mapActions('products', ['getlist'])},//如果个是多个要提交的mutations 或者 actions 可以在数组里增加methods: {...mapActions('products', ['getlist','getimag'])},
demo附件
前端工程化
nodeJs
直接安装nodejs 去官网去装就好
如何查看自己有没有装nodejs 直接在命令行里 node -v就可以查看自己安装的版本号
如何使用node 运行js文件
首先要在你要运行的js文件的目录下面 打开小黑窗 然后 直接node '你要运行的文件名'
- nodejs主要内容
第一块 全局变量 global主要包含 console process module setInterval __filename __dirname require第二块 就是我们的 ES6语法第三块 就是核心模块fs path querystring os http
- fs模块的使用 文件系统模块
//这里模块的意思就是包含很多属性和方法 可以帮我们实现功能fs模块 可以创建文件删除文件读取文件获取文件等操作
- readFile 文件读取
//首先还是要加载fs模块 用require去加载这个模块const fs = require('fs')fs.readFile('要读取的路径','要设置的字符集',(err,data) => {})//-------------------------//const fs = require('fs')fs.readFile('a.txt','utf-8',(err,data) => {})这里如果读取文件成功 err就是null data就是文件的内容这里如果读取失败 err就保存了错误信息 data就是undefined
- writeFile 文件写入
//如果有的话 就会覆盖原有的文件const fs = require('fs')fs.writeFile('要写入的文件路径','要写入的字符串','配置选项字符编码集默认为utf8',function(){写入完成之后出发的回调函数})//----------------------//const fs = require('fs')fs.writeFile('./c.txt','你好',(err) => {if(err){return console.log(err)}console.log('写入成功')})
- appendFile 文件追加
//可以在原有文件的基础上 向后面追加内容const fs = require('fs')fs.appendFile('要写入的文件路径','要写入的字符串','配置选项字符编码集默认为utf8',function(){写入完成之后出发的回调函数})//----------------------//const fs = require('fs')fs.appendFile('./c.txt','你好',(err) => {if(err){return console.log(err)}console.log('写入成功')})
- 请求流程
1.在浏览器的url地址栏输入地址的时候按下回车 浏览器会向对象的服务器发送请求2.服务器收到请求之后 找到对应的文件 并将文件中的内容读取出来3.服务器讲读取出来的文件内容返回给浏览器
- 使用http模块搭建web服务器
1.加载导入http模块2.创建服务器对象3.开启服务器4.监听浏览器的请求 并进行处理和响应
- 搭建代码
//首先我们还是要导入http模块const http = require('http')//创建服务器对象const server = http.createServer()//为我们的服务器开始一个端口号server.listen(3000,() => {console.log('server is running...')})//当有浏览器访问这个服务器的时候 我们通过on来监听他的request事件 来做出响应 回调函数的参数req请求对象 res是响应对象 这里我们通过res给浏览器响应的内容server.on('request',(req,res) => {console.log('哈哈成功了')})
- 响应给浏览器内容 res.end(‘给浏览器内内容’)
const http = require('http')const server = http.createServer()server.listen(3000,() => {console.log('server is running...')})server.on('request',(req,res) => {console.log(req.url) //这里就是你端口号后面 也就是斜杠后面就的字符console.log(req.method) //这里是浏览器的请求的方式console.log(req.headers) //这里是请求头信息})
- 根据不同的url地址返回给浏览器不同的内容
//我们可以通过判断req.url可以返回给浏览器不同的响应内容const http = require('http')const server = http.createServer()server.listen(3000,() => {console.log('server is running...')})server.on('request',(req,res) => {if(req.url == '/index'){res.end('I AM Admin')}else if(req.url == '/home'){res.end('I am home')}})
- 响应对象 解决中文乱码问题
const http = require('http')const server = http.createServer()server.listen(3000,() => {console.log('server is running...')})server.on('request',(req,res) => {if(req.url == '/index'){//这里就是通过设置响应头 告诉浏览器 你按照什么格式去给我解析res.setHeader('content-type','text/html;charset=utf-8')res.end('我是管理员')}else if(req.url == '/home'){//这里也可以根据写响应状态码的形式来设置响应头res.writeHeader(200,{'content-type':'text/html;charset=utf-8'})res.end('我是首页')}})
- 读取文件返回给页面
const http = require('http')const fs = require('fs')const server = http.createServer()server.listen(3000,() => {console.log('server is running...')})server.on('request',(req,res) => {if(req.url == '/index'){//当你请求的URL是index的时候 我就去读文件res.setHeader('content-type','text/html;charset=utf-8')//然后把读取文件的内容通过res.end去返还给浏览器fs.readFile('./c.txt',(err,data) => {if(err){console.log(err)res.end('失败了')}res.end(data)})}else if(req.url == '/home'){res.setHeader('content-type','text/html;charset=utf-8')res.end('我是首页')}})
- 静态资源加载
当我们html文件里面 里有引入css文件和js文件的时候 就相当于又向我们的服务器发送了请求 所以我们也需要监听res.url然后通过fs.readFile读文件 然后给浏览器返回内容 res.end(data)
- 代码
const http = require('http');const server = http.createServer();server.listen(8888, () => {console.log('Server is running...');})const fs = require('fs');server.on('request', (req, res) => {//判断url地址if (req.url === '/index.html') {fs.readFile('./view/index.html', (err, data) => {if (err) {console.log(err);return res.end('404 not found');}res.end(data)})} else if (req.url === '/movie') {fs.readFile('./view/movie.html', (err, data) => {if (err) {console.log(err);return res.end('404 not found');}res.end(data)})} else if (req.url === '/admin/login') {fs.readFile('./view/admin/login.html', (err, data) => {if (err) {console.log(err);return res.end('404 not found');}res.end(data)})//这里就是通过有判断了一下你css文件和js文件的请求url然后去读文件 返回给浏览器内容} else if (req.url === '/public/css/a.css') {fs.readFile('./view/public/css/a.css', (err, data) => {if (err) {console.log(err);return res.end('404 not found');}res.end(data)})} else if (req.url === '/public/js/b.js') {fs.readFile('./view/public/js/b.js', (err, data) => {if (err) {console.log(err);return res.end('404 not found');}res.end(data)})}})
- 统一处理静态资源 css文件 js文件
因为我们的静态资源文件 都是以什么什么开头的 所以我们可以通过判断 让他都去拿个文件夹里去找文件else if (req.url.startsWith('/public')) {//读取文件时,文件在磁盘上的物理路径刚好和url地址一致,并且都在view目录下//所以,读取文件的相对路径就是 /view + req.url//可以通过几个文件的路径来验证//例如:// req.url = /public/css/a.css ===> 物理路径是 ./view/public/css/a.css// req.url = /public/js/b.js ===> 物理路径是 ./view/public/js/b.jsfs.readFile('./view' + req.url, (err, data) => {if (err) {console.log(err);return res.end('404 not found');}res.end(data)})
- 相对路径存在的问题
如果在我们自己的电脑上用相对路径的话 因为是以我们命令执行为相对起点的 所以 如果拷贝到别的电脑上的时候会出错 所以要改为绝对路径
- 路径变量模块
__dirname 获取当前文件所处目录的绝对路径 不包含文件本身只是路径指向文件所在的位置__filename 获取当前文件的绝对路径 包含文件本身自动处理路径分隔符 有的系统支持\有的系统支持/ 所以当我们用了路径变量之后 就会统一帮我们处理
- path.join方法
首先还是要加载path模块const path = require('path')path.join('D:','http','index.js') //这样就把\或者/统一处理了//这里我们长配合__dirname 去使用path.join(__dirname,'view','index.html')
- 实例
const path = require('path');path.join('aaa', 'bbb', 'ccc'); // windows: aaa\bbb\ccc 类unix: aaa/bbb/cccpath.join('d:/', 'node', 'test.js'); // d:\node\test.jspath.join('d:\\', 'node/test.js'); // d:\node\test.jspath.join(__dirname, 'view', 'index.html'); // d:\node\day-2\code\view\index.htmlpath.join(__dirname, 'view/list.html'); // d:\node\day-2\code\view\list.html
信贷前端工作流

