最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • [复习笔记-03] 03

    正文概述 掘金(woow_wu7)   2021-01-02   426

    (一) 前置知识

    (1) 一些单词

    identifier:标识符
    locator:定位
    anchor: 锚
    parasitic:寄生
    
    manual:手动的
    automatic:自动的
    specifier:标志语,指示语
    several:几个,一些
    composition:组成,组合
    
    crushed:压坏,压碎
    shallow:浅的
    

    (2) URL 和 URI

    • URL -> Locator -> 统一资源定位符 -> 强调位置
    • URI -> Identifier -> 统一资源标识符 -> 强调标识,具有唯一性

    (3) URL的组成

    http://www.baidu.com:80/stu/index.html?name=xxx&age=25#teacher

    • Protocal: 协议 http://, https://
    • Domain: 域名 www.baidu.com
    • Port: 端口 :80
      • http协议默认端口 :80
      • https协议默认端口 :443
    • Path: 文件路径 /开头, ?之前的部分 , => /stu/index.html
    • Query: 查询字符串 ?开头到结尾, 或者?开头到#之前,=> ?name=xxx&age=25
    • Hash: 哈希值 #开头到结尾 => #teacher
    • 总结:protocal, domain, port, path, query, hash

    (4) DOMContentLoaded事件,load事件

    • window.onload === window.addEventListener('load', listener, ...)
    • 两者的却别
      • DOMConentLoaded: ( DOM加载完成时触发 )
      • load: DOM,样式,脚本,图片,视频等所有资源全部加载完成时才会触发,即 ( 整个页面加载完成时触发 )

    (5) window.location

    window.location对象
    
    属性:
    pathname: 返回url的path部分,( /开始 ?之前 ) 或者 ( /开始到结尾 ),如果没有query和hash
    origin:protocal + hostname + port 三者之和,相当于协议,域名,端口
    protocal:协议 http:// https://
    hostnme: 主机名
    port:端口号
    host:主机 (hostname + port)
    search:查询字符串 (?开头到#之前,或者?开头到结尾)
    hash:片段字符串 (哈希值,#开头到结尾)
    

    (6) splice(start, count, addElement1, addElement2, ...)

    • 功能:splice方法用于删除原数组的一部分成员,可以在删除的位置添加新的数组成员
    • 返回值
      • 被删除的元素组成的数组
      • 返回值是一个数组
      • 如果删除0个成员,还添加新成员,返回值还是一个空数组,和添不添加无关
    • 参数
      • start:
        • 删除的起始位置,默认从0开始
        • 如果起始位置是负数,表示从倒数位置开始删
        • 如果是添加成员,则会添加到start位置的前面
        • 如果splice只提供一个参数,等于将原数组在指定位置拆分成两个数组
      • count: 被删除的元素个数
      • 后面的参数
        • 是要添加到数组的新元素
        • 添加的成员会放在start位置的前面
    • 改变原数组
    const a = [1,2,3,4]
    const b = a.splice(0)
    
    a // []
    b // [1,2,3,4]
    因为splice()改变原数组,a就会变成空数组
    

    (二) 前端路由

    (1) Hash路由

    • url中的hash以#号开头,原本用来作为锚点,从而定位到页面的特定区域
    • 当 hash 发生改变时,页面不会刷新,浏览器也不会向服务器发送请求
    • 注意:hash改变时,可以触发 hashchange 事件,在监听函数中可以请求数据,实现页面的更新操作

    (1) 作为锚点,定位页面的特定位置

    <a href="#anchor1">锚点1</a>
    <a href="#anchor2">锚点2</a>
    
    <div id="anchor1">锚点1的位置</div>
    <div id="anchor2">锚点2的位置</div>
    
    说明:
    - 点击a2,页面会跳转到div2的位置
    - 并且页面的hash部分也会改变,即 url 中以 #开头的字符串会改变
    - anchor:是锚的意思
    
    - 注意:a标签的name属性已经废弃,用id代替 (因为有的教程使用name属性实现的)
    

    (2) hashchange事件

    • 如果监听了 ( hashchange ) 事件,hash改变,( 地址栏的hash部分也会改变 ),同时hashchange也会触发
    • 但是 ( 浏览器不会刷新 ),即浏览器的刷新按钮的 ( 圈圈不会转动 )
    • 但是可以利用hashchange的回调函数更新页面内容,注意不是刷新页面
    <body>
      <a href="#anchor1">锚点1</a>
      <a href="#anchor2">锚点2</a>
      <script>
        window.addEventListener('hashchange', function() {
          console.log('111111111')
        }, false)
      </script>
    </body>
    
    说明:
    - 点击a标签,url中的hash改变,hash改变,hashchange事件触发,则监听函数就会执行,输出111111
    

    (3) 手动实现一个hash路由

    • 原理
      • ( hash改变 ) 时,地址栏的hash会变化,同时触发 ( hashchange ) 事件
      • 在 hashchange 事件的监听函数中去更新视图
    • 代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <a href="#/home">home页面</a>
      <a href="#/other">other页面</a>
      <div id="content">内容部分,即路由替换的部分</div>
      <div id="current-hash"></div>
      <script>
        const routes = [{
          path: '/home',
          component: 'home页面的内容'
        }, {
          path: '/other',
          component: 'other页面的内容'
        }]
    
        class HashRouter {
          constructor(routes) {
            this.route = {} // 路由映射
            this.createRouteMap(routes) // 创建路由映射,为 ( this.route ) 创建 ( map ) 映射;key=path;value=()=>{更新页面}
            this.init() // 初始化
          }
          createRouteMap = (routes) => {
            if (routes.length) {
              routes.forEach(({ path, component }) => {
                this.route[path] = () => {
                  document.getElementById('content').innerHTML = component // 替换内容
                }
              })
            }
          }
          init = () => {
            window.addEventListener('load', this.updateView, false)
            window.addEventListener('hashchange', this.updateView, false)
          }
          // 更新视图
          updateView = () => {
            // (1)
            // 这里 ( load事件 ) 和 ( hashchange事件 ) 都会触发 ( updateView方法 )
            // (2)
            // load事件: ( 页面加载完成时触发 ),包括 ( DOM,样式,图片,视频等所有资源都加载完成 )
            // DOMContentLoaded事件: 是在 ( DOM加载完成时触发 ) 
            // (3)
            // 当load事件触发时,hash并没有改变,即 window.location.hash = '' => ''.slice(1) => ''
            const hash = this.getCurrentHash() // 获取hash
            // if (Object.keys(this.route).includes(hash)) { // 还有更简单的方法
            //   this.route[hash]()
            // }
            if (this.route[hash]) this.route[hash]() // 如果this.route对象中的key对应得值存在,就执行该函数
          }
          // 获取当前地址栏的 hash
          getCurrentHash = () => {
            const hash = window.location.hash.slice(1)
            this.printHahToHtml(hash) // 该函数是用来在html中显示当前hash的
            return hash ? hash : '/home'
            // load事件触发时,hash就不存在,hash='',这种情况下即默认情况下返回 '/home' 路由
            // load事件触发时,window.location.hash => 返回 '' 空字符串
            // ''.slice(1) => 返回''
    
          }
          printHahToHtml = (hash) => {
            const DOM = document.getElementById('current-hash')
            DOM.innerHTML =  `当前页面的hash是:=> #${hash}`
            DOM.style.setProperty('background', 'yellow')
            DOM.style.setProperty('padding', '10px')
          }
        }
    
        new HashRouter(routes)
      </script>
    </body>
    </html>
    

    [复习笔记-03] 03

    (2) history路由

    三个方法 pushState() replaceState() popstate事件

    (1) window.history 对象

    • window.history对象的方法:back() forward() go() pushState() replaceState()
    • pushState()repalceState()
      • 都不会触发页面更新,只能导致history发生变化,地址栏的url会有变化
      • 都会改变url,不会触发 popstate 事件,地址栏的url会有变化

    (2) window.history.pushState(state, title, url)

    • 参数
      • state: 是一个与添加的记录相关联的对象
      • title: 新页面的标题,现在所有浏览器都忽略该参数,可以传入空字符串
      • url: 新的url地址,必须与当前页面同一个域,浏览器的地址栏显示这个网址
      • window.history.pushState({}, null, url)
        • pushState不会刷新页面,只会改变history对象,地址栏的url会变化
        • 可以通过 History.state 来读取状态

    (3) popstate

    • popstate触发的条件
      • 浏览器的前进后退按钮
      • history.go()
      • history.back()
      • history.forward()
    • window.history.pushState() 和 window.history.replaceState() 不会触发 popstate 事件
    • pushState()和replaceState()
      • 可以改变url,而且不向服务器发送请求,不存在#号,比hash美观
      • 但是需要服务器的支持,并且需要所有的路由重定向到根页面
    • 原理分析
      • 第一步:给每个a标签都绑定一个click事件,click事件触发时,再去触发pushState()事件,将url的path部分改变为a的data-href自定义属性的值即路由的path,传入window.history.pushState({}, null, path),这样地址栏的url就改变了
      • 第二步:改变地址栏的url后,通过window.history.pathname获取更新的url的path部分
      • 第三步:用 path 和 route对象中的key去匹配,匹配上就去执行更新视图的函数
      • 第四步:
        • 这只是一条线:1-3步即点击a标签的情况
        • 还有一条线:浏览器的前进后退,和函数式导航go() back() forward() 则有 popstate 事件来处理,过程差不多
    • 代码
      • 封装一个方法,在pushState()和replaceState()改变url后调用,在该方法中获取最新的window.location.pathname,更新页面
      • 通过 go() back() forward() 浏览器前进后退等触发 popstate 事件
    <!DOCTYPE html>
    <html lang="en"
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <a href="javascript:void(0)" data-href="/home">点击去home页面</a>
      <a href="javascript:void(0)" data-href="/other">点击去other页面</a>
      <div id="content">内容部分,即路由要替换的内容</div>
      <script>
        const routes = [{
          path: '/home',
          component: '<h1>home页面</h1>'
        }, {
          path: '/other',
          component: '<h1>other页面</h1>'
        }]
        class HistoryRouter {
          constructor(routes) {
            this.route = {} // 路由映射 key=path value=()=>{更新视图}
            this.createRouteMap(routes) // 创建路由映射
            this.bindEvent() // 绑定事件
            this.init() // 初始化
          }
          createRouteMap = (routes) => {
            if (routes.length) {
              routes.forEach(({ path, component }) => {
                this.route[path] = () => {
                  document.getElementById('content').innerHTML = component
                }
              })
            }
          }
          bindEvent = () => {
            const a = document.getElementsByTagName('a')
            Array.prototype.forEach.call(a, aDom => {
              aDom.addEventListener('click', () => {
                const path = aDom.getAttribute('data-href')
                this.triggerPushState(path) // 触发pushState事件
              }, false)
            })
          }
          triggerPushState = (path) => {
            window.history.pushState({}, null, path)
            // pushState() 可以改变地址栏的url,但是不会触发页面更新,所以要执行下面的更新函数
            // (1) 情况1:这只是 ( 点击a标签 ) 的情况,使用的是 pushState() 函数
            // (2) 情况2:还有就是 ( 点击浏览器的前进后退按钮 ) 和 ( 函数式k导航 window.history.go() back() forward() 的情况 )
            // (3) 情况3:就是初始化时,在 ( load ) 事件触发是的情况,默认path='/'
            this.updateView()
          }
          updateView = () => {
            // 因为:在执行该方法之前,已经触发了 pushState() || popstate事件 || load事件
            // 所以:可以用window.location.pathname 获取最新的 url中的 path 部分
            const currentPath = window.location.pathname
              ? window.location.pathname
              : '/'
    
            if (this.route[currentPath]) this.route[currentPath]()
          }
          init = () => {
            window.addEventListener('load', this.updateView, false) // 页面加载完成时的情况
            window.addEventListener('popstate', this.updateView, false) // popstate触发的情况,浏览器前进后退和函数式导航
          }
        }
    
        new HistoryRouter(routes)
      </script>
    </body>
    </html>
    
    注意:
    - history路由是需要启动服务的
    - 该html需要用 Live Server 启动,vscode插件
    

    (3) 手动实现一个vue-router (hash路由版本)

    • 大体的原理和hash路由的html版本一样,微小区别
    • 都是利用 ( a标签 ) 的 ( href ) 中的 ( '#/xxx' ) 这样的hash串
    • 只要点击 a标签 => 地址栏的url的hash部分就会改变 => 同时触发 hashchange 事件 => 通过window.location.hash获取最新的hash
    • 获取到 hash 在和 routeMap 中的path匹配,匹配后就改变视图
    • 区别
      • 区别就是 Vue 自己封装了 <router-link><router-view> 组件
      • 可以通过 Vue.component() 方法注册上面两个组件
      • 通过 vm.name 可以访问到 new Vue({data: {name: xx}})中的name
    手动实现一个vue-router(hash版)
    
    vue相关前置知识
    - <router-link to="#/home">home</router-link> // 点击会跳转到 '#/home' 地址
    - <router-view></router-view> // 路由将显示的DOM位置
    
    // 定义一个名为 button-counter 的新组件
    Vue.component('button-counter', {
      data: function () {
        return {
          count: 0
        }
      },
      template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
    })
    - 因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项
    - 例如 data、computed、watch、methods 以及生命周期钩子等。
    
    
    ---------------
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
      <!-- 引入 Vue 通过CDN引入 -->
      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    <body>
      <div id="app">
        <!-- 注意 router-link组件具有 to 属性 -->
        <router-link to="#/home">home</router-link>
        <router-link to="#/other">other</router-link>
        <router-view></router-view>
      </div>
    
      <script>
        // 创建两个vue组件
        const Home = {template: '<h1>home页面</h1>'}
        const Other = {template: '<h1>other页面</h1>'}
    
        // 创建vue路由数组
        const routes = [{
          path: '/home',
          component: Home
        }, {
          path: '/other',
          component: Other
        }]
    
        class VueRouter {
          constructor(Vue, option) {
            // 参数:
            // Vue:Vue构造函数,通过cdn引入的
            // option: 配置对象,包含routes路由数组属性
            this.$options = option
            this.routeMap = {} // 路由映射,是这样的结构 { path: component }
            this.createRouteMap(this.$options) // 创建路由映射
    
            this.app = new Vue({
              data: {
                currentHash: '#/'
              }
            })
            // this.app.currentHash => 可以访问到currentHash的值 '#/'
            // 举例
            // var data = {name: 'woow_wu7'}
            // var vm = new Vue({
            //  data: data
            // })
            // vm.name === data.name => true
    
            this.init() // 初始化监听函数
            this.initComponent(Vue) // 初始化Vue种的各种组件
          }
          createRouteMap = (option) => {
            // 注意:option 是传入VueRoute的第二个参数,即 {routes: routes}
            // 所以:options是一个对象
            option.routes.forEach(item => {
              this.routeMap[item.path] = item.component
              // this.routeMap是这样一个对象:{path: component}
            })
          }
          init = () => {
            window.addEventListener('load', this.onHashChange, false)
            // 页面加载完成触发,注意区别 DOMContentLoaded
            // load:页面加载完成时触发,包括 DOM加载完成,图片,视频等所有资源加载完成
            // DOMContentLoaded:DOM加载完成时触发
            window.addEventListener('hashchange', this.onHashChange, false)
            // 监听 hashchange 事件
            // 触发hashchange的条件:hash改变时候
          }
          onHashChange = () => {
            this.app.currentHash = window.location.hash.slice(1) || '/'
            // (1)
            // 当 hahs没有改变时,load事件触发时
            // window.location.hash = '' =>  window.location.hash.slice(1) = ''
            // 所以:此种情况:this.app.currentHash =  '/'
            // (2)
            // hash改变时,window.location.hash有值,是 '#/...' 这样的字符串
          }
          initComponent = (Vue) => {
            // router-link组件
            // props to属性
            // template 本质上会被处理成a标签,href属性是传入的 to 属性,内容是 slot 插入的内容
            Vue.component('router-link', {
              props: {
                to: {
                  type: String,
                  value: ''
                }
              },
              template: '<a :href="to"><slot/></a>'
            })
            Vue.component('router-view', {
              render: (h) => {
                const component = this.routeMap[this.app.currentHash] // 拿到最新hash对应的组件
                return h(component)
                // h(component) 相当于 createElement(component)
                // render: function(createElement) { return createElement(App); }
              }
            })
          }
        }
        new VueRouter(Vue, {
          routes
        })
        new Vue({
          el: '#app'
        })
      </script>
    </body>
    </html>
    

    (三) 继承

    (1) 原型链继承

    • 将 ( 子类的prototype ) 指向 ( 父类的实例 ),同时修改 ( 子类的constructor ) 让其重新指向子类
    • 缺点:
      • 创建子类实例时,不能向父类传参
      • 不能实现多继承 ( 继承多个父类 ),主要是因为直接给prototype属性直接赋值
      • 多个实例共享父类的属性和父类prototype上的属性,当属性是引用类型时,子类实例间修改会相互影响【特别对于数组】
      • 在子类的prototype上挂属性和方法,必须要在修改子类的prototype指向之后
      • Sub.prototype = new Super()之后,才可以挂载子类实例的原型属性 Sub.prototype.sex = 'man',不然会被新的引用替代
    原型链继承再复习
      
    1. 原理
      - 将 ( 子类的prototype属性 ) 指向 ( 父类生成的实例 ),那么 new 子类生成的实例就能继承 ( 父类 ) 和 ( 父类prototype ) 上的属性和方法
    2. 缺点
      - 不能实现多继承 ( 继承多个父类 ),因为 prototype 是直接赋值的
      - 创建子类实例时,不能向父类传参
      - 子类prototype属性挂载属性,必须是在子类prototype赋值之后
      - 多个子类实例是共享父类实例和父类实例原型上的属性和方法,修改引用类型的数据时会相互影响
    3. 代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script>
        // 原型链继承
        // 父类
        function Super(name) {
          this.superName = name
        }
        Super.prototype.superAge = 20 
        // 子类
        function Sub(name) {
          this.subName = name
        }
        // 原型链继承
        // 原理:将子类的prototype指向父类的实例,这样 ( 子类的实例 ) 就能继承 ( 父类实例 ) 和 ( 父类实例原型上 ) 的属性和方法
        Sub.prototype = new Super('super')
        // 修改prototpe属性后,一定要同时修改constructor的指向,防止引用出错
        Sub.prototype.contructor = Sub
        Sub.prototype.subAge = 10 // 缺点:挂载属性必须在prototype赋值之后
        const sub = new Sub('sub') // 缺点:只能向子类传参,不能向父类传参
    
        console.log('sub.subName', sub.subName)
        console.log('sub.subAge', sub.subAge)
        console.log('sub.superName', sub.superName)
        console.log('sub.superAge', sub.superAge)
      </script>
    </body>
    </html>
    

    (2) 借用构造函数继承 ( 经典继承 )

    • 原理
      • 在 ( 子类 ) 中通过 call() 方法将 ( 父类中的this绑定为子类的this ),并执行父类的构造函数,即把父类的this换成子类的this
    • 优点
      • 可以实现多继承,即在子类中call多个父类
      • 生成子类实例时,可以向父类传参
      • 属性和方法都是生成在子类实现上的,每个实例独享,修改属性不会相互影响
    • 缺点
      • 子类实例不能继承父类prototype原型链上的属性和方法,因为只是利用了构造函数,而没有通过new命令调用
      • 属性和方法生成在子类上,相互之间不共享,造成资源浪费
    • 代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script>
        //  借用构造函数继承
        function Super1(name) { // 父类1
          this.superName1 = name
        }
        function Super2(name) { // 父类2
          this.superName2 = name
        }
        Super2.prototype.superAge2 = 20 // 缺点:不能继承父类prototype属性原型链上的属性和方法
        
        function Sub(superName1, superName2, subName) { // 子类
          Super1.call(this, superName1) // 借用构造函数继承
          Super2.call(this, superName2) // 优点:可以实现多继承
          this.subName = subName
        }
        const sub = new Sub('super1', 'super2', 'sub') // 优点:生成子类实例时,可以向父类传参
        console.log('sub.superName1', sub.superName1)
        console.log('sub.superName2', sub.superName2)
        console.log('sub.subName', sub.subName)
      </script>
    </body>
    </html>
    

    (3) 组合式继承 ( 原型链继承 + 借用构造函数继承 )

    • 原理:原型链继承 + 借用构造函数继承
    • 优点
      • 既具有借用构造函数继承的优点(向父类传参,多继承,不存在属性共享
      • 又具有原型链继承的优点(继承父类实例原型链上的属性和方法,并且是共享)
    • 缺点
      • 会调用两次父构造函数,导致 (子类实例-即借用构造函数继承 ) 和 ( 子类实例的原型链上-即原型链继承 ) 上都有相同的属性和方法
        • 本例中:子类实例上有 superName1 属性;子类实例的原型链上也有 superName1 属性
      • 父类被调用了两次,一次是借用构造函数是的call调用,一次是原型链继承时的new调用
      • 因为父类两次调用,所以子类和父类实例原型链上有相同的属性和方法,造成浪费
    • 代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script>
        // 组合式继承 = 借用构造函数继承 + 原型链式继承
        // 优点:两者组合,相互补充
        // 缺点:
        // 1. 会调用两次父构造函数,导致 (子类实例-即借用构造函数继承 ) 和 ( 子类实例的原型链上-即原型链继承 ) 上都有相同的属性和方法
        //    - 本例中:子类实例上有 superName1 属性;子类实例的原型链上也有 superName1 属性
        // 2. 父类被调用了两次,一次是借用构造函数是的call调用,一次是原型链继承时的new调用
        // 3. 因为父类两次调用,所以子类和父类实例原型链上有相同的属性和方法,造成浪费
        function Super1(name) {
          this.superName1 = name
        }
        function Super2(name) {
          this.superName2 = name
        }
        Super1.prototype.superAge1 = 10
        Super2.prototype.superAge2 = 20
    
        function Sub(superName1, superName2, subName) {
          // 借用构造函数继承
          // 优点:可以向父构造函数传参,多继承,属性不共享
          // 缺点:不能继承父类prototype对象原型链上的属性和方法
          Super1.call(this, superName1)
          Super2.call(this, superName2)
          this.subName = subName
        }
        // 原型链继承
        // 优点:可以继承父类实例原型链上的属性和方法,共享属性
        // 缺点:在生成子类实例时不能向父类传传参,不能实现多继承,继承的属性是引用类型时,子类实例之间修改会相互影响
        Sub.prototype = new Super1()
        Sub.prototype.constructor = Sub
        Sub.prototype.subAge = 30
    
        const sub = new Sub('super1', 'super2', 'sub')
        console.log('sub', sub)
        console.log('sub.superName1', sub.superName1)
        console.log('sub.superName2', sub.superName2)
        console.log('sub.subName', sub.subName)
        console.log('sub.superAge1', sub.superAge1)
        console.log('sub.subAge', sub.subAge)
      </script>
    </body>
    </html>
    

    [复习笔记-03] 03

    (4) 继承组合式继承

    • 要解决的问题:在组合继承中两次调用父构造函数的问题
    • 主要解决:
      • 组合式继承中,父类被多次调用,导致子类实例属性和子类实例原型链上有相同的属性的问题
      • 因为父类两次被调用,call和new,构造函数中的属性会两次生成,造成资源的浪费
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script>
        // 寄生组合式继承
        function Super1(name) {
          this.superName1 = name
        }
        function Super2(name) {
          this.superName2 = name
        }
        Super1.prototype.superAge1 = 20
        function Sub(superName1, superName2, name) {
          Super1.call(this, superName1)
          Super2.call(this, superName2)
          this.subName = name
        }
        function Parasitic() { } // 中间函数,本身没有任何属性和方法
        Parasitic.prototype = Super1.prototype
        // 这样 sub 实例就能继承 Super1.prototype上的属性和方法,而这条继承线不用在继承 super1 实例上的方法
        Sub.prototype = new Parasitic()
        Sub.prototype.constructor = Sub
        Sub.prototype.subAge = 30
        const sub = new Sub('super1', 'super2', 'sub')
        console.log('sub', sub)
      </script>
    </body>
    </html>
    

    [复习笔记-03] 03

    (四) 观察者模式 和 发布订阅模式

    (1) 观察者模式

    • 对程序中某个对象进行观察,并在发生改变时得到通知
    • 存在 ( 观察者对象 ) 和 ( 目标对象 ) 两种角色
    • 目标对象:subject
    • 观察者对象:observer

    在观察者模式中,subject和observer相互独立又相互联系

    • 一对多:一个 ( subject ) 对象对应多个 ( observer ) 对象
    • observer对象在subject对象中 ( 订阅事件 ),目标对象 ( 广播对象 )
    • Subject对象:维护一个观察者实例组成的数组,并且具有 ( 添加,删除,通知 ) 操作该数组的各种方法
    • Observer对象:仅仅需要维护收到通知后 ( 更新 ) 操作的方法

    代码实现 - ES5

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script>
        function Subject() { // 目标对象的构造函数
          this.observers = [] // 观察者实例组成的数组
        }
        Subject.prototype = {
          add(...params) { // 添加观察者,可以一次添加多个
            this.observers = this.observers.concat(params)
          },
          delete(obj) { // 删除观察者
            const cache = this.observers
            cache.forEach((item, index) => {
              if (item === obj) {
                cache.splice(index, 1) // 删除该对象
              }
            })
          },
          notify() { // 通知观察者
            if (this.observers.length) {
              this.observers.forEach(item => item.update())
            }
          }
        }
    
        // 修改prototype时,一定要记得修改constructor,防止引用出错
        Subject.prototype.constructor = Subject
    
        function Observer(fn) { // 观察者对象的构造函数
          this.update = fn // 观察者对象只需要维护 ( 更新函数 )
        }
    
        // 观察者对象上具有update更新函数,并且观察者对象也只需要维护update方法
        const observerObj1 = new Observer(() => console.log('observer update1'))
        const observerObj2 = new Observer(() => console.log('observer update2'))
    
        // 目标对象上具有 ( 添加, 删除, 通知 ) 等方法
        const subject = new Subject()
        subject.add(observerObj1, observerObj2) // 将观察者对象添加到目标对象维护的observers数组中
        subject.notify() // 目标对象发送通知,则执行观察者对象上的更新方法
        subject.delete(observerObj1)
        subject.notify()
      </script>
    </body>
    </html>
    
    • 代码实现 - ES6
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script>
        // ES6
        class Subject {
          constructor() {
            this.observers = [] // 观察者对象数组
          }
          add(...params) {
            this.observers = this.observers.concat(params)
          }
          delete(obj) {
            this.observers.forEach((observer, index) => {
              if (obj === observer) {
                this.observers.splice(index, 1)
              }
            })
          }
          notify() {
            this.observers.forEach(observer => {
              if (observer.update) {
                observer.update()
              }
            })
          }
        }
        class Observer {
          constructor(fn) {
            this.update = fn
          }
        }
        const observerObj1 = new Observer(() => console.log('observer update1111'))
        const observerObj2 = new Observer(() => console.log('observer update2222'))
        const subject = new Subject()
        subject.add(observerObj1, observerObj2)
        subject.notify()
        subject.delete(observerObj2)
        subject.notify()
      </script>
    </body>
    </html>
    

    [复习笔记-03] 03

    (2) 发布订阅模式

    角色

    • 发布者 Publisher
    • 订阅者 Subscriber
    • 中介 Topic/Event Channel
      • 中介既要接收发布者所发布的消息,又要将消息派发给订阅者
      • 通过中介对象,完全解耦了 ( 发布者 ) 和 ( 订阅者 )

    发布订阅模式 - ES5实现

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script>
        // 中介对象
        const pubsub = {}
    
        // 注意:( 小括号 ) 和 ( 中括号 ) 开头的 ( 前一条语句 ) 必须加分号,或者在小括号或中括号的最前面加分号
        ;(function(pubsub) {
          const topic = {}
    
          // 订阅 
          // subscribe(订阅的事件名, 事件触发的回调函数)
          pubsub.subscribe = function(eventName, fn) {
            if (!topic[eventName]) topic[eventName] = [];
            topic[eventName].push({
              fnName: fn.name,
              fn,
            })
            console.log('topic[eventName]', topic[eventName])
          }
    
          // 发布
          // publish(事件名,事件触发对应的回调函数的参数)
          pubsub.publish = function(eventName, params) {
            console.log('topic[eventName]', topic[eventName])
            if (topic[eventName]) {
              topic[eventName].forEach(observer => {
                observer.fn(params)
              })
            }
          }
    
          // 取消订阅
          // unScribe(需要取消的事件名, 需要取消的回调函数名)
          pubsub.unScribe = function(eventName, fnName) {
            if (topic[eventName]) {
              topic[eventName].forEach((observer, index) => {
                if (observer.fnName === fnName) {
                  topic[eventName].splice(index, 1)
                }
              })
            }
          }
        })(pubsub)
    
        pubsub.subscribe('go', function go1(address1){console.log(`${address1}one`)})
        pubsub.subscribe('go', function go2(address2){console.log(`${address2}two`)})
        pubsub.publish('go', 'home')
        pubsub.unScribe('go','go1') // 取消订阅go1函数
        pubsub.publish('go', 'work')
      </script>
    </body>
    </html>
    

    发布订阅模式 - ES6实现

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script>
        class PubSub {
          constructor() {
            this.topic = {}
          }
    
          subscribe = (eventName, fn) => {
            if (!this.topic[eventName]) this.topic[eventName] = [];
            this.topic[eventName].push({
              fnName: fn.name,
              fn
            })
          }
    
          publish = (eventName, params) => {
            if (this.topic[eventName]) {
              this.topic[eventName].forEach(observer => {
                observer.fn(params)
              })
            }
          }
    
          unSubscribe = (eventName, fnName) => {
            if (this.topic[eventName]) {
              this.topic[eventName].forEach((observer, index) => {
                if ( observer.fnName === fnName) {
                  this.topic[eventName].splice(index, 1)
                }
              })
            }
          }
        }
        const pubSub = new PubSub()
        pubSub.subscribe('go', function go1(params) { console.log(`${params+'1'}`)})
        pubSub.subscribe('go', function go2(params) { console.log(`${params+'2'}`)})
        pubSub.publish('go', 'home')
        pubSub.unSubscribe('go', 'go1') // 取消订阅
        pubSub.publish('go', 'work')
      </script>
    </body>
    </html>
    

    (3) 观察者模式和发布订阅模式的区别和联系

    • 区别
      • 观察者模式:需要观察者自己定义事件发生改变时的响应函数
      • 发布订阅模式:在发布者和订阅者之间加了中介对象
    • 联系
      • 二者降低了代码的耦合性
      • 都具有消息传递的机制,以数据为中心的思想

    (4) 手动实现vue的双向数据绑定

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <!-- 引入 Vue 通过CDN引入 -->
      <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
    </head>
    
    <body>
      <div id="root">
        <div type="text" v-text="name">v-text的内容</div>
        <input type="text" v-model="name">
        <script>
          class MyVue {
            constructor(options) {
              const { el, data } = this.$options = options
              this.$el = document.getElementById(el)
              this.$data = data
    
              this._directive = {}
              // key:data对象中的key
              // value: 
    
              this._observer(this.$data)
              this._compile(this.$el)
            }
    
            _observer = (data) => {
              for (let [key, value] of Object.entries(data)) {
                // key就是data对象中的key;  value就是data对象中每个key对应的值
                // data: {name: 'woow_wu7'} => key=name,value='woow_wu7'
                if (data.hasOwnProperty(key)) {
                  this._directive[key] = [] // data中每个key都对应一个数组
                }
                if (typeof value === 'object') this._observer(value);
                const that = this
                Reflect.defineProperty(this.$data, key, {
                  enumerable: true,
                  configurable: true,
                  get() {
                    return value
                  },
                  set(newValue) {
                    if (value !== newValue) {
                      value = newValue
                      that._directive[key].forEach(item => item._update())
                    }
                  }
                })
    
              }
            }
    
            _compile = (el) => {
              for (let [key, value] of Object.entries(el.children)) {
                if (value.length) {
                  this._compile(value)
                }
                if (value.hasAttribute('v-text')) {
                  const attrubuteValue = value.getAttribute('v-text')
                  this._directive[attrubuteValue].push(new Watcher('input', value, this, attrubuteValue, 'innerHTML'))
                  // 注意:
                  // attrubuteValue是v-text对应的值 => 其实就是data中的key值,和_observer中的声明保持一致了
                }
                if (value.hasAttribute('v-model') && value.tagName === 'INPUT' || value.tagName === 'TEXTAREA') {
                  const attributeValue = value.getAttribute('v-model')
                  this._directive[attributeValue].push(new Watcher('v-model', value, this, attributeValue, 'value'))
    
                  const that = this
                  value.addEventListener('input', (e) => {
                    // 1. input事件修改data中的属性
                    // 2. data中的属性被修改,触发 Reflect.defineProperty 的 setter() 函数
                    this.$data[attributeValue] = e.target.value
                  })
                }
              }
            }
          }
    
          class Watcher {
            constructor(directiveName, el, vm, exp, attr) {
              this.name = directiveName // 指令的名字,比如 'v-text','v-model'
              this.el = el // 每个具体的DOM节点
              this.vm = vm // MyVue实例对象
              this.exp = exp // el中的directiveName属性对应的属性值
              this.attr = attr // el的属性,需要需改的属性
    
              this._update()
            }
            _update = () => {
              this.el[this.attr] = this.vm.$data[this.exp]
              // 将MyVue实例的data属性的最新值更新到ui视图中
            }
          }
    
          new MyVue({
            el: 'root',
            data: {
              name: 'woow_wu7'
            }
          })
        </script>
    </body>
    
    </html>
    

    [复习笔记-03] 03

    (五) reflow回流 和 repaint重绘

    (1) reflow回流 - 回流也叫(重排)

    • reflow概念
      • 对DOM的修改引发了DOM几何尺寸的变化(宽高,隐藏)等,浏览器需要重新计算元素的几何属性。同时其他元素的几何属性和位置也会受到影响,浏览器需要将计算结果绘制出来,这个过程叫做回流reflow
    • repaint概念
      • 对DOM的修改,只是导致了样式变化,并没有改变几何属性,浏览器不需要重新计算几何属性,而是直接绘制新的样式,这个过程叫做重绘repaint
    • 重绘不一定引起回流,但回流一定会引起重绘

    (2) 常见的会引起 ( 回流 ) 的操作

    • 页面首次渲染
    • 浏览器窗口变化
    • 元素尺寸和位置发生变化
    • 元素字体发生变化
    • 添加和删除可见DOM
    • 激活css伪类
    • offsetWidth, width, clientWidth, scrollTop/scrollHeight的计算, 会使浏览器将渐进回流队列Flush,立即执行回流

    (六)script标签的两个属性 - ( async ) ( defer )

    • defer
      • 异步加载,不阻塞页面,在DOM解析完成后才执行js文件
      • 顺序执行,不影响依赖关系
    • async
      • 异步加载,加载不阻塞页面,但是async会在异步加载完成后,立即执行,如果此时html未加载完,也就阻塞页面
      • 异步加载,加载不会阻塞页面,执行会阻塞页面
      • 不能保证各个js的执行顺序

    [复习笔记-03] 03

    (七) 前端模块化

    (1) 模块的概念

    • 将一个复杂程序的各个部分,按照一定的规则(规范)封装不同的块(不同的文件),并组合在一起
    • 块 内部的变量和方法是私有的,只会向外暴露一些接口,通过接口与外部进行通信

    (2) 非模块化存在的问题

    • 对全局变量的污染
    • 各个js文件内部变量会造成相互修改,即只存在全局作用域,没有函数作用域
    • 各个模块如果存在依赖关系,依赖关系模糊,很难分清是谁依赖谁,而依赖又必须前置
    • 难以维护

    (3) 模块化的各种方案

    • IIFE
    • Commonjs规范
    • AMD
    • CMD
    • ES6的模块化方案
    • 总结:
      • Commonjs用于服务端,同步加载,是nodejs使用的模块化方案,commonjs模块就是对象,输入时必须查找对象属性
      • AMD和CMD主要用于浏览器,异步加载
      • ES6的模块化方案,用于浏览器和服务器,通用方案,静态化
      • AMD依赖前置,依赖必须一开始写好,提前加载依赖 ------ RequireJs
      • CMD依赖就近,需要使用的时候才去加载依赖 ------------ SeaJs
      • ES6模块化是静态的,在编译时就能确定模块的依赖关系,输入输出的变量;而AMD,CMD, CommonJs必须在运行时才能确定

    (4) ES6的模块化方案

    (1) ES6的模块化方案 和 CommonJs 的对比

    • Commonjs模块是对象,输入时必须遍历对象的属性,运行时确定模块的依赖关系,输入输出变量
    • ES6的模块不是对象,而是通过export显示输出,再通过import显示输入

    (2) export 命令

    • export用于规定模块对外的接口
    • import用于输入其他模块提供的功能
    • 一个模块就是一个独立的文件,该模块内部的变量,外部无法获取
    • 如果外部需要获取模块内的变量就需要使用 ( export ) 命令 ( 输出 ) 该 ( 变量 )
    • ( export ) 命令除了输出 ( 变量 ),还可以输出 ( 函数 ) 或者 ( )
    • 通常情况export输出的变量就是本来的名字,但是可以使用 as 关键字重命名

    (3) import 命令

    • import命令输入的变量是只读的,因为它的本质就是输入接口,即不允许在加载模块的脚本里改写接口
    • 但是导入的变量是一个对象,修改导入对象的属性是可以的,只是其他模块读取的也将会是改写过后的值,难排错不建议使用
    • from的路径:可以是相对路径也可以是绝对路径
    • import也具有提升效果,类似于变量提升,因为import命令是在 ( 编译阶段 ) 执行,在代码 ( 运行阶段 ) 之前

    (4) export default 命令

    • export default指定默认输出,一个模块只能有一个默认输出

    (5) export 和 import 的复合写法

    export { foo, bar } from 'my_module';
    
    // 可以简单理解为
    import { foo, bar } from 'my_module';
    export { foo, bar };
    
    // 注意!!!!!!!!!!
    1. foo 和 bar 并没有导入当前模块,只是相当于对外转发了这两个接口
    2. 所以当前模块是不能使用 foo 和 bar 变量的
    

    (6) import(specifier) 函数

    • 作用
      • import() 函数可以实现在运行时加载模块
    • 参数
      • specifier:指定所要加载模块的位置
      • import命令能接收什么参数,import()函数就能接收什么参数,区别是import()是动态的
    • 返回值
      • import()方法返回的是一个 promise 对象
    • import()可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用
    • 适用场合
      • 按需加载
      • 条件加载
      • 动态的模块路径

    (八) PlainObject

    • PlainObject表示纯对象,即用对象字面量方式创建( {} ),或者用构造函数创建的对象( new Object() )

    (九) redux 和 react-redux源码

    (1) 源码仓库

    • [redux和react-redux]源码分析仓库
    • [源码] Redux React-Redux01 我的掘金文章

    (2) redux

    [复习笔记-03] 03

    (3) react-redux

    • Provider
    • connect
    • useSelector
    • useDispatch
    • useStore
    • createSelectorHook
    • createDispatchHook
    • createStoreHook
    • shallowEqual 浅比较 shallow是浅的意思
    • batch
    • connectAdvanced
    • ReactReduxContext

    起源地下载网 » [复习笔记-03] 03

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元