首页 > 其他分享 >CoderWhy深入Jacascript高级语法完整直播版

CoderWhy深入Jacascript高级语法完整直播版

时间:2024-12-11 21:13:47浏览次数:3  
标签:Jacascript coderwhy const 函数 对象 直播 console CoderWhy 属性

CoderWhy深入Jacascript高级语法完整直播版

01浏览器工作原理和V8引擎

https://ke.qq.com/course/3619571?tuin=1635c7de#term_id=104381716

TypeScript 太s快

coderwhy TypeScript会取代JavaScript吗?
TypeScript只是给JavaScript带来了类型的思维?
因为JavaScript本身长期是没有对变量、函数参数等类型进行限制的;这可能给我们的项目带来某种安全的隐患;在之后的JavaScript社区中出现了一系列的类型约束方案:
口2014年,Facebook推出了flow来对JavaScript进行类型检查;同年,Microsoft微软也推出了TypeScript1.0版本;他们都致力于为JavaScript提供类型检查,而不是取代JavaScript;并且在TypeScript的官方文档有这么一句话:源于JavaScript,归于JavaScript!
TypeScript只是JavaScript的一个超级,在它的基础之上进行了扩展;口并且最终TypeScript还是需要转换成JavaScript代码才能真正运行的;当然我们不排除有一天JavaScript语言本身会加入类型检测,那么无论是TypeScript,还是Flow都会退出历史舞台。

我们经常会说:不同的浏览器有不同的内核组成
口Gecko:早期被Netscape和Mozilla Firefox浏览器浏览器使用;
口Trident:微软开发,被IE4~IE11浏览器使用,但是Edge浏览器已经转向Blink;
口Webkit:苹果基于KHTML开发、开源的,用于Safari,Google Chrome之前也在使用;
口Blink:是Webkit的一个分支,Google开发,目前应用于Google Chrome、Edge、Opera等;
口等等...

事实上,我们经常说的浏览器内核指的是浏览器的排版引擎:
口排版引擎(layout engine),也称为浏览器引擎(browser engine)、页面渲染引擎(rendering engine)
或样版引擎。

为什么需要JavaScript引擎呢?
口我们前面说过,高级的编程语言都是需要转成最终的机器指令来执行的;
口事实上我们编写的JavaScript无论你交给浏览器或者Node执行,最后都是需要被CPU执行的;
口但是CPU只认识自己的指令集,实际上是机器语言,才能被CPU所执行;
口所以我们需要JavaScript引擎帮助我们将JavaScript代码翻译成CPU指令来执行;

比较常见的JavaScript引擎有哪些呢?
口SpiderMonkey:第一款JavaScript引擎,由Brendan Eich开发(也就是JavaScript作者);
口Chakra:微软开发,用于IT浏览器;
口JavaScriptCore:WebKit中的JavaScript引擎,Apple公司开发;
口V8:Google开发的强大JavaScript引擎,也帮助Chrome从众多浏览器中脱颖而出;
口等等…..

这里我们先以WebKit为例,WebKit事实上由两部分组成的:
口WebCore:负责HTML解析、布局、渲染等等相关的工作;
口JavaScriptCore:解析、执行JavaScript代码;

看到这里,学过小程序的同学有没有感觉非常的熟悉呢?
口在小程序中编写的JavaScript代码就是被JSCore执行的;

我们来看一下官方对V8引擎的定义:
口V8是用C ++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等。
口它实现ECMAScript和WebAssembly,并在Windows 7或更高版本,macOS 10.12+和使用x64,IA-32,ARM或MIPS处理器的Linux系统上运行。
口V8可以独立运行,也可以嵌入到任何C++应用程序中。

http://astexplorer.net

ast抽象语法树

C++

coderwhy V8引擎的架构
V8引擎本身的源码非常复杂,大概有超过100w行C++代码,通过了解它的架构,我们可以知道它是如何对JavaScript执行的:
Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码;
如果函数没有被调用,那么是不会被转换成AST的;
Parse的V8官方文档:https://v8.dev/blog/scanner
Ignition是一个解释器,会将AST转换成ByteCode(字节码)
同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算);
如果函数只调用一次,Ignition会执行解释执行ByteCode;
Ignition的V8官方文档:https://v8.dev/blog/ignition-interpreter
TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码;
如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能;
但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码;
TurboFan的V8官方文档:https://v8.dev/blog/turbofan-jit

http://v8.dev/blog/scanner

github.com/v8/v8/tree/master/src

coderwhy V8执行的细节
那么我们的JavaScript源码是如何被解析(Parse过程)的呢?
Blink将源码交给V8引擎,Stream获取到源码并且进行编码转换;
Scanner会进行词法分析(lexical analysis),词法分析会将代码转换成tokens;
接下来tokens会被转换成AST树,经过Parser和PreParser:
Parser就是直接将tokens转成AST树架构;
PreParser称之为预解析,为什么需要预解析呢?
这是因为并不是所有的JavaScript代码,在一开始时就会被执行。那么对所有的JavaScript代码进行解析,必然会影响网页的运行效率;
所以V8引擎就实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
比如我们在一个函数outer内部定义了另外一个函数inner,那么inner函数就会进行预解析;

GEC(global exection context)

VO(variable Object):GO VO指向GO

GO global object 全局对象

ECStack

1.代码被解析,v8引擎内部会帮助我们创建一个对象(GlobalObject-> go)
2.运行代码
2.1.-v8为了执行代码,v8引擎内部会有一个执行上下文栈(Execution Context Stack,ECStack)(函数调用栈)
2.2,因为我们执行的是全局代码,为了全局代码能够正常的执行,需要创建-全局执行上下文(Global-Execution-Context)(全局代码需要被执行时才会创建)

coderwhy初始化全局对象
js引擎会在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)
口该对象所有的作用域(scope)都可以访问;
口里面会包含Date、Array、String、Number、setTimeout、setInterval等等;
口其中还有一个window属性指向自己;

coderwhy执行上下文栈(调用栈)
js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈。
那么现在它要执行谁呢?执行的是全局的代码块:
口全局的代码块为了执行会构建一个Global Execution Context(GEC);
口GEC会 被放入到ECS中 执行;

vscode插件 Draw.io integration

02函数执行-作用域链-面试题-内存管理

函数执行上下文(FEC) Function Execution Context

VO指向AO AO(Activation Object)

AO(Activation Object) 函数里面的变量 参数提升到AO

当我们查找一个变量时,真实的查找路径是沿着作用域链来查找

scope chain: AO+GO

window本身上就有name属性

01:17:03

ECStack调用栈

函数执行上下文(FEC)

GEC(global excution context)全局执行上下文:执行全局代码

js红宝石 javascript高级程序设计

es5以前叫 AO/VO/GO 大部分面试官知道的

ES5以后叫 在最新的ECMA的版本规范中,对于一些词汇进行了修改:

VO==>Variable Environment(变量环境)

添加到环境记录 Environment Record

JavaScript会在定义变量时为我们分配内存。
但是内存分配方式是一样的吗?
口JS对于基本数据类型内存的分配会在执行时,直接在栈空间进行分配;
口JS对于复杂数据类型内存的分配会在堆内存中开辟一块空间,并且将这块空间的指针返回值变量引用;

引用 js java

指针 c

php dsx

GC 垃圾回收器

常见的GC算法-引用计数

引用计数存在一个很大的弊端:循环引用

常见的GC算法-标记清除

JS引擎比较广泛的采用的就是标记清除算法,当然类似于V8引擎为了进行更好的优化,它在算法的实现细节上也会结合一些其他的算法。

03闭包的定义-理解-内存模型-内存泄露

php java...dsx

高阶函数:把一个函数如果接受另外一个函数作为参数,或者该函数会返回另外一个函数作为返回值的函数,那么这个函数就称之为是一个高阶函数

函数function:独立的function,那么称之为是一个函数

方法method:当我们的一个函数属于某一个对象时,我们成这个函数是这个对象的方法

闭包是两部分组件的:函数+可以访问的自由变量

那么我的理解和总结:
一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
从广义的角度来说:JavaScript中的函数都是闭包;
从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;

调用栈 销毁 AO对象不销毁 有引用

fn=null 0x0

04闭包内存回收和this的四个绑定规则

vue3+react
口vue3 composition api:setup函数->代码(函数hook,定义函数);
口react:class-> function-> hooks

chrome 打开开发者工具 刷新区-右击-强刷新

debugger console 刚好的位置

从某些角度来说,开发中如果没有this,很多的问题我们也是有解决方案
但是没有this,会让我们编写代码变得非常的不方便

在大多数情况下,this都是出现在函数中
在全局作用域下
浏览器:window (globalObject)
Node 环境:{}

01:27:00

common js 简称cjs

es module 简称esm

coderwhy为什么需要this?
在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是JavaScript中的this和常见的面向对象语言中的this不太一样:
常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中。
也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象。
但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义。

我们先说一个最简单的,this在全局作用于下指向什么?
口这个问题非常容易回答,在浏览器中测试就是指向window

但是,开发中很少直接在全局作用于下去使用this,通常都是在函数中使用。
口所有的函数在被调用时,都会创建一个执行上下文:
口这个上下文中记录着函数的调用栈、AO对象等;
口this也是其中的一条记录;

this动态绑定 执行时确定

这个的案例可以给我们什么样的启示呢?
1.函数在调用时,JavaScript会默认给this绑定一个值;
2.this的绑定和定义的位置(编写的位置)没有关系;
3.this的绑定和调用方式以及调用的位置有关系;
4.this是在运行时被绑定的;

那么this到底是怎么样的绑定规则呢?一起来学习一下吧
绑定一:默认绑定;
绑定二:隐式绑定;
绑定三:显示绑定;
绑定四:new绑定;

coderwhy规则一:默认绑定
什么情况下使用默认绑定呢?独立函数调用。
口独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;

coderwhy规则二:隐式绑定
另外一种比较常见的调用方式是通过某个对象进行调用的:
口也就是它的调用位置中,是通过某个对象发起的函数调用。

隐式绑定:object,fn()
object对象会被js引擎绑定到fn函数的中this里面

coderwhy规则三:显示绑定
隐式绑定有一个前提条件:
口必须在调用的对象内部有一个对函数的引用(比如一个属性);
口如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
口正是通过这个引用,间接的将this绑定到了这个对象上;

如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)。
它们两个的区别这里不再展开;
√其实非常简单,第一个参数是相同的,后面的参数,apply为数组,call为参数列表;
口这两个函数的第一个参数都要求是一个对象,这个对象的作用是什么呢?就是给this准备的。
口在调用这个函数时,会将this绑定到这个传入的对象上。

foo直接调用和coll/apply调用的不同在于this绑定的不同
foo直接调用指向的是全局对象(window)

剩余参数

call 和apply在执行函数时,是可以明确的绑定this,这个绑定规则称之为显示绑定

默认绑定和显示绑定bind冲突:优先级(显示绑定)

coderwhy new绑定
JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
使用new关键字来调用函数是,会执行如下的操作:
1.创建一个全新的对象;
2这个新对象会被执行prototype连接;
3.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
4.如果函数没有返回其他对象,表达式会返回这个新对象;

我们通过一个new关键字调用一个函数时(构造器),这个时候this是在调用这个构造器时创建出来的对象this = 创建出来的对象这个绑定过程就是new 绑定

05this绑定规则细节和面试题分析

王红元 coderwhy

call kou apply 二婆懒 bind 班

setTimeout 独立调用 window对象 this

arr.forearch(fn,thiis)

coderwhy规则优先级
学习了四条规则,接下来开发中我们只需要去查找函数的调用应用了哪条规则即可,但是如果一个函数调用位置应用了多条规则,优先级谁更高呢?
1.默认规则的优先级最低
口毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
2.显示绑定优先级高于隐式绑定
口代码测试

3.new绑定优先级高于隐式绑定
代码测试

new绑定>·显示绑定(opply/coll/bind)>·隐式绑定(obj.foo())->·默认绑定(独立函数调用)

bind高于call

apply/call/bind:当传入null/undefined时,自动将this绑定成全局对象

加(小括号) 要加分号 否则error当一个整体

()()独立函数 this指向window

coderwhy箭头函数arrow function
箭头函数是ES6之后增加的一种编写函数的方法,并且它比函数表达式要更加简洁:
口箭头函数不会绑定this,arguments属性;箭头函数如何编写呢?
口箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误);
箭头函数如何编写呢?
口():函数的参数
口{}:函数的执行体

coderwhy this规则之外-ES6箭头函数
箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。

https://mp.weixin.qq.com/s/hYm0JgBI25grNG_2sCRITA?

对象没有作用域 函数有

06 apply-call-bind实现-参数解析

边界情况 edge case

coderwhy实现apply,call,bind接下来我们来实现一下apply、call、bind函数:
口注意:我们的实现是练习函数、this、调用关系,不会过度考虑一些边界情况

看完 手打一个代码

Function.prototype.myCall=function(thisArg,...args){
    let fn=this //隐式调用
    thisArg=(thisArg!==null&&thisArg!==undefined)?Object(thisArg):window //传undefined null 指向window全局
    thisArg.fn=fn //1
    let result=thisArg.fn(...args) //2
    delete thisArg.fn //3  1 2 3不用call调用
    return result //call函数有返回值
}

function foo(){
    console.log("foo调用了",this)
}
function sum(num1,num2){
    return num1+num2
}
foo.myCall()
let re=sum.myCall({},20,30)
console.log(re)
Function.prototype.myApply=function(thisArg,argArray){
    let fn=this
    thisArg=(thisArg!==null&&thisArg!==undefined)?Object(thisArg):window
    thisArg.fn=fn
    let result
    //一:
    // if(!argArray){//没传值时
    //     result=thisArg.fn()
    // }else{//传值时
    //     result=thisArg.fn(...argArray)
    // }
    //二:
    // argArray=argArray?argArray:[]
    //三:
    argArray=argArray||[]
    result=thisArg.fn(...argArray)
    delete thisArg.fn
    return result
}

function foo(){
    console.log("foo调用了",this)
}
function sum(num1,num2){
    return num1+num2
}
foo.myApply({})
let re=sum.myApply({},[20,30])
console.log(re)

01:08:13

{}.fn里面有个fn就覆盖的了 this找不到 fn使用symbol

var bar=sum.bind('aaa',1,2,3)
bar()
var bar=sum.bind('aaa')
bar(1,2,3)
var bar=sum.bind('aaa',1,2)
bar(3)
Function.prototype.myBind=function(thisArg,...argArray){
    //1.获取到真实需要调用的函数
    let fn=this
    //2.绑定this
    thisArg=(thisArg!==null&&thisArg!==undefined)?Object(thisArg):window
    function proxyFn(...arg){
        //3.将函数放到thisArg中进行调用
        thisArg.fn=fn
        //特殊:对两个传入的参数进行合并
        let finalArg=[...argArray,...arg]
        let result=thisArg.fn(...finalArg)
        delete thisArg.fn
        //4.返回结果
        return result
    }
    return proxyFn
}

function foo(){
    console.log("foo执行了",this)
}
function sum(num1,num2, num3){
    console.log(num1,num2,num3)
    return num1+num2+num3
}

let fn=foo.myBind({})
fn()

let su=sum.myBind({},2,3)
let result=su(5)
console.log(result)

1w+问

coderwhy认识arguments
arguments 是一个 对应于 传递给函数的参数 的 类数组(array-like)对象。

arguments 啊旧门死

类数组对象中(长的像是一个数组,本质上是一个对象):arguments

console.log(arguments.length)
arguments[2]
arguments.callee 获取当前arguments所在的函数
arguments.callee() 递归调用 不要这样做

array-like意味着它不是一个数组类型,而是一个对象类型:
口但是它却拥有数组的一些特性,比如说length,比如可以通过index索引来访问;
口但是它却没有数组的一些方法,比如forEach,map等;

转数组
{
    var newArr=[]
    for(var i=0;i<arguments.length;i++){
        newArr.push(arguments[i])
    }
}
var newArr=Array.prototype.slice.call(arguments) //加call 改this指向
var newArr=[].slice.call(arguments)
var newArr=Array.from(arguments)
var newArr=[...arguments]

箭头函数中没有arguments 向上级找

浏览器全局没有arguments node有

浏览器全局有name node没有

07纯函数-柯里化实现-组合函数

JS函数式编程

编程范式(方式)
面向对象方式

coderwhy理解JavaScript纯函数
函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念;
口在react开发中纯函数是被多次提及的;
口比如react中组件就被要求像是一个纯函数(为什么是像,因为还有class组件),redux中有一个reducer的概念,也是要求必须是一个纯函数;
口所以掌握纯函数对于理解很多框架的设计是非常有帮助的;

setup-> 函数-> 编写很多其他的逻辑->更加接近于原生开发-> 函数的概念-> hook
vue.options data/methods/computed

纯函数的维基百科定义:
口在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数:
口此函数在相同的输入值时,需产生相同的输出。
口函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
口该函数不能有语义上可观察的函数副作用,诸如“触发事件",使输出设备输出,或更改输出值以外物件的内容等。

(纯函数)当然上面的定义会过于的晦涩,所以我简单总结一下:
确定的输入,一定会产生确定的输出;
函数在执行过程中,不能产生副作用;

coderwhy副作用的理解
那么这里又有一个概念,叫做副作用,什么又是副作用呢?
口副作用(side effect)其实本身是医学的一个概念,比如我们经常说吃什么药本来是为了治病,可能会产生一些其他的副作用;
口在计算机科学中,也引用了副作用的概念,表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储;

纯函数在执行的过程中就是不能产生这样的副作用:
口副作用往往是产生bug的“温床”。

coderwhy纯函数的案例
我们来看一个对数组操作的两个函数:
slice:slice截取数组时不会对原数组进行任何操作,而是生成一个新的数组;
splice:splice截取数组,会返回一个新的数组,也会对原数组进行修改;
slice就是一个纯函数,不会修改传入的参数;

C++

00:36:43

splice sp懒s

coderwhy纯函数的优势
为什么纯函数在函数式编程中非常重要呢?
口因为你可以安心的编写和安心的使用;
口你在写的时候保证了函数的纯度,只是单纯实现自己的业务逻辑即可,不需要关心传入的内容是如何获得的或者依赖其他的外部变量是否已经发生了修改;
口你在用的时候,你确定你的输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出;

React中就要求我们无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改:
React 非常灵活,但它也有一个严格的规则:
所有React组件都必须像纯函数一样保护它们的props不被更改。

coderwhy JavaScript柯里化
柯里化也是属于函数式编程里面一个非常重要的概念。
我们先来看一下维基百科的解释:
口在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化;
口是把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术;
口柯里化声称“如果你固定某些参数,你将得到接受余下参数的一个函数";
维基百科的结束非常的抽象,我们这里做一个总结:
口只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数;
口这个过程就称之为柯里化;

var sum=x=>y=>z=>x+y+z

单一职责原则(SRP single responsibility principle)
面向对象->类->尽量完成第一的一件事

柯里化-单一职责的原则
柯里化-逻辑的复用

柯里化函数的实现hyCurrying

function foo(x,y,z){}
foo.length  得到参数的个数 xyz

博客

自动柯里化函数的实现

function add1(x,y,z){
    return x+y+z
}

//柯里化函数的实现
function hyCurrying(fn){
    function curried(...args){
        // 判断当前已经接收的参数的个数,可以参数本身需要接受的参数是否已经一致了
        // 1.当已经传入的参数 大于等于 需要的参数时,就执行函数
        if(args.length>=fn.length){
            // fn(...args)
            // fn.call(this,...args)
            return fn.apply(this,args)
        }else{
            //没有达到个数时,需要返回一个新的函数,继续来接收的参数
            function curried2(...args2){
                //接收到参数后,需要递归调用curried来检查函数的个数是否达到
                return curried.apply(this,args.concat(args2))
            }
            return curried2
        }
    }
    return curried
}

var curryAdd=hyCurrying(add1)
console.log(curryAdd(10,20,30))
// curryAdd(10,20)(30)
// curryAdd(10)(20)(30)

2:19:52

coderwhy理解组合函数
组合(Compose)函数是在JavaScript开发过程中一种对函数的使用技巧、模式:
口比如我们现在需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的;
口那么如果每次我们都需要进行两个函数的调用,操作上就会显得重复;
口那么是否可以将这两个函数组合起来,自动依次调用呢?
口这个过程就是对函数的组合,我们称之为组合函数(Compose Function);

// 组合函数的理解
function double(num){
    return num*2
}
function square(num){
    return num**2
}
var count=10
var result=square(double(count))
console.log(result)
//实现最简单的组合函数
function composeFn(m,n){
    return function(count){
        return n(m(count))
    }
}
var newFn=composeFn(double,square)
console.log(newFn(count))
//通用的组合函数的实现
function hyCompose(...fns){
    var length=fns.length
    for(var i=0;i<length;i++){
        if(typeof fns[i] !== 'function'){
            throw new TypeError('Expected arguments are functions')
        }
    }
    function compose(...args){
        var index=0
        var result=length?fns[index].apply(this,args):args
        while(++index<length){
            result=fns[index].call(this,result)
        }
        return result
    }
    return compose
}
function double(num){
    return num*2
}
function square(num){
    return num**2
}
var count=10
var newFn=hyCompose(double,square)
console.log(newFn(count))

08 with-eval-严格模式-面向对象一

with wi

with语句:可以形成自己的作用域

var info={name:'koo'}
with(info){//在info里面找 找不到 向上找
    console.log(name)
}
"use strict";//开启严格模式  不建议用with 报错

with语句 扩展一个语句的作用域链。

不建议使用with语句,因为它可能是混淆错误和兼容性问题的根源。

coderwhy eval函数
eval是一个特殊的函数,它可以将传入的字符串当做JavaScript代码来运行。

不建议在开发中使用eval:
eval代码的可读性非常的差(代码的可读性是高质量代码的重要原则);
eval是一个字符串,那么有可能在执行的过程中被刻意篡改,那么可能会造成被攻击的风险;
eval的执行必须经过JS解释器,不能被JS引擎优化;

在ECMAScript5标准中,JavaScript提出了严格模式的概念(Strict Mode):
口严格模式很好理解,是一种具有限制性的JavaScript模式,从而使代码隐式的脱离了"懒散(sloppy)模式";
口支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行;
严格模式对正常的JavaScript语义进行了一些限制:
口严格模式通过 抛出错误 来消除一些原有的 静默(silent)错误;
口严格模式让JS引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理);
口严格模式禁用了在ECMAScript未来版本中可能会定义的一些语法;

coderwhy开启严格模式
那么如何开启严格模式呢?严格模式支持粒度话的迁移:
口可以支持在js文件中开启严格模式;
口也支持对某一个函数开启严格模式;

codenwhy严格模式限制
这里我们来说几个严格模式下的严格语法限制:
JavaScript被设计为新手开发者更容易上手,所以有时候本来错误语法,被认为也是可以正常被解析的;
但是这种方式可能给带来留下来安全隐患;
在严格模式下,这种失误就会被当做错误,以便可以快速的发现和修正;
1.无法意外的创建全局变量
2.严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常
3.严格模式下试图删除不可删除的属性
4.严格模式不允许函数参数有相同的名称
5.不允许0的八进制语法
6.在严格模式下,不允许使用with
7.在严格模式下,eval不再为上层引用变量
8.严格模式下,this绑定不会默认转成对象

var obj={}
Object.defineProperty(obj,"name":{
 	configurable:false,//不可删除
    writable:false,//不可修改
    value:"why"
 })
var num=0o123//八进制
var num=0x123//十六进制
var num=0b100//二进制

严格模式下与非严格模式下  都是window
setTimeout(function(){
    console.log(this)//window
},100)

在严格模式下,自执行函数(默认绑定)会指向undefined

setTimeout 黑盒子 猜不到里面是怎么做的

01:25:48

深入JS面向对象

面向对象是现实的抽象方式

对象是JavaScript中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物:
口比如我们可以描述一辆车:Car,具有颜色(color)、速度(speed)、品牌(brand)、价格(price),行驶(travel)等等;
口比如我们可以描述一个人:Person,具有姓名(name)、年龄(age)、身高(height),吃东西(eat)、跑步(run)等等;

用对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构:
口所以有一些编程语言就是纯面向对象的编程语言,比Java;
口你在实现任何现实抽象时都需要先创建一个类,根据类再去创建对象;

coderwhy JavaScript的面向对象
JavaScript其实支持多种编程范式的,包括函数式编程和面向对象编程:
口JavaScript中的对象被设计成一组属性的无序集合,像是一个哈希表,有key和value组成;
口key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型;
口如果值是一个函数,那么我们可以称之为是对象的方法;

如何创建一个对象呢?
早期使用创建对象的方式最多的是使用Object类,并且使用new关键字来创建一个对象:
口这是因为早期很多JavaScript开发者是从Java过来的,它们也更习惯于Java中通过new的方式创建一个对象;
后来很多开发者为了方便起见,都是直接通过字面量的形式来创建对象:
口这种形式看起来更加的简洁,并且对象和属性之间的内聚性也更强,所以这种方式后来就流行了起来;

coderwhy对属性操作的控制
在前面我们的属性都是直接定义在对象内部,或者直接添加到对象内部的:
口但是这样来做的时候我们就不能对这个属性进行一些限制:比如这个属性是否是可以通过delete删除的?这个属性是否在for-in遍历的时候被遍历出来呢?

如果我们想要对一个属性进行比较精准的操作控制,那么我们就可以使用属性描述符。
口通过属性描述符可以精准的添加或修改对象的属性;
口属性描述符需要使用Object.defineProperty 来对属性进行添加或者修改;

Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

可接收三个参数:
口obj要定义属性的对象;
口prop要定义或修改的属性的名称或Symbol;
口descriptor要定义或修改的属性描述符;

返回值:
口被传递给函数的对象。

Object.defineProperty(obj,prop,descriptor)

直接写一个value 是不可枚举的 obj.name才可以看到

coderwhy属性描述符分类
属性描述符的类型有两种:
口数据属性(Data Properties)描述符(Descriptor);
口存取属性(Accessor访问器Properties)描述符(Descriptor);

coderwhy数据属性描述符
数据数据描述符有如下四个特性:
[[Configurable]]:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符;
当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true;
当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false;

该属性不可删除/也不可以重新定义属性描述符

[[Enumerable]]:表示属性是否可以通过for-in或者Object.keys0返回该属性;
当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true;
当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false;

[[Writable]]:表示是否可以修改属性的值;
当我们直接在一个对象上定义某个属性时,这个属性的[[Writable]]为true;
当我们通过属性描述符定义一个属性时,这个属性的[[Writable]]默认为false;

[[value]]:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改;
口默认情况下这个值是undefined;

coderwhy存取属性描述符
数据数据描述符有如下四个特性:
[[Configurable]]:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符;
和数据属性描述符是一致的;
当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true;
当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false;
[[Enumerable]]:表示属性是否可以通过for-in或者Object.keys()返回该属性;
和数据属性描述符是一致的;
当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true;
当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false;
[[get]]:获取属性时会执行的函数。默认为undefined
[[set]]:设置属性时会执行的函数。默认为undefined

enumerable+configurale+get+set=存取属性描述符

enumerable+configurale+value+writable=数据属性描述符

存取属性描述符
1.隐藏某一个私有属性被希望直接被外界使用和賦值
2.如果我们希望截获某一个属性它访问和设置值的过程时,也会使用存储属性描述符

09 对象补充-原型和函数原型-创建对象

var obj={}
Object.defineProperties(obj,{
    name:{
        configurable:true,
        enumerable:true,
        writable:true,
        value:'why'
    },
    age:{}
})
var obj={
    //私有属性(js里面是没有严格意义的私有属性)
    _age:18,
    set age(value){
        this._age=value
    },
    get age(){
        return this._age
    }
}

获取某一个特性属性的属性描述符
Object.getOwnPropertyDescriptor()

获取对象的所有属性插述符
Object.getOwnPropertyDescriptors()

禁止对象继续添加新的属性
Object.preventExtensions()

禁止对象配置/删除里面的属性
Object.seal(obj)
for(var key in obj){
    Object.defineProperty(obj,key,{
        configurable:false,
        enumerable:true,
        writable:true,
        value:obj[key]
    })
}

3.让属性不可以修改(writable:false)
Object.freeze(obj)

00:36:39

目前我们已经学习了两种方式:
Inew Object方式;口字面量创建的方式;

coderwhy认识构造函数
工厂方法创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是Object类型
口但是从某些角度来说,这些对象应该有一个他们共同的类型;
口下面我们来看一下另外一种模式:构造函数的方式;
我们先理解什么是构造函数?
口构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;
口在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;
口但是JavaScript中的构造函数有点不太一样;

constructor /kənˈstrʌktər/ kən说特

JavaScript中的构造函数是怎么样的?
口构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
口那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数;

换一种方式来调用foo函数:通过new关键字去调用一个函数,那么这个函数就是一个构造函数了

new foo new foo() 小括号可以不写

coderwhy new操作符调用的作用
如果一个函数被使用new操作符调用了,那么它会执行如下操作:
1.在内存中创建一个新的对象(空对象);
2.这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;(后面详细讲);
3.构造函数内部的this,会指向创建出来的新对象;
4·执行函数的内部代码(函数体代码);
5·如果构造函数没有返回非空对象,则返回创建出来的新对象;

约定成俗 首字母大写 大驼峰

早期的ECMA是没有规范如何去查看[[prototype]]

给对象中提供了一个属性,可以让我们查看一下这个原型对象(浏览器提供) proto

ES5之后提供的Object.getPrototypeOf 查看原型
console.log(Object.getPrototypeOf(obj))

我们每个对象中都有一个[[prototype]],这个属性可以称之为对象的原型(隐式原型)

隐式原型 不会用 改它

coderwhy认识对象的原型
JavaScript当中每个对象都有一个特殊的内置属性[[prototype]],这个特殊的对象可以指向另外一个对象。
那么这个对象有什么用呢?
口当我们通过引用对象的属性key来获取一个value时,它会触发[[Get]]的操作;
口这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;;
口如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性;

那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?
口答案是有的,只要是对象都会有这样的一个内置属性;
获取的方式有两种:
口方式一:通过对象的_proto_属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题);
口方式二:通过Object.getPrototypeOf 方法可以获取到;

对象原型__proto__

函数原型prototype 函数也是一个对象 也有__proto__

函数它因为是一个函数,所以它还会多出来一个显示原型属性:prototype

function foo(){
    var moni={}
    this={}
    this.__proto__=foo.prototype
    return this
}
var fn=new foo()
fn.__proto__===foo.prototype

foo.prototype.constructor = 构造函数本身foo

10 面向对象的原型链和继承实现

函数原型 不用箭头函数

coderwhy JavaScript中的类和对象
当我们编写如下代码的时候,我们会如何来称呼这个Person呢?
口在JS中Person应该被称之为是一个构造函数;
口从很多面向对象语言过来的开发者,也习惯称之为类,因为类可以帮助我们创建出来对象p1、p2;
口如果从面向对象的编程范式角度来看,Person确实是可以称之为类的;

coderwhy面向对象的特性-继承
面向对象有三大特性:封装、继承、多态
口封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程;
口继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);
口多态:不同的对象在执行时表现出不同的形态;

那么这里我们核心讲继承。
那么继承是做什么呢?
口继承可以帮助我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可。
那么JavaScript当中如何实现继承呢?
口不着急,我们先来看一下JavaScript原型链的机制;
口再利用原型链的机制实现一下继承;

new 一个函数
1.在内存中创建一个对象
var moni ={}
2.this的赋值
this = moni
3.将Person函数的显示原型prototype赋值给前面创建出来的对象的隐式原型
moni._proto_= Person.prototype
4.执行代码体
5.返回这个对象

那么我们可能会问题:[Object:null prototype]原型有什么特殊吗?
口特殊一:该对象有原型属性,但是它的原型属性已经指向的是null,也就是已经是顶层原型了;
口特殊二:该对象上有很多默认的属性和方法;

//继承-原型链的继承方案
function Person(){
    this.name='koo'
    this.friends=[]
}
Person.prototype.eating=function(){
    console.log(this.name+'在吃东西')
}
function Student(){
    this.sno=88
}

var p1=new Person()
Student.prototype=p1

Student.prototype.studing=function(){
    console.log('在学习')
}
// var s1=new Student()
// console.log(s1.name)
// s1.eating()

// 原型链实现继承的弊端:
// 1.打印stu对象,继承的属性是看不到的
// console.log(s1)

//2.创建出来两个stu的对象 引用数据共享 不独立
var stu1=new Student()
var stu2=new Student()

//直接修改对象上的属性,是给本对象添加了一个新属性
stu1.name='kobe'
console.log(stu2.name)

//获取引用,修改引用中的值,会相互影响
stu1.friends.push('john')
console.log(stu1.friends) // 俩个相同
console.log(stu2.friends) // 俩个相同 应该每个学生的朋友不同才对

//3.第三个弊端:在前面实现类的过程中都没有传递参数
var stu3=new Student('lilei',222)

coderwhy借用构造函数继承
为了解决原型链继承中存在的问题,开发人员提供了一种新的技术:constructor stealing(有很多名称:借用构造函数或者称之为经典继承或者称之为伪造对象):
口steal是偷窃、剽窃的意思,但是这里可以翻译成借用;

借用继承的做法非常简单:在子类型构造函数的内部调用父类型构造函数.
口因为函数可以在任意的时刻被调用;
口因此通过apply(和call()方法也可以在新创建的对象上执行构造函数;

coderwhy组合借用继承的问题
组合继承是JavaScript最常用的继承模式之一:
口如果你理解到这里,点到为止,那么组合来实现继承只能说问题不大;
口但是它依然不是很完美,但是基本已经没有问题了;(不成问题的问题,基本一词基本可用,但基本不用)
组合继承存在什么问题呢?
口组合继承最大的问题就是无论在什么情况下,都会调用两次父类构造函数。
一次在创建子类原型的时候;
另一次在子类构造函数内部(也就是每次创建子类实例的时候);
口另外,如果你仔细按照我的流程走了上面的每一个步骤,你会发现:所有的子类实例事实上会拥有两份父类的属性
一份在当前的实例自己里面(也就是person本身的),另一份在子类对应的原型对象中(也就是person.proto_里面);
当然,这两份属性我们无需担心访问出现问题,因为默认一定是访问实例本身这一部分的;

//继承-借用构造函数方案
function Person(name,friends){
    this.name=name
    this.friends=friends
}
Person.prototype.eating=function(){
    console.log(this.name+'在吃东西')
}
function Student(name,friends,sno){
    Person.call(this,name,friends)
    this.sno=sno
}

var p1=new Person()
Student.prototype=p1

Student.prototype.studing=function(){
    console.log('在学习')
}
// var s1=new Student()
// console.log(s1.name)
// s1.eating()

// 原型链实现继承的弊端:
// 1.第一个弊端 打印stu对象,继承的属性是看不到的 也解决3个弊端
// console.log(s1)

//2.第二个弊端,创建出来两个stu的对象 引用数据共享 不独立 也解决3个弊端
// var stu1=new Student('koo',['ohn'],88)
// var stu2=new Student('kok',['too'],38)

//直接修改对象上的属性,是给本对象添加了一个新属性
// stu1.name='kobe'
// console.log(stu2.name)

//获取引用,修改引用中的值,会相互影响
// stu1.friends.push('john')
// console.log(stu1.friends) // 俩个相同
// console.log(stu2.friends) // 俩个相同 应该每个学生的朋友不同才对

//3.第三个弊端:在前面实现类的过程中都没有传递参数 也解决3个弊端
// var stu3=new Student('lilei',['lisi'],222)

// 强调:借用构造函数也是有弊端
// 1.第一个弊端:Person函数至少被调用了两次
// 2.第二个弊端:-stu的原型对象上会多出一些属性,但是这些属性是没有存在的必要

11 继承的实现-对象-函数-原型的关系

coderwhy原型式继承函数
原型式继承的渊源
口这种模式要从道格拉斯·克罗克福德(Douglas Crockford,著名的前端大师,JSON的创立者)在2006年写的一篇文章说起:Prototypal Inheritance in JavaScript(在JS中使用原型式继承)
口在这篇文章中,它介绍了一种继承方法,而且这种继承方法不是通过构造函数来实现的.
口为了理解这种方式,我们先再次回顾一下JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法.

//继承-原型式继承-对象
var obj={
    name:'koo',
    age:18
}
//原型式继承函数
//1
function createObject1(o){
    var newObj={}
    Object.setPrototypeOf(newObj,o)
    return newObj
}

//还没有Object.setPrototypeOf时 写的
//2
function createObject2(o){
    function Fn(){}
    Fn.prototype=o
    var newObj=new Fn()
    return newObj
}

// var info=createObject1(obj)
//3
var info=Object.create(obj)
console.log(info)//{}
console.log(info.__proto__)//{ name: 'koo', age: 18 }

coderwhy寄生式继承函数
寄生式(Parasitic)继承
口寄生式(Parasitic)继承是与原型式继承紧密相关的一种思想,并且同样由道格拉斯·克罗克福德(Douglas Crockford)提出和推广的;
口寄生式继承的思路是结合原型类继承和工厂模式的一种方式;
口即创建一个封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再将这个对象返回;

//继承-寄生式继承-对象
var personObj={
    running:function(){
        console.log('running')
    }
}
function createStudent(name){//工厂模式
    var stu=Object.create(personObj)//原型式继承
    stu.name=name
    stu.studying=function(){//弊端 每个学生都会创建一个studying方法
        console.log('studying')
    }
    return stu
}
var stuObj=createStudent('koo')
var stuObj1=createStudent('john')

coderwhy寄生组合式继承
现在我们来回顾一下之前提出的比较理想的組合继承
口组合继承是比较理想的继承方式,但是存在两个问题:
口问题一:构造函数会被调用两次:一次在创建子类型原型对象的时候,一次在创建子类型实例的时候.
口问题二:父类型中的属性会有两份:一份在原型对象中,一份在子类型实例中.
事实上,我们现在可以利用寄生式继承将这两个问题给解决掉.
口你需要先明确一点:当我们在子类型的构造函数中调用父类型.call(this,参数)这个函数的时候,就会将父类型中的属性和方法复制一份到了子类型中.所以父类型本身里面的内容,我们不再需要.
口这个时候,我们还需要获取到一份父类型的原型对象中的属性和方法.
能不能直接让子类型的原型对象 = 父类型的原型对象呢?
口不要这么做因为这么做意味着以后修改了子类型原型对象的某个引用类型的时候,父类型原生对象的引用类型也会被修改.
口我们使用前面的寄生式思想就可以了.

//继承-寄生组合式继承
function createObj(o){//替代Object.create
    function fn(){}
    fn.prototype=o
    var newObj=new fn()
    return newObj
}
function inheritPrototype(subType,superType){ //多个子类用
    // subType.prototype=Object.create(superType.prototype)
    subType.prototype=createObj(superType.prototype)
    // subType.prototype.constructor=subType
    Object.defineProperty(subType.prototype,'constructor',{
        enumerable:false,
        writable:true,
        configurable:true,
        value:subType
    })
}

function Person(name,age){
    this.name=name
    this.age=age
}
Person.prototype.eating=function(){
    console.log('eating')
}
function Student(name,age,sno){
    Person.call(this,name,age)
    this.sno=sno
}

inheritPrototype(Student,Person)

Student.prototype.studying=function(){
    console.log('studying')
}

var stu=new Student('koo',18,88)
console.log(stu)
stu.eating()
stu.studying()
JS原型内容补充
判断方法的补充
//判断是否在当前属性上 不包括原型
obj.hasOwnProperty('address')

//in 操作符:不管在当前对象还是原型中返回的都是true
console.log('name' in obj)

console.log(stu instanceof Student)//true
console.log(stu instanceof Person)//true
console.log(stu instanceof Object)//true

console.log(Person.prototype.isPrototypeOf(p))

coderwhy对象的方法补充
hasOwnProperty对象是否有某一个属于自己的属性(不是在原型上的属性)
in/for in 操作符
口判断某个属性是否在某个对象或者对象的原型上
instanceof 构造函数
口用于检测构造函数的pototype,是否出现在某个实例对象的原型链上

所有类都是继承Object

isPrototypeOf 对象
口用于检测某个对象,是否出现在某个实例对象的原型链上

对象里面是有一个__proto__对象:隐式原型对象
Foo是一个函数,那么它会有一个显示原型对象:Foo.prototype
Foo,prototype来自哪里?
答案:创建了一个函数,Foo.prototype = f constructor:Foo}
Foo是一个对象,那么它会有一个隐式原型对象:Foo.__proto
Foo.__proto___来自哪里?
答案:new Function()-Foo.proto= Function.prototype
Function prototype = f-constructor:Function}

Function.prototype===Function.__proto__
其他函数不同

12 ES6类的使用和转ES5源码阅读

coderwhy认识class定义类
我们会发现,按照前面的构造函数形式创建类,不仅仅和编写普通的函数过于相似,而且代码并不容易理解。
口在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类;
口但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已;
口所以学好了前面的构造函数、原型链更有利于我们理解类的概念和继承关系;
那么,如何使用class来定义一个类呢?
口可以使用两种方式来声明类:类声明和类表达式;

class Person{
    //一个类只能有一个构造函数
    //1.在内存中创建一个对象 moni = f}
	//2.将类的原型prototype赋值给创建出来的对象 moni.__proto__= Person.prototype
	//3.将对象赋值给函数的this:new绑定 this = moni
	//4.执行函数体中的代码
	//5.自动返回创建出来的对象
    constructor(){
        
    }
}
var Student=class{}
console.log(typeof Person)//function

coderwhy类的构造函数
如果我们希望在创建对象的时候给类传递一些参数,这个时候应该如何做呢?
口每个类都可以有一个自己的构造函数(方法),这个方法的名称是固定的constructor;
口当我们通过new操作符,操作一个类的时候会调用这个类的构造函数constructor;
口每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常;

当我们通过new关键字操作类的时候,会调用这个constructor函数,并且执行如下操作:
01.在内存中创建一个新的对象(空对象);
02.这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性;
03.构造函数内部的this,会指向创建出来的新对象;
04.执行构造函数的内部代码(函数体代码);
05.如果构造函数没有返回非空对象,则返回创建出来的新对象;

class Person{
    constructor(){
        this._address='beijing'
    }
    //类的访问器方法
    get address(){
        return this._address
    }
    set address(newAddress){
        this._address=newAddress
    }
    //类的静态方法(类方法)
    //Person.randomPerson()
    static randomPerson(){}
}
var p=new Person()
console.log(p.address)
p.address='guangzhou'

普通的实例方法
创建出来的对象进行访问

coderwhysuper关键字
我们会发现在上面的代码中我使用了一个super关键字,这个super关键字有不同的使用方式:
口注意:在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数!
口super的使用位置有三个:子类的构造函数、实例方法、静态方法;

JS引擎在解析子类的时候就有要求,如果我们有实现继承
那么子类的构造方法中,在使用this之前,必须调用super

类对父类的方法的重写

调用 父对象/父类 上的方法

复用父类中的处理逻辑

class Person{
    constructor(name,age){
        this.name=name
        this.age=age
    }
    personMethod(){
        console.log('处理逻辑1')
    }
    static staticMethod(){
        console.log('static method')
    }
}
class Student extends Person{
    //那么子类的构造方法中,在使用this之前,必须调用super
    constructor(name,age,sno){
        super(name,age)
        this.sno=sno
    }
    //调用 父对象/父类 上的方法 重写
    personMethod(){
        super.personMethod() //复用父类中的处理逻辑
        console.log('处理逻辑2')
    }
    static staticMethod(){
        console.log('student static method')
    }
}
var stu=new Student('koo',28,88)
stu.personMethod()
Student.staticMethod()


静态方法直接放在类上面的 不是原型
vue cli webpack环境(babel)
babel是什么?讲新JS、TS代码-> 较低版本浏览器可以识别的代码

http://babeljs.io

最终要运行的浏览器可以称之为目标浏览器

有__PURE注释 是纯函数 如果不用 webpack可以thunk-shaking优化 删除

目的静态方法的继承
Student.proto= Person

IE10不支持class

3 5年回头看源码

bookmarks 快捷键 vscode插件

阅读源码
阅读源码大家遇到的最大的问题:
1.—定不要浮躁
2.看到后面忘记前面的东西
	口Bookmarks的打标签的工具:command(ctrl)+ alt + k
	口读一个函数的时候忘记传进来是什么?截图 写下
3.读完一个函数还是不知道它要干嘛
4.debugge

13ES6-语法解析-let-const等

优秀不是一种行为,而是一种习惯

继承内置类

coderwhy类的混入mixin react中redux-connect

JavaScript的类只支持单继承:也就是只能有一个父类
口那么在开发中我们我们需要在一个类中添加更多相似的功能时,应该如何来做呢?
口这个时候我们可以使用混入(mixin);

class Person{

}

function mixinRunner(baseClass){
    class newClass extends baseClass{
        run(){
            console.log('running')
        }
    }
    return newClass
}
function mixinEater(BaseClass){
    return class extends BaseClass{
        //匿名类
        eating(){
            console.log('eating')
        }
    }
}
//在JS中类只能有一个父类:单继承
class Student extends Person{

}

var newStudent=mixinEater(mixinRunner(Student))
var ns=new newStudent()
ns.run()
ns.eating()

coderwhy JavaScript中的多态
面向对象的三大特性:封装、继承、多态。
口前面两个我们都已经详细解析过了,接下来我们讨论一下JavaScript的多态。
JavaScript有多态吗?
口维基百科对多态的定义:多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口,或使用一个单一的符号来表示多个不同的类型。
口非常的抽象,个人的总结:不同的数据类型进行同一个操作,表现出不同的行为,就是多态的体现。
那么从上面的定义来看,JavaScript是一定存在多态的。

多态:当对不同的数据类型执行同一个操作时,如果表现出来的行为(形态)不一样,那么就是多态的体现。

传统的面向对象多态是有三个前提:
1> 必须有继承(是多态的前提)
2> 必须有重写(子类重写父类的方法)
3>-必须有父类引用指向子类对象

var obj={
    //俩个相同 语法糖 简写
    foo:function(){},
    foo(){}
    //不是箭头函数
    foo:()=>{},
    //(计算属性名)
    [name+123]:'hhahahh'
}

coderwhy字面量的增强
ES6中对对象字面量进行了增强,称之为Enhanced object literals(增强对象字面量)。
字面量的增强主要包括下面几部分:
属性的简写:Property Shorthand
方法的简写:Method Shorthand
计算属性名:Computed Property Names

coderwhy解构Destructuring
ES6中新增了一个从数组或对象中方便获取数据的方法,称之为解构Destructuring

我们可以划分为:数组的解构和对象的解构。

var arr=[1,2,3]
var [a=5]=arr

var obj={
    name:'koo'
}
var {name:newName='john'}=obj

coderwhy let/const基本使用
在ES5中我们声明变量都是使用的var关键字,从ES6开始新增了两个关键字可以声明变量:let、const
口let、const在其他编程语言中都是有的,所以也并不是新鲜的关键字;
口但是let、const确确实实给JavaScript带来一些不一样的东西;

const本质上是传递的值不可以修改
但是如果传递的是一个引用类型(内存地址),可以通过引用找到对应的对象,去修改对象内部的属性,这个是可以的

let关键字:
口从直观的角度来说,let和var是没有太大的区别的,都是用于声明一个变量
const关键字:
口const关键字是constant的单词的缩写,表示常量、衡量的意思;
口它表示保存的数据一旦被赋值,就不能被修改;
口但是如果赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容;
注意:另外let、const不允许重复声明变量;

coderwhy let/const作用域提升
let、const和var的另一个重要区别是作用域提升:
口我们知道var声明的变量是会进行作用域提升的;
口但是如果我们使用let声明的变量,在声明之前访问会报错;

那么是不是意味着foo变量只有在代码执行阶段才会创建的呢?
口事实上并不是这样的,我们可以看一下ECMA262对let和const的描述;
口这些变量会被创建在包含他们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值;

coderwhy let/const有没有作用域提升呢?
从上面我们可以看出,在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的。
口那么变量已经有了,但是不能被访问,是不是一种作用域的提升呢?
事实上维基百科并没有对作用域提升有严格的概念解释,那么我们自己从字面量上理解;
口作用域提升:在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升;
口在这里,它虽然被创建出来了,但是不能被访问,我认为不能称之为作用域提升;

所以我的观点是let、const没有进行作用域提升,但是会在执行上下文创建阶段被创建出来。

coderwhy变量被保存到VariableMap中
也就是说我们声明的变量和环境记录是被添加到变量环境中的:
口但是标准有没有规定这个对象是window对象或者其他对象呢?
口其实并没有,那么JS引擎在解析的时候,其实会有自己的实现;
口比如v8中其实是通过VariableMap的一个hashmap来实现它们的存储的。
口那么window对象呢?而window对象是早期的GO对象,在最新的实现中其实是浏览器添加的全局对象,并且一直保持了window和var之间值的相等性;

在ES5中只有两个东西会形成作用域
1.全局作用域
2.函数作用域

with

ES6的代码块级作用域
对let/const/function/class声明的类型是有效

不同的浏览器有不同实现的(大部分浏览器为了兼容以前的代码,让function是没有块级作用域)

coderwhy let/const的块级作用域
在ES6中新增了块级作用域,并且通过let、const、function、class声明的标识符是具备块级作用域的限制的:

但是我们会发现函数拥有块级作用域,但是外面依然是可以访问的:
口这是因为引擎会对函数的声明进行特殊的处理,允许像var那样进行提升;

15ES6知识点详细解析

coderwhy var、let、const的选择
那么在开发中,我们到底应该选择使用哪一种方式来定义我们的变量呢?
对于var的使用:
口我们需要明白一个事实,var所表现出来的特殊性:比如作用域提升、window全局对象、没有块级作用域等都是一些历史遗留问题;
口其实是JavaScript在设计之初的一种语言缺陷;
口当然目前市场上也在利用这种缺陷出一系列的面试题,来考察大家对JavaScript语言本身以及底层的理解;
口但是在实际工作中,我们可以使用最新的规范来编写,也就是不再使用var来定义变量了;
对于let、const:
口对于let和const来说,是目前开发中推荐使用的;
口我们会优先推荐使用const,这样可以保证数据的安全性不会被随意的篡改;
口只有当我们明确知道一个变量后续会需要被重新赋值时,这个时候再使用let;
口这种在很多其他语言里面也都是一种约定俗成的规范,尽量我们也遵守这种规范;

for...of:ES6新增的遍历数组(对象)

coderwhy暂时性死区
在ES6中,我们还有一个概念称之为暂时性死区:
口它表达的意思是在一个代码中,使用let,const声明的变量,在声明之前,变量都是不可以访问的;
口我们将这种现象称之为 temporal dead zone(暂时性死区,TDZ);

构建工具的基础上创建项目\开发项目 webpack/vite/rollup
babel
ES6-->·ES5

//另外调用函数的方式:标签模块字符串
第一个参数依然是模块字符串中整个字符串,只是被切成多块,放到了一个数组中
第二个参数是模块字符串中,第一个 ${}
function(m,n){
    console.log(m,n)
}
const name='why'
const age=18
//['hello','Wo','rld'] why
foo`hello${name}Wo${age}rld`

//左边解构  右默认值
function info({name}={name:'koo'}){}
function info({name='koo'}={}){}

有默认值的形参最好放到最后

另外参数的默认值我们通常会将其放到最后(在很多语言中,如果不放到最后其实会报错的):
口但是JavaScript允许不将其放到最后,但是意味着还是会按照顺序来匹配;
另外默认值会改变函数的length的个数,默认值以及后面的参数都不计算在length之内了。

那么剩余参数和arguments有什么区别呢?
口剩余参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参;
口arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作;
口arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构,而rest参数是ES6中提供并且希望以此来替代arguments的;

rest paramaters剩余参数必须放到最后

剩余参数必须放到最后一个位置,否则会报错。

coderwhy函数箭头函数的补充
在前面我们已经学习了箭头函数的用法,这里进行一些补充:
口箭头函数是没有显式原型的,所以不能作为构造函数,使用new来创建对象;foo.prototype

coderwhy展开语法
展开语法(Spread syntax):
口可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开;
口还可以在构造字面量对象时,将对象表达式按key-value的方式展开;

展开语法的场景:
口在函数调用时使用;
口在数组构造时使用;
口在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性;

...展开语法 剩余参数

展开语法是浅拷贝

const num1=100 十进制
b->binary
const num2=0b100 二进制
o->octonary
const num3=0o100 八进制
x->hexadecimal
const num4=0x100 十六进制

大的数值的连接符(ES2021-ES12)
const num=10_100_100_100 //10100100100

coderwhy Symbol的基本使用
Symbol是什么呢?Symbol是ES6中新增的一个基本数据类型,翻译为符号。
那么为什么需要Symbol呢?
口在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
比如我们前面在讲apply,call,bind实现时,我们有给其中添加一个fn属性,那么如果它内部原来已经有了fn属性了呢?
比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉;

ES6之前,对象的属性名(key) 都是string

Symbol就是为了解决上面的问题,用来生成一个独一无二的值。
Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;
也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;

const s1=Symbol()
const s1=Symbol('aaa')
console.log(s1.description) //aaa

const obj={
	[s1]:'koo'
}
obj[s1]='aaa'
Object.defineProperty(obj,s1,{
	enumrable:true,
	connfigurable:true,
	wirtable:true,
	value:'bbb'
})
console.log(obj[s1])
不能 obj.s1
使用Symbol作为key的属性名,在遍历/Object.keys等中是获取不到这些Symbol值

Object.key(obj)//不能获取
Object.getOwnPropertyNames(obj)//不能获取

const sKeys=Object.getOwnPropertySymbols(obj)
for(const sKey of sKeys){
	console.log(obj[sKey])
}

const s1=Symbol.for('aaa')
const s2=Symbol.for('aaa')
console.log(s1===s2) //true

const key=Symbol.keyFor(s1)

Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;
我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性;l

16 await、async等

coderwhySet的基本使用
在ES6之前,我们存储数据的结构主要有两种:数组、对象。
口在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap.

数据结构:存放数据的方式

Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。
口创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式):

const set=new Set()
set.add(19)
set.add(88)
//set不能有重复的值
set.size
set.delete(88)
set.has(88)
set.clear()

set.forEach(item=>{
    console.log(item)
})
for(const item of set){
    console.log(item)
}

我们可以发现Set中存放的元素是不会重复的,那么Set有一个非常常用的功能就是给数组去重。

coderwhySet的常见方法
Set常见的属性:
size:返回Set中元素的个数;

Set常用的方法:
add(value):添加某个元素,返回Set对象本身;
delete(value):从set中删除和这个值相等的元素,返回boolean类型;
clear():清空set中所有的元素,没有返回值;
has(value):判断set中是否存在某个元素,返回boolean类型;
forEach(callback,[,thisArg]):通过forEach遍历set;
另外Set是支持for of的遍历的。

coderwhy WeakSet使用
和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。

那么和Set有什么区别呢?
口区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
口区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;

强引用 strong reference

弱引用 weak reference

00:41:26

WeakSet常见的方法:
add(value):添加某个元素,返回WeakSet对象本身;
delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
has(value):判断WeakSet中是否存在某个元素,返回boolean类型

coderwhy WeakSet的应用
注意:WeakSet不能遍历
口因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
口所以存储到WeakSet中的对象是没办法获取的;

throw 吃路

coderwhyMap的基本使用
另外一个新增的数据结构是Map,用于存储映射关系。
但是我们可能会想,在之前我们可以使用对象来存储映射关系,他们有什么区别呢?
口事实上我们对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key);
口某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key;

const map=new Map()
map.set({},'aaa')
const map=new Map([[key,value],[key,value]])
map.size
map.get(key)
map.has(key)
map.delete(key)
map.clear()
map.forEach()
for(const item of map){}

coderwhy WeakMap的使用
和Map类型相似的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。
那么和Map有什么区别呢?
区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
区别二:WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;

WeakSet只能存放对象
WeakMap:key只能对象

weak wi

4.应用场景(vue3响应式原理)WeakMap

WeakMap常见的方法有四个:
set(key,value):在Map中添加key、value,并且返回整个Map对象;
get(key):根据key获取Map中的value;
has(key):判断是否包括某一个key,返回Boolean类型;
delete(key):根据key删除一个键值对,返回Boolean类型;

了解真相,你才能获得真知的自由。

ES6 or ES2015非常大的版本的更迭

ES12 ES2021 WeakRef

该视频2021 2023学 2年前

coderwhy ES7-Array Includes
在ES7之前,如果我们想判断一个数组中是否包含某个元素,需要通过 indexOf 获取结果,并且判断是否为-1。

ES7 ES2016

const names=['abc','bac']
name.includes('abc')
name.includes('abc',2) //第二个参数 从第几个开始
indexOf 无法判断NaN includes可以

在ES7中,我们可以通过includes来判断一个数组中是否包含一个指定的元素,根据情况,如果包含则返回true,否则返回false。

coderwhy ES7-指数运算符
在ES7之前,计算数字的平方需要通过 Math.pow 方法来完成。
在ES7中,增加了**运算符,可以对数字来计算平方。

17CommonJS、AMD、CMD

coderwhy ES8 Object values
之前我们可以通过Object.keys获取一个对象所有的key,在ES8中提供了Object.values来获取所有的value值:

Object.keys(obj)
Object.values(obj) 所有value组成数组

coderwhy ES8 Object entries
通过Object.entries 可以获取到一个数组,数组中会存放可枚举属性的键值对数组。

const obj={
    name:'why',
    age:18
}
console.log(Object.entries(obj)) 
//[['name','why'],['age',18]]

coderwhy ES8-String Padding
某些字符串我们需要对其进行前后的填充,来实现某种格式化效果,ES8中增加了padStart和padEnd方法,分别是对字符串的首尾进行填充的。

const message='hello'
const newMessage=message.padStart(15,'*').padEnd(19,'-')

coderwhy ES8-Trailing Commas
在ES8中,我们允许在函数定义和调用时多加一个逗号:

coderwhy ES8-Object Descriptors
Object.getOwnPropertyDescriptors,这个在之前已经讲过了,这里不再重复。
Async Function:async,await。

coderwhy
ES9新增知识点
Async iterators:后续迭代器讲解
Object spread operators:前面讲过了

Promise finally:后续讲Promise讲解

coderwhy ES10-flat flatMap
flat()方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

三维数组 多维

var arr=[1,[2,3]]
const newArr=arr.flat(1)

const arr=[19,2,4]
const newArr=arr.flatMap(item=>{},this)

flatMap()方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。
口注意一:flatMap是先进行map操作,再做flat的操作;
口注意二:flatMap中的flat相当于深度为1;

Object.fromEntries

const obj={
    name:'koo'
}
const entries=Object.entries(obj)

const newObj={}
for(const entry of entries){\
	newObj[entry[0]]=entry[1]
}
                            
ES10中新增了Object.fromEntries方法
 Entries格式转object
 const newObj=Object.fromEntries(entries)
//Object.fromEntries的应用场景
 const queryString='name=why&age=18'
 const queryParams=new URLSearchParams(queryString)
 1.
  for(const param of queryParams){}
 2.
  const paramObj=Object.fromEntries(queryParams)

for(const item in arr){} 拿到的索引

ES10 - trimStart trimEnd

coderwhy
ES10 其他知识点
Symbol description:已经讲过了
Optional catch binding:后面讲解try cach讲解

coderwhy ES11-BigInt 2020
在早期的JavaScript中,我们不能正确的表示过大的数字:
口大于MAX_SAFE_INTEGER的数值,表示的可能是不正确的。

Number.MAX_SAFE_INTEGER

BigInt 
const bigInt=900719925474099100n //后面加个n
console.log(bigInt+10n)
const num=100
console.log(bigInt+BigInt(num))

ES11 - Nullish Coalescing Operator 空值合并运算 ??

ES11 - Nullish Coalescing Operator 空值合并运算 ??
const foo=undefined
// const bar=foo || 'defualt value'
const bar=foo ?? 'defualt value' //不包括 '' 0

coderwhy ES11 -Optional Chaining 可选链

可选链也是ES11中新增一个特性,主要作用是让我们的代码在进行null和undefined判断时更加清晰和简洁:

info.firend?.girFriend?.name 
没有取到返回undefined  有就有值

coderwhy ES11-Global This
在之前我们希望获取JavaScript环境的全局对象,不同的环境获取的方式是不一样的
口比如在浏览器中可以通过this,window来获取;
口比如在Node中我们需要通过global来获取;

//浏览器
console.log(window)
console.log(this)

//node
console.log(global)

同一个文件可能在浏览器或者node运行 以前获取是判断 
现在 es11 2020
console.log(globalThis)

cow ES11-for..in标准化
在ES11之前,虽然很多浏览器支持for..in来遍历对象类型,但是并没有被ECMA标准化。

在ES11中,对其进行了标准化,for...in是用于遍历对象的key的:

coderwhy ES11 其他知识点
Dynamic Import:后续ES Module模块化中讲解。
Promise.allSettled:后续讲Promise的时候讲解。
import meta:后续ES Module模块化中讲解。

作者node --version v14.17.0

coderwhy ES12-FinalizationRegistry 2021
FinalizationRegistry对象可以让你在对象被垃圾回收时请求一个回调。
FinalizationRegistry提供了这样的一种方法:当一个在注册表中注册的对象被回收时,请求在某个时间点上调用一个清理回调。(清理回调有时被称为 finalizer);
你可以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值;

c语言GC时时 JavaScript-GC-不定时

//ES12 FinalizationRegistry类
const finalRegistry=new FinalizationRegistry((value)=>{
    console.log('注册在finalRegistry的对象,某一个被销毁',value)
})
let obj={name:'koo'}
finalRegistry.register(obj,'obj')
obj=null

浏览器执行

developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WeakRef

WeakRef
-ES12:WeakRef类
WeakRef.prototype.deref:
如果原对象没有销毁,那么可以获取到原对象
如果原对象已经销毁,那么获取到的是undefined
//ES12 WeakRef类
const finalRegistry=new FinalizationRegistry((value)=>{
    console.log('注册在finalRegistry的对象,某一个被销毁',value)
})
let obj={name:'koo'}
let info=new WeakRef(obj)
finalRegistry.register(obj,'obj')
console.log(info.deref().name)
obj=null

coderwhy ES12-WeakRefs

如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用:
口如果我们希望是一个弱引用的话,可以使用WeakRef;

ES12 - logical assignment operators

// 1.||= 逻辑或赋值运算
// let msg=undefined
// // msg=msg||'default vlaue'
// msg ||= 'default value'
// console.log(msg)
// 2.&&= 逻辑与赋值运算
// const obj={
//     name:'koo',
//     foo:function(){}
// }
// obj.foo && obj.foo()

let info={
    name:'koo'
}
// 1.判断info
// 2.有值的情况下,取出info.name
// info=info && info.name
// info &&= info.name
// console.log(info)
// 3.??= 逻辑空赋值运算
let msg=0
msg ??= 'default value'
console.log(msg)

coderwhy ES12其他知识点
Numeric Separator:讲过了;
String.replaceAll:字符串替换;

18ES Module以及原理

Proxy 婆s Reflect 绿flai

coderwhy监听对象的操作
我们先来看一个需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程
口通过我们前面所学的知识,能不能做到这一点呢?
口其实是可以的,我们可以通过之前的属性描述符中的存储属性描述符来做到;

左边这段代码就利用了前面讲过的 Object.defineProperty 的存储属性描述符来对属性的操作进行监听。
但是这样做有什么缺点呢?
首先,Object.defineProperty设计的初衷,不是为了去监听截止一个对象中所有的属性的。
我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符。
其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么Object.defineProperty是无能为力的。
所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象。

let obj={
    name:'koo',
    age:18
}
Object.keys(obj).forEach(key=>{
    let value=obj[key]
    Object.defineProperty(obj,key,{
        get:function(){
            console.log(`访问了${key}`)
            return value //没返回 默认是undefined
        },
        set:function(newValue){
            console.log(`设置了${key}`)
            value=newValue
        }
    })
})
obj.name=88
console.log(obj.age)

coderwhy Proxy基本使用
在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的:
口也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象);
口之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作;
我们可以将上面的案例用Proxy来实现一次:
口首先,我们需要new Proxy对象,并且传入需要侦听的对象以及一个处理对象,可以称之为handler;

​ const p = new Proxy(target,handler)
口其次,我们之后的操作都是直接对Proxy的操作,而不是原有的对象,因为我们需要在handler里面进行侦听;

let obj={
    name:'koo',
    age:18
}
let objProxy=new Proxy(obj,{
    get(target,key){
        console.log(`访问了${key}`,target)
        return target[key]
    },
    set:function(target,key,newValue){
        console.log(`设置了${key}`,target)
        target[key]=newValue
    }
})
objProxy.name='john'
console.log(objProxy.age)

coderwhy Proxy的set和get捕获器
如果我们想要侦听某些具体的操作,那么就可以在handler中添加对应的捕捉器(Trap):
set和get分别对应的是函数类型;
set函数有四个参数:
target:目标对象(侦听的对象);
property:将被设置的属性key;
value:新属性值;
receiver:调用的代理对象;
get函数有三个参数:
target:目标对象(侦听的对象);
property:被获取的属性key;
receiver:调用的代理对象;

let obj={
    name:'koo',
    age:18
}
let objProxy=new Proxy(obj,{
    get(target,key){
        console.log(`访问了${key}`,target)
        return target[key]
    },
    set:function(target,key,newValue){
        console.log(`设置了${key}`,target)
        target[key]=newValue
    },
    has(target,key){
        console.log(`in了${key}`,target)
        return key in target
    },
    deleteProperty(target,key){
        console.log(`delete了${key}`,target)
        delete target[key]
    }
})
// objProxy.name='john'
// console.log(objProxy.age)
// console.log('name' in objProxy)

delete objProxy.name

coderwhy Proxy所有捕获器
13个活捉器分别是做什么的呢?
handler.getPrototypeOf()
Object.getPrototypeOf 方法的捕捉器。
handler.setPrototypeOf()
Object.setPrototypeOf 方法的捕捉器。
handler.isExtensible()
Object.isExtensible 方法的捕捉器。
handler.preventExtensions()
Object.preventExtensions方法的捕捉器。
handler.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.defineProperty()
Object.defineProperty 方法的捕捉器。

handler.ownKeys()
Object.getOwnPropertyNames 方法和
Object.getOwnPropertySymbols 方法的捕捉器。
handler.has()
in 操作符的捕捉器。
handler.get()
属性读取操作的捕捉器。
handler.set()
属性设置操作的捕捉器。
handler.deleteProperty()
delete 操作符的捕捉器。
handler.apply函数调用操作的捕捉器。
handler.construct()
new 操作符的捕捉器。

apply constuct 是对函数有用的
function foo(){}
let fooProxy=new Proxy(foo,{
    apply(target,thisArg,argArray){
        console.log('apply调用foo')
        return target.apply(thisArg,argArray)
    },
    construct(target,argArray,newTarget){
        console.log('new调用foo')
        return new target(...argArray)
    }
})
fooProxy.apply({},[1,2,3])
new fooProxy(1,2,3)

http://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Objectmethods

coderwhy Reflect的作用
Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。
那么这个Reflect有什么用呢?
它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();
比如Reflect.defineProperty(target,propertyKey,attributes)类似于Object.defineProperty);
如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?
这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;
但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;
另外还包含一些类似于 in,delete操作符,让JS看起来是会有一些奇怪的;
所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;
那么Object和Reflect对象之间的API关系,可以参考MDN文档:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods

coderwhy Reflect的常见方法
Reflect中有哪些常见的方法呢?它和Proxy是——对应的,也是13个:
Reflect.getPrototypeOf(target)
类似于 Object.getPrototypeOf().
Reflect.setPrototypeOf(target,prototype)
设置对象原型的函数.返回一个 Boolean,如果更新成功,则返回true
Reflect.isExtensible(target)
类似于 Object.isExtensible()
Reflect.preventExtensions(target)
类似于 Object.preventExtensions()。返回一个Boolean
Reflect.getOwnPropertyDescriptor(target,propertyKey)
类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,否则返回undefined.
Reflect.defineProperty(target,propertyKey,attributes)
和 Object.defineProperty0 类似。如果设置成功就会返回 true

Reflect.ownKeys(target)
返回一个包含所有自身属性(不包含继承属性)的数组。(类似于Object.keys(),但不会受enumerable影响)
.Reflect.has(target,propertyKey)判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
Reflect.get(target,propertyKeyl,receiver])
获取对象身上某个属性的值,类似于 target[name]。
Reflect.set(target,propertyKey,value[,receiver])
将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
Reflect.deleteProperty(target,propertyKey)
作为函数的delete操作符,相当于执行 delete target[name]。
Reflect.apply(target,thisArgument,argumentsList)
对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和Function.prototype.apply0功能类似。
Reflect.construct(target,argumentsList[,newTarget])
对构造函数进行 new 操作,相当于执行 new target(...args).

receiver 绿色我

let obj={
    name:'koo'
}
let objProxy=new Proxy(obj,{
    get(target,key,receiver){
        console.log('get````````````')
        return Reflect.get(target,key)
    },
    set(target,key,newValue,receiver){
        console.log('set``````````````')
        let result=Reflect.set(target,key,newValue)
        if(result){}else{}
    }
})
objProxy.name='john'
console.log(objProxy.name)
let obj={
    _name:'koo',
    get name(){
        return this._name
    },
    set name(newName){
        this._name=newName
    }
}
let objProxy=new Proxy(obj,{
    get(target,key,receiver){
        console.log('get````````````',receiver)//receiver===objProxy代理对象
        return Reflect.get(target,key,receiver)//receiver 相当改变this
    },
    set(target,key,newValue,receiver){
        console.log('set``````````````')
        Reflect.set(target,key,newValue,receiver)
    }
})
objProxy.name='john'
console.log(objProxy.name)
function Student(name,age){
    this.name=name
    this.age=age
}
function Teacher(){

}

// const stu=new Student('why',18)
// console.log(stu)
// console.log(stu.__proto__===Student.prototype)

//执行Student函数中的内容,但是创建出来对象是Teacher 对象
const teacher=Reflect.construct(Student,['why',18],Teacher)
console.log(teacher)
console.log(teacher.__proto__===Teacher.prototype)

响应式原理实现

19npm、yarn、cnpm包管理工作

1.数据结构和算法2.设计模式3.计算机网络4.操作系统

Depend优化:
1> depend方法
2> 使用Set来保存依赖函数,而不是数组[]

// 保存当前需要收集的响应式函数
let activeReactiveFn=null
class Depend{
    constructor(){
        this.rectiveFns=new Set()
    }
    depend(){
        if(activeReactiveFn){
            this.rectiveFns.add(activeReactiveFn)
        }
    }
    notify(){
        this.rectiveFns.forEach(fn=>{
            fn()
        })
    }
}
//封装一个响应式的函数
function watchFn(fn){
    activeReactiveFn=fn
    fn()
    activeReactiveFn=null
}
// 封装一个获取depend函数
const targetMap=new WeakMap()
function getDepend(target,key){
    //根据target对象获取map的过程
    let map=targetMap.get(target)
    if(!map){
        map=new Map()
        targetMap.set(target,map)
    }
    //根据key获取depend对象
    let depend=map.get(key)
    if(!depend){
        depend=new Depend()
        map.set(key,depend)
    }
    return depend
}

function reactive(obj){
    //监听对象的属性变量:Proxy(vue3)/Object.defineProperty(vue2)
    //vue3
    // return new Proxy(obj,{
    //     get(target,key,receiver){
    //         const depend=getDepend(target,key)
    //         depend.depend()
    //         return Reflect.get(target,key,receiver)
    //     },
    //     set(target,key,newValue,receiver){
    //         Reflect.set(target,key,newValue,receiver)
    //         let depend=getDepend(target,key)
    //         depend.notify()
    //     }
    // })
    //vue2
    Object.keys(obj).forEach(key=>{
        let value=obj[key]
        Object.defineProperty(obj,key,{
            get(){
                const depend=getDepend(obj,key)
                depend.depend()
                return value
            },
            set(newValue){
                value=newValue
                let depend=getDepend(obj,key)
                depend.notify()
            }
        })
    })
    return obj
}

//对象的响应式
const obj=reactive({
    name:'koo',
    age:18
})

watchFn(function(){
    console.log('hello world',obj.name)
})

obj.name='john'

00:57:36

Al 穷举:围棋-> 所有可能性-> 所有的可能性步骤加在一起比整个宇宙所有的原子加在一起还要多。Al永远不能在围棋上战胜世界顶级的围棋选手。神经元

coderwhy异步任务的处理
在ES6出来之后,有很多关于Promise的讲解、文章,也有很多经典的书籍讲解Promise
口虽然等你学会Promise之后,会觉得Promise不过如此,但是在初次接触的时候都会觉得这个东西不好理解;

那么这里我从一个实际的例子来作为切入点:
口我们调用一个函数,这个函数中发送网络请求(我们可以用定时器来模拟);
口如果发送网络请求成功了,那么告知调用者发送成功,并且将相关数据返回过去;
口如果发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息;

新技术的出现都是为了解决原有技术的某一个痛点。

★这种回调的方式有很多的弊端:
I> 如果是我们自己封装的requestData,那么我们在封装的时候必须要自己设计好callback名称,并且使用好
2> 如果我们使用的是别人封装的requestData或者一些第三方库,那么我们必须去看别人的源码或者文档,才知道它这个函数需要怎么去获取到结果

then 仍

0:21:22

coderwhy什么是Promise呢?
在上面的解决方案中,我们确确实实可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:
口第一,我们需要自己来设计回调函数、回调函数的名称、回调函数的使用等;
口第二,对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它这个函数到底怎么用;
我们来看一下Promise的API是怎么样的:
口Promise是一个类,可以翻译成承诺、许诺、期约;
口当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;
口在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
这个回调函数会被立即执行,并且给传入另外两个回调函数resolve,reject;
当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;

resolve 绿弱 reject 绿接

传入的这个函数,被称之为 executor

-resolve:回调函数,在成功时,回调resolve函数
reject:回调函数,在失败时,回调reject函数

then方法传入的回调函数,会在Promise执行resolve函数时,被回调

catch方法传人的回词國做,会在Pramse执行reject函数时,被回调

node then catch分开写 error

const promise=new Promise((resolve,reject)=>{
    resolve()
    //reject
})
promise.then((res)={},(err)=>{})

钩子函数:hook 回调函数

coderwhy Promise的代码结构
我们来看一下Promise代码结构:
上面Promise使用过程,我们可以将它划分成三个状态:
待定(pending):初始状态,既没有被兑现,也没有被拒绝;
当执行executor中的代码时,处于该状态;
已兑现(fulfilled):意味着操作成功完成;
执行了resolve时,处于该状态;
已拒绝(rejected):意味着操作失败;
执行了reject时,处于该状态;

状态一旦确定下来,那么就是不可更改的(锁定)

resolve(参数)
1> 普通的值或者对象 pending-> fulfilled
2>-传入一个Promise
那么当前的Promise的状态会由传入的Promise来决定
相当于状态进行了移交

resolve(参数)
1> 普通的值或者对象 pending-> fulfilled
2>-传入一个Promise
那么当前的Promise的状态会由传入的Promise来决定
相当于状态进行了移交
3> 传入一个对象,并且这个对象有实现then方法那么也会执行该then方法,并且又该then方法决定后续状态
const newPromise=new Promise((resolve,reject)=>{
    resolve() //新的决定状态
})
new Promise((resolve,reject)=>{
    resolve(newPromise)
}).then((res)={
    console.log('res:',res)
},(err)=>{})


3> 传入一个对象,并且这个对象有实现then方法(并且这个对象是实现了thenable)
那么也会执行该then方法,并且又该then方法决定后续状态
new Promise((resolve,reject)=>{
    const obj={
        then:function(resolve,reject){
            reject('reject message')
        }
    }
    resolve(obj)
}).then((res)={
    console.log('res:',res)
},(err)=>{
    console.log('err:',err)
})

eatable 接口
interface eating
let obj={
    eating:function(){}
}

20Proxy和Reflect的详细解析

Object.getOwnPropertyDescriptors查看方法

1.同一个Promise可以被多次调用then方法
当我们的resolve方法被回调时,所有的then方法传入的回调函数都会被调用

promise.then().then() 链式调用 不是同一个promise

分开 是
promise.then()
promise.then()

2.then方法传入的"回调函数:可以有返回值
then方法本身也是有返回值的,它的返回值是Promise
1> 如果我们返回的是一个普通值(数值/字符串/普通对象/undefined),那么这个普通的值被作为一个新的Promise的resolve值
promise.then(res=>{
	return 'aaaa'
})
========上下相等 自动转成
promise.then(res=>{
	return new Promise(resolve=>resolve('aaaa'))
})

return undefined // new Promise(resolve => resolve(undefined))
默认返回undefined

2>-如果我们返回的是一个Promise
promise.then(res=>{
	return new Promise(resolve=>{ //由新的promise决定状态
		reslove(1111)
	})
}).then(res=>{
	console.log('res:',res)
})

3>-如果返回的是一个对象,并且该对象实现了thenable
promise.then(res=>{
	return {
		then(resolve,reject){
			resolve(222)
		}
	}
}).then(res=>{
	console.log('res:',res) //222
})

00:48:55

const promise=new Promise((resolve,reject)=>{
    throw new Error('rejected status')
})
//1.当executor 抛出异常时,也是会调用错误捕获的回调函数的
promise.then(undefined,err=>{
    console.log('err:',err)
})

promise.then(res=>{}).catch(err=>{
    return '1211' //new Promise(resolve=>resolve('1211'))
}).then(res=>{
    后面这个resolve生效
}).catch(err=>{
    没用
})

https://promisesaplus.com

throw 和 return 一样 后面代码不执行

coderwhy finally方法
finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行的代码。
finally方法是不接收参数的,因为无论前面是fulfilled状态,还是rejected状态,它都会执行。

promise.then().catch().finally(()=>{
    一些清除工作
})

coderwhy resolve方法
前面我们学习的then,catch,finally方法都属于Promise的实例方法,都是存放在Promise的prototype上的。
口下面我们再来学习一下Promise的类方法。
有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用Promise.resolve方法来完成。
Promise.resolve的用法相当于new Promise,并且执行resolve操作:

const promise=Promise.resolve(obj)
const promise=new Promise((resolve,reject)=>{
    resolve(obj)
})
Promise.reject(obj) 注意:无论传入什么值都是一样的 thenable Promise一样

需求:所有的Promise都变成fulfilled时,再拿到结果
const p1=new Promise()
Promise.add([p1,p2,p3,'aaa']).then(res=>{
    res数组 按传入的顺序
}) //aaa自动转promise.resolve 

意外:在拿到所有结果之前,有一个promise变成了rejected,那么整个promise是rejected
中断 后面的不执行
Promise.add([p1,p2,p3,'aaa']).then(res=>{}).catch(err=>{
    
})
Promise.addSettled([p1,p2,p3,'aaa']).then(res=>{
    res数组包含对象 包括reject都在里面
}).catch(err=>{
    不会到这里
})

race:竞技/竞赛
只要有一个Promise变成fulfilled状态,那么就结束意外:
Promise.race([p1,p2,p3,'aaa']).then(res=>{
    最快成功的一个
}).catch(err=>{
    如果reject时间最短就到这
})

Promise.any([p1,p2,p3]).then(res=>{
    等到有一个resolve就执行 不管reject最快
}).catch(err=>{
    如果全部都是reject 就reject
    console.log(err.errors) 报一个合计错
})

coderwhy allSettled方法
all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。
口那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;
在ES11(ES2020)中,添加了新的API Promise.allSettled:
口该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;
口并且这个Promise的结果一定是fulfilled的;

coderwhy any方法
any方法是ES12中新增的方法,和race方法是类似的:
口any方法会等到一个fulfilled状态,才会决定新Promise的状态;
口如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;

手写Promise-结构的设计

02:30:17

onClick on当点击时执行这个函数

21 手写Promise的整个逻辑和API

八.all/allSettled核心:
要知道new Promise的resolve、reject在什么情况下执行
all:
情况一:所有的都有结果
情况二:有一个reject
allSettled:情况:所有都有结果,并且一定执行resolve

九.race/any
race:
情况:只要有结果
any:
情况一:必须等到一个resolve结果
情况二:都没有resolve,所有的都是reject

手写promise

// ES6 ES2015
// https://promisesaplus.com

const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'reject'

// 工具函数
function execFunctionWithCatchError(execFn, value, resolve, reject){
    try {
        const result = execFn(value)
        resolve(result)
    } catch (err) {
        reject(err)
    }
}

class HYPromise{
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectedFns = []

        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING){
                // setTimeout(() => {
                //     this.value = value
                //     this.onFulfilled(this.value)
                // }, 0);
                // 添加微任务
                queueMicrotask(() => { //加入微任务
                    if (this.status !== PROMISE_STATUS_PENDING) return 
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.onFulfilledFns.forEach(fn=>{
                        fn(this.value)
                    })
                });
            }
        }

        const reject = (reason) => {
            if (this.status === PROMISE_STATUS_PENDING){
                queueMicrotask(()=>{
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED
                    this.reason = reason
                    this.onRejectedFns.forEach(fn=>{
                        fn(this.reason)
                    })
                })
            }
        }

        try {
            executor(resolve,reject)
        } catch (err){
            reject(err)
        }
    }

    then(onFulfilled, onRejected) {
        const defaultOnRejected = err => {throw err }
        onRejected = onRejected || defaultOnRejected

        const defaultOnFulfilled = value => { return value}
        onFulfilled = onFulfilled || defaultOnFulfilled

        return new HYPromise((resolve,reject) => {
            // 1.如果在then 调用的时候,状态已经确定下来
            if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled){
                execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
            }
            if (this.status === PROMISE_STATUS_REJECTED && onRejected){
                execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
            }

            //onFulfilled, onRejected可以做一些判断 传空传其他
            // 2.将成功回调和失败的回调放到数组中
            if (this.status === PROMISE_STATUS_PENDING){
                if (onFulfilled) this.onFulfilledFns.push(() => {
                    execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
                })
                if (onRejected) this.onRejectedFns.push(() => {
                    execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
                })
            }
        })
    }

    catch(onRejected){
        return this.then(undefined, onRejected)
    }

    finally(onFinally){
        this.then(() => {
            onFinally()
        }, () => {
            onFinally()
        })
    }

    static resolve(value) {
        return new HYPromise((resolve) => resolve(value))
    }

    static reject(reason){
        return new HYPromise((resolve, reject) => reject(reason))
    }

    static all(promises) {
        return new HYPromise((resolve, reject)  => {
            const values = []
            promises.forEach(promise => {
                promise.then(res => {
                    values.push(res)
                    if(values.length === promise.length){
                        resolve(values)
                    }
                }, err => {
                    reject(err)
                })
            })
        })
    }

    static allSettled(promises) {
        return new HYPromise((resolve) => {
            const results = []
            promises.forEach(promise => {
                promise.then(res => {
                    results.push({ status: PROMISE_STATUS_FULFILLED, value: res})
                    if(results.length === promise.length){
                        resolve(results)
                    }
                }, err => {
                    results.push({ status: PROMISE_STATUS_REJECTED, value: err})
                    if(results.length === promise.length){
                        resolve(results)
                    }
                })
            })
        })
    }

    static race(promises) {
        return new HYPromise((resolve, reject) => {
            promises.forEach(promise => {
                // promise.then(res => {
                //     resolve(res)
                // }, err => {
                //     reject(err)
                // })
                promise.then(resolve,reject)
            })
        })
    }

    static any(promises) {
        // resolve必须等到有一个成功的结果
        // reject所有的都失败才执行reject
        const reasons = []
        return new HYPromise((resolve, reject) => {
            promises.forEach(promise => {
                promise.then(resolve, err => {
                    reasons.push(err)
                    if (reasons.length === promises.length){
                        reject(new AggregateError(reasons))
                    }
                })
            })
        })
    }
}

HYPromise.resolve('hello').then(res => {
    console.log('res:',res)
})

// const promise = new HYPromise((resolve,reject) => {
//     console.log('状态pending')
//     resolve(2222)
//     reject(1111)
// })

// promise.then(res=>{
//     console.log('res1:',res)
// }).catch(err=>{
//     console.log('err1:',err)
// })

// promise.then(res=>{
//     console.log('res2:',res)
// },err=>{
//     console.log('err2:',err)
// })

// setTimeout(() => {
//     promise.then(res=>{
//         console.log('res3:',res)
//     },err=>{
//         console.log('err3:',err)
//     })
// }, 1000);

22 迭代器-可迭代对象-生成器用法

undefined320

当函数参数有默认值的情况下,多形成一个作用域的

参数作用域

当函数的参数有默认值时,会形成一个新的作用域,这个作用域用于保存参数的值

https://262.ecma-international.org

coderwhy认识BOM JavaScript有一个非常重要的运行环境就是浏览器,而且浏览器本身又作为一个应用程序需要对其本身进行操作,所以通常浏览器会有对应的对象模型(BOM,Browser Object Model).
我们可以将BOM看成是连接JavaScript脚本与浏览器窗口的桥梁。
BOM主要包括一下的对象模型:
window:包括全局属性、方法,控制浏览器窗口相关的属性、方法;
location:浏览器连接到的对象的位置(URL);
history:操作浏览器的历史;
document:当前窗口操作文档的对象;
window对象在浏览器中有两个身份:
身份一:全局对象。
我们知道ECMAScript其实是有一个全局对象的,这个全局对象在Node中是global;在浏览器中就是window对象;
身份二:浏览器窗口对象。
作为浏览器窗口时,提供了对浏览器操作的相关的API;

width: 100px;
height: 100px;
快捷键
w100+h100 vscode

http://developer.mozilla.org/zh-CN/docs/Web/API/Node

const div=document.createElemnet('div')
div.textContent='内容'
box.appendChild(div)

注意事项:document对象
document.body.appendChild(div)

window.location === document.location

<div name='koo'></div>
document.getElementByName('koo')


coderwhy认识事件监听
前面我们讲到了JavaScript脚本和浏览器之间交互时,浏览器给我们提供的BOM,DOM等一些对象模型。
口事实上还有一种需要和浏览器经常交互的事情就是事件监听:
口浏览器在某个时刻可能会发生一些事件,比如鼠标点击、移动、滚动、获取、失去焦点、输入内容等等一系列的事件;.
我们需要以某种方式(代码)来对其进行响应,进行一些事件的处理;
口在Web当中,事件在浏览器窗口中被触发,并且通过绑定到某些元素上或者浏览器窗口本身,那么我们就可以给这些元素或者window窗口来绑定事件的处理程序,来对事件进行监听。
如何进行事件监听呢?
口事件监听方式一:在script中直接监听;
口事件监听方式二:通过元素的on来监听事件;
口事件监听方式三:通过EventTarget中的addEventListener来监听;

01:46:32

onClick 只能监听一次 后面覆盖前面

addEventListener可以多次

addEventListener('click',(evebt)=>{},true)
//true为捕获  不写是冒泡
event.target event.currentTarget在冒泡那个阶段
event.preventDefault阻止默认行为
event.stopImmediatePropagation()阻止以后的事件监听触发
event.stopPropagation()阻止冒泡捕获

coderwhy事件冒泡和事件捕获
我们会发现默认情况下事件是从最内层的span向外依次传递的顺序,这个顺序我们称之为事件冒泡(Event Bubble)。
事实上,还有另外一种监听事件流的方式就是从外层到内层(body-> span),这种称之为事件捕获(Event Capture);
为什么会产生两种不同的处理流呢?
这是因为早期浏览器开发时,不管是IE还是Netscape公司都发现了这个问题,但是他们采用了完全相反的事件流来对事件进行了传递;
IE采用了事件冒泡的方式,Netscape采用了事件捕获的方式;

developer.mozilla.org/zh-CN/docs/Web/Events

coderwhy事件对象event当一个事件发生时,就会有和这个事件相关的很多信息:
比如事件的类型是什么,你点击的是哪一个元素,点击的位置是哪里等等相关的信息;
那么这些信息会被封装到一个Event对象中;
该对象给我们提供了想要的一些属性,以及可以通过该对象进行某些操作;
常见的属性:
type:事件的类型;
target:当前事件发生的元素;
currentTarget:当前处理事件的元素;
offsetX、offsetY:点击元素的位置;
常见的方法:
preventDefault:取消事件的默认行为;
stopPropagation:阻止事件的进一步传递;
事件类型:https://developer.mozilla.org/zh-CN/docs/Web/Events

coderwhy认识防抖和节流函数
防抖和节流的概念其实最早并不是出现在软件工程中,防抖是出现在电子元件中,节流出现在流体流动中
而JavaScript是事件驱动的,大量的操作会触发事件,加入到事件队列中处理。
而对于某些频繁的事件处理会造成性能的损耗,我们就可以通过防抖和节流来限制事件频繁的发生;
防抖和节流函数目前已经是前端实际开发中两个非常重要的函数,也是面试经常被问到的面试题。
但是很多前端开发者面对这两个功能,有点摸不着头脑:
口某些开发者根本无法区分防抖和节流有什么区别(面试经常会被问到);
某些开发者可以区分,但是不知道如何应用;
某些开发者会通过一些第三方库来使用,但是不知道内部原理,更不会编写;
接下来我们会一起来学习防抖和节流函数:
口我们不仅仅要区分清楚防抖和节流两者的区别,也要明白在实际工作中哪些场景会用到;
口并且我会带着大家一点点来编写一个自己的防抖和节流的函数,不仅理解原理,也学会自己来编写;

23 生成器-async-await-js线程

生成器替代迭代器使用

//迭代器
function createArrayIterator(arr) {
    let index = 0
    return {
        next: function() {
            if (index < arr.length){
                return { done: false, value: arr[index++] }
            } else {
                return { done: true, value: undefined}
            }
        }
    }
}
const names=['abc','cba','nba']
const nameIterator=createArrayIterator(names)

console.log(nameIterator.next())
console.log(nameIterator.next())
console.log(nameIterator.next())
console.log(nameIterator.next())

生成器来替代迭代器
function* createArrayIterator(arr) {
    for (const item of arr){
        yield item
    }
}

function* createArrayIterator(arr) {
    yield* arr
    yield* 可迭代对象
}

事实上我们还可以使用yield*来生产一个可迭代对象:
口这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值;

class Person{
    foo=function*(){
        
    }
    foo=()=>{}
    *[Symbol.iterator](){
        
    }
}

回调地狱

generator 抓呢雷特

npm install co 这个包自动执行生成器

n切换node 只支持mac

TJ:-co/n(nvm)/commander(coderwhy/vue cli)/express/koa(egg)

coderwhy异步函数 async functionasync关键字用于声明一个异步函数:
async是asynchronous单词的缩写,异步、非同步;
sync是synchronous单词的缩写,同步、同时;

异步函数的返回值一定是一个Promise

异步函数中的异常,会被作为异步函数返回的Promise的reject值的

coderwhyawait关键字
async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。
await关键字有什么特点呢?
通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise;
那么await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数;
如果await后面是一个普通的值,那么会直接返回这个值;
如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值;

coderwhy进程和线程
线程和进程是操作系统中的两个概念:
口进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式;
口线程(thread):操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中;

听起来很抽象,这里还是给出我的解释:
口进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程);
口线程:每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主线程;
口所以我们也可以说进程是线程的容器;

再用一个形象的例子解释:
口操作系统类似于一个大工厂;
口工厂中里有很多车间,这个车间就是进程;
口每个车间可能有一个以上的工人在工厂,这个工人就是线程;

coderwhy操作系统的工作方式
操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?
口这是因为CPU的运算速度非常快,它可以快速的在多个进程之间迅速的切换;
口当我们进程中的线程获取到时间片时,就可以快速执行我们编写的代码;
口对于用户来说是感受不到这种快速的切换的;

coderwhy浏览器中的JavaScript线程
我们经常会说JavaScript是单线程的,但是JavaScript的线程应该有自己的容器进程:浏览器或者Node。

浏览器是一个进程吗,它里面只有一个线程吗?
口目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出;
口每个进程中又有很多的线程,其中包括执行JavaScript代码的线程

JavaScript的代码执行是在一个单独的线程中执行的:
口这就意味着JavaScript的代码,在同一个时刻只能做一件事;
口如果这件事是非常耗时的,就意味着当前的线程就会被阻塞;

所以真正耗时的操作,实际上并不是由JavaScript线程在执行的:
口浏览器的每个进程是多线程的,那么其他线程可以来完成这个耗时的操作;
口比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可;

事件队列

24 浏览器Node的事件循环-微任务-宏任务

队列 先进先出

栈 先进后出

宏任务队列macrotask queue 定时器-ajax-DOM-UI Rendering
微任务队列microtask queue queueMicrotack Promise.then

规范:在执行任何的宏任务之前,都需要先保证微任务队列已经被清空

codrwh宏任务和微任务
但是事件循环中并非只维护着一个队列,事实上是有两个队列:
口宏任务队列(macrotask queue):ajax,setTimeout,setInterval,DOM监听、UI Rendering等
口微任务队列(microtask queue):Promise的then回调、Mutation Observer API、queueMicrotask()等

那么事件循环对于两个队列的优先级是怎么样的呢?
01.main script中的代码优先执行(编写的顶层script代码);
02.在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
也就是宏任务执行之前,必须保证微任务队列是空的;
如果不为空,那么就优先执行微任务队列中的任务(回调);

new Promise(()=>{
  这个是main script  
})

async function demo(){
	console.log(111)
	await foo() //返回promise
	console.log(222) //加入微任务队列
}

不是普通的值,多加一次微任务

coderwhy Node的宏任务和微任务
我们会发现从一次事件循环的Tick来说,Node的事件循环更复杂,它也分为微任务和宏任务:
宏任务(macrotask):setTimeout、setInterval、IO事件、setImmediate、close事件;
微任务(microtask):Promise的then回调、process.nextTick、queueMicrotask;
但是,Node中的事件循环不只是 微任务队列和 宏任务队列:
微任务队列:
next tick queue:process.nextTick;
other queue:Promise的then回调、queueMicrotask;
宏任务队列:
timer queue:setTimeout、setInterval;
poll queue:IO事件;
check queue:setImmediate;
close queue:close事件;

coderwhy Node事件循环的顺序
所以,在每一次事件循环的tick中,会按照如下顺序来执行代码:
next tick microtask queue;
other microtask queue;
timer queue;
poll queue;
check queue;
close queue;

25 模块化-CommonJS-AMD-CMD-ES模

coderwhy错误处理方案
开发中我们会封装一些工具函数,封装之后给别人使用:
口在其他人使用的过程中,可能会传递一些参数;
口对于函数来说,需要对这些参数进行验证,否则可能得到的是我们不想要的结果;
很多时候我们可能验证到不是希望得到的参数时,就会直接return:
口但是return存在很大的弊端:调用者不知道是因为函数内部没有正常执行,还是执行结果就是一个undefined;
口事实上,正确的做法应该是如果没有通过某些验证,那么应该让外界知道函数内部报错了;
如何可以让一个函数告知外界自己内部出现了错误呢?
口通过throw关键字,抛出一个异常
throw语句:
throw语句用于抛出一个用户自定义的异常;
当遇到throw语句时,当前的函数执行会被停止(throw后面的语句不会执行);
如果我们执行代码,就会报错,拿到错误信息的时候我们可以及时的去修正代码。

throw 'string'
throw {errorCode:-1001,errorMessage:'type不能为零'}

throw 吃路

const err=new Error('type')
console.log(err.message)
err.name
err.stack 调用栈
throw err

Error有一些自己的子类:
RangeError:下标值越界时使用的错误类型;
SyntaxError:解析语法错误时使用的错误类型;口
TypeError:出现类型错误时,使用的错误类型;

强调:如果函数中已经抛出了异常,那么后续的代码都不会继续执行了

两种处理方法:
1.第一种是不处理,那么异常会进一步的抛出,直到最顶层的调用
如果在最顶层也没有对这个异常进行处理,那么我们的程序就会终止执行,并且报错

try{
    
}catch(err){
    
}finally{
    
}

try{
    
}catch{
    
}

coderwhy什么是模块化?
到底什么是模块化、模块化开发呢?
事实上模块化开发最终的目的是将程序划分成一个个小的结构;
这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构;
这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用;
也可以通过某种方式,导入另外结构中的变量、函数、对象等;

上面说提到的结构,就是模块;按照这种结构划分开发程序的过程,就是模块化开发的过程;

无论你多么喜欢JavaScript,以及它现在发展的有多好,它都有很多的缺陷:
比如var定义的变量作用域问题;
比如JavaScript的面向对象并不能像常规面向对象语言一样使用class;
比如JavaScript没有模块化的问题;
Brendan Eich本人也多次承认过JavaScript设计之初的缺陷,但是随着JavaScript的发展以及标准化,存在的缺陷问题基本都得到了完善。
无论是web、移动端、小程序端、服务器端、桌面应用都被广泛的使用;

coderwhy CommonJS规范和Node关系
我们需要知道CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来为了体现它的广泛性,修改为CommonJS,平时我们也会简称为CJS。
Node是CommonJS在服务器端一个具有代表性的实现;
Browserify是CommonJS在浏览器中的一种实现;
webpack打包工具具备对CommonJS的支持和转换;
所以,Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发:
在Node中每一个js文件都是一个单独的模块;
这个模块中包括CommonJS规范的核心变量:exports,module.exports,require;
我们可以使用这些变量来方便的进行模块化开发;
前面我们提到过模块化的核心是导出和导入,Node中对其进行了实现:
exports和module.exports可以负责对模块中的内容进行导出;
require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;

module.exports={
    name,
    age
}
const why=require('./why.js')


var name='koo'
exports.name=name


node源代码
module.exports={}
exports=module.exports

commonjs 可以改导入的变量 import不行

26 包管理工具解析npm-cnpm-yarn

coderwhy require细节
我们现在已经知道,require是一个函数,可以帮助我们引入一个文件(模块)中导出的对象。
那么,require的查找规则是怎么样的呢?
https://nodejs.org/dist/latest-v14.x/docs/api/modules.html#modules_all_together

coderwhy模块的加载过程
结论一:模块在被第一次引入时,模块中的js代码会被运行一次
结论二:模块被多次引入时,会缓存,最终只加载(运行)一次
为什么只会加载运行一次呢?
这是因为每个模块对象module都有一个属性:loaded。
为false表示还没有加载,为true表示已经加载;

coderwhy CommonJS规范缺点
CommonJS加载模块是同步的:
口同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行;
口这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快;
如果将它应用于浏览器呢?
浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行;
那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作;
所以在浏览器中,我们通常不使用CommonJS规范:
当然在webpack中使用CommonJS是另外一回事;
因为它会将我们的代码转成浏览器可以直接执行的代码;
在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD:
口但是目前一方面现代的浏览器已经支持ES Modules,另一方面借助于webpack等工具可以实现对CommonJS或者ES Module代码的转换;
AMD和CMD已经使用非常少了,所以这里我们进行简单的演练;

coderwhy AMD规范
AMD主要是应用于浏览器的一种模块化规范:
口AMD是Asynchronous Module Definition(异步模块定义)的缩写;
口它采用的是异步加载模块;
口事实上AMD的规范还要早于CommonJS,但是CommonJS目前依然在被使用,而AMD使用的较少了;
我们提到过,规范只是定义代码的应该如何去编写,只有有了具体的实现才能被应用:
口AMD实现的比较常用的库是require.js和curl.js;

coderwhy CMD规范
CMD规范也是应用于浏览器的一种模块化规范:
口CMD 是Common Module Definition(通用模块定义)的缩写;
口它也采用了异步加载模块,但是它将CommonJS的优点吸收了过来;
口但是目前CMD使用也非常少了;CMD也有自己比较优秀的实现方案:
口SeaJS

coderwhy认识ES Module JavaScript没有模块化一直是它的痛点,所以才会产生我们前面学习的社区规范:CommonJS、AMD,CMD等,所以在ES推出自己的模块化系统时,大家也是兴奋异常。
ES Module和CommonJS的模块化有一些不同之处:
口一方面它使用了import和export关键字;
口另一方面它采用编译期的静态分析,并且也加入了动态引用的方式;
ES Module模块采用export和import关键字来实现模块化:
export负责将模块内的内容导出;
import负责从其他模块导入内容
了解:采用ES Module将自动采用严格模式:use strict

webpack 自动加后缀 其他环境不自动

<script src='./mian.js' type='module'></script>
不能本地路径打开 要服务器打开

这个在MDN上面有给出解释:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules
你需要注意本地测试 一如果你通过本地加载Html 文件(比如一个 file://路径的文件),你将会遇到 CORS 错误,因为Javascript 模块安全性需要。
你需要通过一个服务器来测试。

export const name='koo'

const name='koo'
function foo(){}

export {
	name as fName,//导入也要别名
    foo as default //变成默认导出
}
export defualt foo

import {name as fName} from './foo.js '
import * as foo from './foo.js'

export {add,sub} from './math.js'
export * from './math.js'


import {name} from './foo.js '
会阻塞后面代码 等它下载完成 

import()异步不会  返回是个promise
import('./foo.js').then(res=>{})

ES11新增的特性
meta属性本身也是一个对象:f url:"当前模块所在的路径"}
import.meta

27 序列化-异常处理-浏览器常见API

静态分析

npm install webpack webpack-cli

包管理工具详解
npm,yarn,cnpm,npx

npmjs.com

npm init -y

coderwhy常见的属性
必须填写的属性:name,version
name是项目的名称;
version是当前项目的版本号;
description是描述信息,很多时候是作为项目的基本描述;
author是作者相关信息(发布时用到);
license是开源协议(发布时用到);
private属性:
private属性记录当前的项目是否是私有的;
当值为true时,npm是不能发布它的,这是防止私有项目或模块发布出去的方式;

coderwhy常见属性
scripts属性
scripts属性用于配置一些脚本命令,以键值对的形式存在;
配置后我们可以通过 npm run 命令的key来执行这个命令;
npm start和npm run start的区别是什么?
它们是等价的;
对于常用的 start、test、stop、restart可以省略掉run直接通过 npm start等方式运行;

dependencies属性
dependencies属性是指定无论开发环境还是生成环境都需要依赖的包;通常是我们项目实际开发用到的一些库模块vue、vuex、vue-router、react、react-dom,axios等等;与之对应的是devDependencies;
devDependencies属性
一些包在生成环境是不需要的,比如webpack,babel等;这个时候我们会通过 npm install webpack--save-dev,将它安装到devDependencies属性中;
peerDependencies属性
还有一种项目依赖关系是对等依赖,也就是你依赖的一个包,它必须是以另外一个宿主包为前提的;比如element-plus是依赖于vue3的,ant design是依赖于react、react-dom;

devDependencies --save-dev -D

coderwhy依赖的版本管理
我们会发现安装的依赖版本出现:^2.0.3或~2.0.3,这是什么意思呢?
npm的包通常需要遵从semver版本规范:semver:https://semver.org/lang/zh-CN/
npm semver:https://docs.npmjs.com/misc/semver

semver版本规范是X.Y.Z:
口X主版本号(major):当你做了不兼容的 API修改(可能不兼容之前的版本);
口Y次版本号(minor):当你做了向下兼容的功能性新增(新功能增加,但是兼容之前的版本);
口Z修订号(patch):当你做了向下兼容的问题修正(没有新功能,修复了之前版本的bug);

我们这里解释一下 ^和~的区别:
^x.y.z:表示x是保持不变的,y和z永远安装最新的版本;
~x.y.z:表示x和y保持不变的,z永远安装最新的版本;

package-lock.json 锁 小版本

28 手写防抖函数实现

coderwhy常见属性
engines属性
口engines属性用于指定Node和NPM的版本号;
口在安装的过程中,会先检查对应的引擎版本,如果不符合就会报错;
口事实上也可以指定所在的操作系统"os":["darwin","linux"],只是很少用到;

browserslist属性
口用于配置打包后的JavaScript浏览器的兼容情况,参考;
口否则我们需要手动的添加polyfills来让支持某些语法;
口也就是说它是为webpack等打包工具服务的一个属性(这里不是详细讲解webpack等工具的工作原理,所以不再给出详情);

caniuse

-S -D

npm get cache

docs.npmjs.com/cli/v7/commands

coderwhy npm其他命令
我们这里再介绍几个比较常用的:
卸载某个依赖包:
npm uninstall package
npm uninstall package --save-dev
npm uninstall package -D
强制重新build
npm rebuild
清除缓存
npm cache clean
npm的命令其实是非常多的:
https://docs.npmjs.com/cli-documentation/cli
更多的命令,可以根据需要查阅官方文档

容易读文档 vue

难读文档 node webpack

yarn init

cnpm

npx 优先在当前node_module下查找

npm login

29 写节流函数实现

目前JSON被使用的场景也越来越多:
网络数据的传输JSON数据;项目的某些配置文件;非关系型数据库(NoSQL)将json作为存储格式;

其他的传输格式:
口XML:在早期的网络传输中主要是使用XML来进行数据交换的,但是这种格式在解析、传输等各方面都弱于JSON,所以目前已经很少在被使用了;
口Protobuf:另外一个在网络传输中目前已经越来越多使用的传输格式是protobuf,但是直到2021年的3.x版本才支持JavaScript,所以目前在前端使用的较少;

coderwhyJSON基本语法
JSON的顶层支持三种类型的值:
口简单值:数字(Number)、字符串(String,不支持单引号)、布尔类型(Boolean)、null类型;
口对象值:由key、value组成,key是字符串类型,并且必须添加双引号,值可以是简单值、对象值、数组值;
口数组值:数组的值可以是简单值、对象值、数组值;

const js1=JSON.stringify(obj,['name'])
const js1=JSON.stringify(obj,(key,value)=>{
    if(key==='age'){
        return value+1
    }
    return value
})
null默认
const js1=JSON.stringify(obj,null,2)//第三参数 数字默认空格 字符串做标识

const obj={
    toJSONJ(){
        return '12345'
    }
}
如果有toJSONJ方法 则用toJSONJ返回值

JSON.parse(jsonstring,(key,value)=>{
    if(key==='age'){
        return value-1
    }
    return value
})

undefined、函数以及 symbol 值,在序列化过程中会被忽略

coderwhy认识Storage WebStorage主要提供了一种机制,可以让浏览器提供一种比cookie更直观的key,value存储方式:了
localStorage:本地存储,提供的是一种永久性的存储方法,在关闭掉网页重新打开时,存储的内容依然保留;
sessionStorage:会话存储,提供的是本次会话的存储,在关闭掉会话时,存储的内容会被清除;

coderwhy localStorage和sessionStorage的区别
我们会发现localStorage和sessionStorage看起来非常的相似。
那么它们有什么区别呢?
口验证一:关闭网页后重新打开,localStorage会保留,而sessionStorage会被删除;
口验证二:在页面内实现跳转,localStorage会保留,sessionStorage也会保留;
口验证三:在页面外实现跳转(打开新的网页),localStorage会保留,sessionStorage不会被保留;

coderwhy Storage常见的方法和属性
Storage有如下的属性和方法:
属性:
口Storage.length:只读属性
返回一个整数,表示存储在Storage对象中的数据项数量;
方法:
口Storage.key0:该方法接受一个数值n作为参数,返回存储中的第n个key名称;
口Storage.getItem(:该方法接受一个key作为参数,并且返回key对应的value;
口Storage.setItem():该方法接受一个key和value,并且将会把key和value添加到存储中。
如果key存储,则更新其对应的值;
口Storage.removeltem(:该方法接受一个key作为参数,并把该key从存储中删除;
口Storage.clear():该方法的作用是清空存储中的所有key;

Storage的工具类的封装

对数据库进行操作的时候一个操作单元

coderwhy认识IndexDB什么是IndexDB呢?
我们能看到DB这个词,就说明它其实是一种数据库(Database),通常情况下在服务器端比较常见;在实际的开发中,大量的数据都是存储在数据库的,客户端主要是请求这些数据并且展示;有时候我们可能会存储一些简单的数据到本地(浏览器中),比如token、用户名、密码、用户信息等,比较少存储大量的数据;口那么如果确实有大量的数据需要存储,这个时候可以选择使用IndexDB;

IndexDB是一种底层的API,用于在客户端存储大量的结构化数据。
它是一种事务型数据库系统,是一种基于JavaScript面向对象数据库,有点类似于NoSQL(非关系型数据库);口IndexDB本身就是基于事务的,我们只需要指定数据库模式,打开与数据库的连接,然后检索和更新一系列事务即可;

indexedDB

coderwhy IndexedDB的数据库操作
我们对数据库的操作要通过事务对象来完成:
第一步:通过db获取对应存储的事务 db.transaction(存储名称,可写操作);
第二步:通过事务获取对应的存储对象 transaction.objectStore(存储名称)
接下来我们就可以进行增删改查操作了:
新增数据 store.add
查询数据
方式一:store.get(key)
方式二:通过 store.openCursor 拿到游标对象
在request.onsuccess中获取cursor:event.target.result
获取对应的key:cursor.key;.
获取对应的value:cursor.value;
可以通过cursor.continue来继续执行;
修改数据 cursor.update(value)
删除数据 cursor.delete()

coderwhy IndexDB的连接数据库
第一步:打开indexDB的某一个数据库;通过indexDB.open(数据库名称,数据库版本)方法;如果数据库不存在,那么会创建这个数据;口如果数据库已经存在,那么会打开这个数据库;

switch 没有独立作用域

30 手写深拷贝函数实现

coderwhy认识Cookie Cookie(复数形态Cookies),又称为"小甜饼"。类型为"小型文本文件,某些网站为了辨别用户身份而存储在用户本地终端
(Client Side)上的数据。
浏览器会在特定的情况下携带上cookie来发送请求,我们可以通过cookie来获取一些信息;

Cookie总是保存在客户端中,按在客户端中的存储位置,Cookie可以分为内存Cookie和硬盘Cookie内存Cookie由浏览器维护,保存在内存中,浏览器关闭时Cookie就会消失,其存在时间是短暂的;硬盘Cookie保存在硬盘中,有一个过期时间,用户手动清理或者过期时间到时,才会被清理;

如果判断一个cookie是内存cookie还是硬盘cookie呢?
口没有设置过期时间,默认情况下cookie是内存cookie,在关闭浏览器时会自动删除;口有设置过期时间,并且过期时间不为0或者负数的cookie,是硬盘cookie,需要手动或者到期时,才会删除;

coderwhy cookie常见的属性
cookie的生命周期:
默认情况下的cookie是内存cookie,也称之为会话cookie,也就是在浏览器关闭时会自动被删除;我们可以通过设置expires或者max-age来设置过期的时间;expires:设置的是Date.toUTCString(),设置格式是;expires=date-in-GMTString-format;max-age:设置过期的秒钟,;max-age=max-age-in-seconds(例如一年为606024*365);

cookie的作用域:(允许cookie发送给哪些URL)
Domain:指定哪些主机可以接受cookie如果不指定,那么默认是 origin,不包括子域名。
如果指定Domain,则包含子域名。例如,如果设置Domain=mozilla.org,则Cookie也包含在子域名中(如developer.mozilla.org)。

Path:指定主机下哪些路径可以接受cookie例如,设置 Path=/docs,则以下地址都会匹配:
/docs
/docs/Web/
/docs/Web/HTTP

1.将cookie附加到每一次的http请求中,/home/category
2.明文传输 headers
3.大小限制:4kb
4.cookie验证登录
客户端cookie(浏览器,iOS swift、Android、小程序)->服务器
token-> 对称加密(非对称)

高屋建瓴 高瓴资本

coderwhy认识BOM JavaScript有一个非常重要的运行环境就是浏览器,而且浏览器本身又作为一个应用程序需要对其本身进行操作,所以通常浏览器会有对应的对象模型(BOM,Browser Object Model)。
我们可以将BOM看成是连接JavaScript脚本与浏览器窗口的桥梁。

BOM主要包括一下的对象模型
window:包括全局属性、方法,控制浏览器窗口相关的属性、方法;
location:浏览器连接到的对象的位置(URL);
history:操作浏览器的历史;
document:当前窗口操作文档的对象

window对象在浏览器中有两个身份:
口 身份一:全局对象。
我们知道ECMAScript其实是有一个全局对象的,这个全局对象在Node中是global;在浏览器中就是window对象;
口 身份二:浏览器窗口对象。
作为浏览器窗口时,提供了对浏览器操作的相关的API;

http://developer.mozilla.org/zh-CN/docs/Web/API/Window

查看MDN文档时,我们会发现有很多不同的符号,这里我解释一下是什么意思:
口删除符号:表示这个API已经废弃,不推荐继续使用了;
口点踩符号:表示这个API不属于W3C规范,某些浏览器有实现(所以兼容性的问题);
口实验符号:该API是实验性特性,以后可能会修改,并且存在兼容性问题;

window.addEventLIstener

window.dispatchEvent派发事件

developer.mozilla.org/zh-CN/docs/Web/Events

coderwhy history对象常见属性和方法history对象允许我们访问浏览器曾经的会话历史记录。
有两个属性:
1length:会话中的记录条数;state:当前保留的状态值;

有五个方法:
口back():返回上一页,等价于history.go(-1);
口forward():前进下一页,等价于history.go(1);
Ogo():加载历史中的某一页;
口pushState():打开一个指定的地址;I
replaceState():打开一个新的地址,并且使用replace;

31 DOM操作架构-浏览器事件

iterator 迭代器 i特雷特
generator 生成器 杰呢雷特

names[Symbol.Iterator]()

const names=['abc','cba','nba']

let index=0
const namesIterator={
    next:function(){
        if(index<names.length){
            return {done:false,value:names[index++]}
        }else{
            return {done:true,value:undefined}
        }
    }
}

console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())


function createArrayIterator(arr){
    let index=0
    return {
        next:function(){
            if(index<arr.length){
                return {done:false,value:arr[index++]}
            }else{
                return {done:true,value:undefined}
            }
        }
    }
}

www.w3.org

//创建一个迭代器对象来访问数组
const iterableObj={
    names:['abc','cba','nba'],
    [Symbol.iterator]:function(){
        let index=0
        return {
            next:()=>{
                if(index<this.names.length){
                    return {done:false,value:this.names[index++]}
                }else{
                    return {done:true,value:undefined}
                }
            }
        }
    }
}
//iterableObj对象就是一个可迭代对象
// const iterator=iterableObj[Symbol.iterator]()
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())

for(let item of iterableObj){
    console.log(item)
}

coderwhy原生迭代器对象
事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:
口String、Array、Map、Set、arguments对象、NodeList集合;

对象不可以

//案例:创建一个教室类,创建出来的对象都是可迭代对象
class Classroom{
    constructor(address,name,students){
        this.address=address
        this.name=name
        this.students=students
    }
    entry(newStudent){
        this.students.push(newStudent)
    }
    // *[Symbol.iterator](){
    //     yield* this.students
    // }
    [Symbol.iterator](){
        let index=0
        return {
            next:()=>{
                if(index<this.students.length){
                    return {done:false,value:this.students[index++]}
                }else{
                    return {done:true,value:undefined}
                }
            },
            return:()=>{
                console.log('迭代器提前终止了~')
                return {done:true,value:undefined}
            }
        }
    }
}

const classroom=new Classroom('3幢5楼205','计算机教室',['james','kobe','curry','why'])
classroom.entry('lilei')

for(let stu of classroom){
    console.log(stu)
    if(stu==='why')break
}

function Person(){}
Person.prototype[Symbol.iterator]=function(){}

coderwhy什么是生成器?
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。
生成器函数也是一个函数,但是和普通的函数有一些区别:
口首先,生成器函数需要在function的后面加一个符号:*
口其次,生成器函数可以通过yield关键字来控制函数的执行流程:口最后,生成器函数的返回值是一个Generator(生成器):
生成器事实上是一种特殊的迭代器;MDN:Instead,they return a special type of iterator,called a Generator.

当遇到yield时候值暂停函数的执行
当遇到return时候生成器就停止执行

generator.next()
//那么就意味着相当于在第一段代码的后面加上return,就会提前终端生成器函数代码继续执行
generator.return(7)
generator.throw()

32 手写防抖、节流函数实现

lodash/underscore

coderwhy认识防抖debounce函数
我们用一副图来理解一下它的过程:
口当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;口当事件密集触发时,函数的触发会被频繁的推迟;口只有等待了一段时间也没有事件触发,才会真正的执行响应函数;

防抖的应用场景很多:
输入框中频繁的输入内容,搜索或者提交信息;频繁的点击按钮,触发某个事件;监听浏览器滚动事件,完成某些特定操作;用户缩放浏览器的resize事件;

coderwhy认识节流throttle函数
我们用一副图来理解一下节流的过程
口当事件触发时,会执行这个事件的响应函数;口如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数;口不管在这个中间有多少次触发这个事件,执行函数的频繁总是固定的;

节流的应用场景:
监听页面的滚动事件;鼠标移动事件;用户频繁点击按钮操作;游戏中的一些设计;

防抖延迟 节流固定频率触发

coderwhy Underscore库的介绍
事实上我们可以通过一些第三方库来实现防抖操作:
lodash underscore
这里使用underscore
口我们可以理解成lodash是underscore的升级版,它更重量级,功能也更多;
口但是目前我看到underscore还在维护,lodash已经很久没有更新了;

https://underscorejs.org

function debounce(fn,delay){
    let timer=null
    return function(...args){
        if(timer)clearTimeout(timer)
        timer=setTimeout(() => {
            fn.apply(this,args)
        }, delay);
    }
}
function debounce(fn,delay,immediate=false){
    let timer=null
    let isInvoke=false
    const _debounce = function(...args){
        if(timer)clearTimeout(timer)
        //判断是否需要立即执行
        if(immediate&& !isInvoke){
            fn.apply(this,args)
            isInvoke=true
        }else{
            timer=setTimeout(() => {
                fn.apply(this,args)
                isInvoke=false
                timer=null
            }, delay);
        }
        
    }

    //封装取消功能
    _debounce.cencel=function(){
        if(timer)clearTimeout(timer)
        timer=null
        isInvoke=false
    }
    return _debounce
}
function debounce(fn,delay,immediate=false){
    let timer=null
    let isInvoke=false
    const _debounce = function(...args){
        return new Promise((resolve,reject)=>{
            if(timer)clearTimeout(timer)
            //判断是否需要立即执行
            if(immediate&& !isInvoke){
                const result=fn.apply(this,args)
                resolve(result)
                isInvoke=true
            }else{
                timer=setTimeout(() => {
                    const result=fn.apply(this,args)
                    resolve(result)
                    isInvoke=false
                    timer=null
                }, delay);
            }
        })
    }

    //封装取消功能
    _debounce.cencel=function(){
        if(timer)clearTimeout(timer)
        timer=null
        isInvoke=false
    }
    return _debounce
}

防抖就像坐电梯,节流就像等红绿灯

节流

节流 基本实现
function throttle(fn,interval){
    //1.记录上一次的开始时间
    let lastTime=0
    //2.事件触发时,真正执行的函数
    const _throttle=function(){
        //2.1.获取当前事件触发时的时间
        const nowTime=new Date().getTime()
        //2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间,计算出还剩余多长事件需要去触发函数
        const remainTime=interval-(nowTime-lastTime)
        if(remainTime<=0){
            //2.3.真正触发函数
            fn()
            //2.4.保留上次触发的时间
            lastTime=nowTime
        }
    }
    return _throttle
}

33 手写节流、深拷贝函数实现

coderwhy
自定义防抖和节流函数
我们按照如下思路来实现:
防抖基本功能实现:可以实现防抖效果
优化一:优化参数和this指向
优化二:优化取消操作(增加取消功能)
优化三:优化立即执行效果(第一次立即执行)
优化四:优化返回值
我们按照如下思路来实现:
口节流函数的基本实现:可以实现节流效果
口优化一:节流最后一次也可以执行
口优化二:优化添加取消功能
口优化三:优化返回值问题

00:35:39

function throttle(fn,interval,options={leading:true,trailing:false}){
    const {leading,trailing,resultCallback}=options
    //1.记录上一次的开始时间
    let lastTime=0
    let timer=null
    //2.事件触发时,真正执行的函数
    const _throttle=function(...args){
        //2.1.获取当前事件触发时的时间
        const nowTime=new Date().getTime()
        if(!lastTime&&!leading){
            lastTime=nowTime
        }
        //2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间,计算出还剩余多长事件需要去触发函数
        const remainTime=interval-(nowTime-lastTime)
        if(remainTime<=0){
            if(timer){
                clearTimeout(timer)
                timer=null
            }
            //2.3.真正触发函数
            const result=fn.apply(this,args)
            if(resultCallback)resultCallback(result)
            //2.4.保留上次触发的时间
            lastTime=nowTime
            return
        }
        if(trailing&&!timer){
            timer=setTimeout(() => {
                timer=null
                lastTime=!leading?0:new Date().getTime()
                const result=fn.apply(this,args)
                if(resultCallback)resultCallback(result)
            }, remainTime);
        }
    }
    _throttle.cancel=function(){
        if(timer)clearTimeout(timer)
        timer=null
        lastTime=0
    }
    return _throttle
}

coderwhy自定义深拷贝函数
前面我们已经学习了对象相互赋值的一些关系,分别包括:
口引入的赋值:指向同一个对象,相互之间会影响;口对象的浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响;口对象的深拷贝:两个对象不再有任何关系,不会相互影响
前面我们已经可以通过一种方法来实现深拷贝了:JSON.parse口这种深拷贝的方式其实对于函数、Symbol等是无法处理的;口并且如果存在对象的循环引用,也会报错的;
自定义深拷贝函数:
01.自定义深拷贝的基本功能;
□2.对Symbol的key进行处理;
03.其他数据类型的值进程处理:数组、函数、Symbol、Set、Map;
□4.对循环引用的处理;

深拷贝基本实现
function isObject(value){
    const valueType=typeof value
    return (value!==null)&&(valueType==='object'||valueType==='function')
}
function deepClone(originValue){
    //判断传入的originValue是否是一个对象类型
    if(!isObject(originValue)){
        return originValue
    }
    const newObject={}
    for(const key in originValue){
        newObject[key]=deepClone(originValue[key])
    }
    return newObject
}

const obj={
    name:'koo',
    friend:{
        name:'john'
    }
}

const newObj=deepClone(obj)
console.log(obj.friend===newObj.friend)

02:17:06

完整
function isObject(value){
    const valueType=typeof value
    return (value!==null)&&(valueType==='object'||valueType==='function')
}
const map=new Map()
function deepClone(originValue,map=new WeakMap()){
    //判断是否是一个Set类型
    if(originValue instanceof Set){
        return new Set([...originValue])
    }
    //判断是否是一个Map类型
    if(originValue instanceof Map){
        return new Map([...originValue])
    }
    //判断如果是Symbol的value,那么创建一个新的Symbol
    if(typeof originValue ==='symbol'){
        return Symbol(originValue.description)
    }
    //判断如果是函数类型。那么直接使用同一个函数
    if(typeof originValue ==='function'){
        return originValue
    }
    //判断传入的originValue是否是一个对象类型
    if(!isObject(originValue)){
        return originValue
    }
	//解结循环引用
    if(map.has(originValue)){
        return map.get(originValue)
    }

    //判断传入的对象是数组,还是对象
    const newObject=Array.isArray(originValue)?[]:{}
    //解结循环引用
    map.set(originValue,newObject)

    for(const key in originValue){
        newObject[key]=deepClone(originValue[key],map)
    }
    //对Symbol的key进行特殊的处理
    const symbolKeys=Object.getOwnPropertySymbols(originValue)
    for(const sKey of symbolKeys){
        // const newSKey=Symbol(sKey.description)
        // newObject[newSKey]=deepClone(originValue[sKey])
        newObject[sKey]=deepClone(originValue[sKey],map)
    }

    return newObject
}

const s1=Symbol('aaa')
const s2=Symbol('bbb')
const obj={
    name:'koo',
    friend:{
        name:'john'
    },
    arr:['abc','bac'],
    foo:function(){},
    [s1]:'aaa',
    s2:s2,
    set:new Set(['aaa','bbb','ccc']),
    map:new Map([['aaa','abc'],['bbb','cba']])
}
//解结循环引用
obj.info=obj

const newObj=deepClone(obj)
console.log(newObj)
console.log(obj.friend===newObj.friend)

mitt 自定义事件总线 event-bus

http://github.com/coderwhy?tab=repositories

coderwhy自定义事件总线
自定义事件总线属于一种观察者模式,其中包括三个角色:口发布者(Publisher):发出事件(Event);口订阅者(Subscriber):订阅事件(Event),并且会进行响应(Handler);口事件总线(EventBus):无论是发布者还是订阅者都是通过事件总线作为中台的;

当然我们可以选择一些第三方的库:
口Vue2默认是带有事件总线的功能;口Vue3中推荐一些第三方库,比如mitt;

当然我们也可以实现自己的事件总线:口事件的监听方法on;口事件的发射方法emit;口事件的取消监听off;

class EventBus{
    constructor(){
        this.eventBus={}
    }
    on(eventName,eventCallback,thisArg){
        let handlers=this.eventBus[eventName]
        if(!handlers){
            handlers=[]
            this.eventBus[eventName]=handlers
        }
        handlers.push({
            eventCallback,
            thisArg
        })
    }
    off(eventName,eventCallback){
        const handlers=this.eventBus[eventName]
        if(!handlers)return
        const newHandlers=[...handlers]
        for(let i=0;i<newHandlers.length;i++){
            const handler=newHandlers[i]
            if(handler.eventCallback===eventCallback){
                const index=handlers.indexOf(handler)
                handlers.splice(index,1)
            }
        }
    }
    emit(eventName,...payload){
        const handlers=this.eventBus[eventName]
        if(!handlers)return
        handlers.forEach(handler => {
            handler.eventCallback.apply(handler.thisArg,payload)
        });
    }
}
const eventBus=new EventBus()

//main.js
eventBus.on('abc',function(){
    console.log('watch abc1',this)
},{name:'koo'})
const handleCallback=function(){
    console.log('watch abc2',this)
}
eventBus.on('abc',handleCallback,{name:'koo'})

//utils.js
eventBus.emit('abc',123)

//移除监听
eventBus.off('abc',handleCallback)
eventBus.emit('abc',123)

标签:Jacascript,coderwhy,const,函数,对象,直播,console,CoderWhy,属性
From: https://www.cnblogs.com/KooTeam/p/18600697

相关文章

  • 国标GB28181网页直播平台LiteGBS安防FAQ:摄像机在LED环境下图画有暗横条纹怎么办?
    随着视频技术的不断进步,视频监控、直播、执法记录仪等多种视频资源的应用场景愈发广泛且多样化。这些视频资源不仅在数量上快速增长,更在质量、格式及编码标准等方面展现出极高的多样性。因此,为了实现对这些资源的有效整合和统一管理输出,信息化项目中对于视频综合接入能力的需求愈......
  • 【BUN】bun搭配 WebRTC 实现一个直播平台
    前言:近日。学习BUN中,突发奇想,如何实现一个直播平台?0.BUN的安装安装BUN1.初始化项目buninit2.实现serve信令服务器index.tsimportBunfrom'bun';importtype{ServerWebSocket}from'bun';typeMessageKeys='join'|'create'|'offe......
  • H.265流媒体播放器EasyPlayer.js网页直播播放器,如何在vue3中播放H.265视频流?
    在Vue3中使用EasyPlayer播放H.265视频流,你需要先确保你已经安装了EasyPlayer播放器库,以及相关的依赖和支持的H.265编解码器。以下是一个基本的示例,说明如何在Vue3应用中集成EasyPlayer并播放H.265视频流。步骤1:安装依赖确保你的项目中已经安装了EasyPlayer,你可以通过npm或ya......
  • H5流媒体播放器EasyPlayer.js无插件H5播放器如何在iOS上如何实现低延时直播?
    随着流媒体技术的迅速发展,H5流媒体播放器已成为现代网络视频播放的重要工具。其中,EasyPlayer.js播放器作为一款功能强大的H5播放器,凭借其全面的协议支持、多种解码方式以及跨平台兼容性,赢得了广泛的关注和应用。那么要在iOS上实现低延时直播,EasyPlayer.js视频流媒体播放器提供了......
  • 国标GB28181网页直播平台LiteGBS国标GB28181软件关于设备离线如何处理?
    GB28181协议作为国家标准,规定了公共安全视频监控联网系统的互联结构、传输、交换、控制的基本要求和安全性要求。这有助于实现不同设备和系统之间的互联互通和信息共享,提高公共安全视频监控的效率和质量。LiteGBS国标GB28181软件平台,作为视频监控领域的核心管理系统,其稳定性和可......
  • 国标GB28181公网直播LiteGBS国标GB28181-2022平台网络摄像机怎么进行时间同步?
    随着视频技术的不断进步,视频监控、直播、执法记录仪等多种视频资源的应用场景愈发广泛且多样化。这些视频资源不仅在数量上快速增长,更在质量、格式及编码标准等方面展现出极高的多样性。因此,为了实现对这些资源的有效整合和统一管理输出,信息化项目中对于视频综合接入能力的需求愈......
  • 在Vue3中如何使用H.265流媒体播放器EasyPlayer.js网页直播/点播播放器?
    随着技术的发展,越来越多的H5流媒体播放器开始支持H.265编码格式。例如,EasyPlayer.js播放器能够支持H.264、H.265等多种音视频编码格式,这使得播放器能够适应不同的视频内容和网络环境。在Vue3中如何使用EasyPlayer.js播放器?具体流程如下:1)首先通过npm引入easyplayer.js;npminst......
  • FloEFD单管道水冷板热设计仿真分析与VIP直播答疑
     ......
  • 直播间氛围提升工具:音效助手
    概述在直播领域,创造一个活跃和吸引人的氛围是至关重要的。今天,我们将介绍一款专为直播场景设计的音效辅助工具——音效助手。这款工具以其丰富的音效库和便捷的操作界面,成为了许多主播的得力助手。视频演示功能介绍音效库音效助手拥有一个庞大的音效库,涵盖了各种流行......
  • 无插件H5播放器EasyPlayer.js网页直播/点播播放器应该怎么使用JavaScript初始化?
    JavaScript可以用来控制播放器的基本功能,如播放、暂停、停止、快进、快退等。通过监听播放器的事件,JavaScript可以响应用户的操作,实现交互式控制。使用JavaScript,开发者可以创建自定义的播放器界面,而不是使用浏览器默认的控件。这可以通过操作DOM来实现,比如显示播放进度条、音量控......