Giter VIP home page Giter VIP logo

pma934.github.io's People

Contributors

pma934 avatar

Stargazers

 avatar  avatar

Watchers

 avatar

pma934.github.io's Issues

前端笔记-简

有道云

html

  • HTML5
    • HTML5的离线储存
      • HTML5的离线存储是基于一个新建的.appcache文件的,通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。
  • 新标签
    • 8个语义元素 header section footer aside nav main article figure
    • 内容元素mark高亮 progress进度
    • 新的表单控件calander date time email url search
    • 新的input类型 color date datetime datetime-local email
    • 移除过时标签big font frame frameset
  • HTML5新增的元素
    • web语义化方面,增加了header,footer,nav,aside,section等语义化标签,
    • 在表单方面,为了增强表单,为input增加了color,emial,data ,range等类型,
    • 在存储方面,提供了sessionStorage,localStorage,和离线存储,通过这些存储方式方便数据在客户端的存储和获取
    • 在多媒体方面,规定了音频和视频元素audio和vedio,
    • 其他,另外还有地理定位,canvas画布,拖放,多线程编程的web worker和websocket协议
  • 新API
    • IntersectionObserver
    • requestAnimationFrame
      • window.requestAnimationFrame()方法。它可以将某些代码放到下一次重新渲染时执行。
    • requestIdleCallback
      • window.requestIdleCallback(),也可以用来调节重新渲染。它指定只有当一帧的末尾有空闲时间,才会执行回调函数。
  • 页面导入样式时,使用link和@import有什么区别
    • 从属关系的区别:link属于XHTML标签,而@import是CSS提供的语法规则,link除了加载CSS,还可以定义RSS,定义rel连接属性等,@import就只能加载CSS。
    • 加载顺序的区别:页面加载时,link会同时被加载,而@import引用的CSS会等页面被加载完后再加载。
    • 兼容性的区别:@import只有IE5以上才能被识别,而link是XHTML标签,无兼容问题。
    • DOM可控性区别:通过js操作DOM,可以插入link标签来改变样式;由于DOM方法是基于文档的,无法使用@import方式插入样式
  • 多标签页通信 https://segmentfault.com/a/1190000018386033
    • 方式一:cookie+setInterval
    • 方式二:localStorage
    • 方式三:WebSocket
    • 方式四:BroadcastChannel
    • 方式五:SharedWorker

css

  • 了解???
  • 响应式布局
  • width 100%
    • 默认状态下 width 100% 是相对于父元素的可用宽度,不含 padding。
    • 二、加了 absolute 之后元素变成了 绝对定位,会一直向上寻找第一个非 static 的父元素(例如其它的 absolute 或 relative ),如果上级元素中都没有,那就是相对于 body 了,所以这时候搭配 width 100% 的话就是占据整个视图区域的宽度了。
  • 可以对元素的margin设置百分数,百分数是相对于父元素的width计算,不管是margin-top/margin-bottom还是margin-left/margin-right
  • 清除浮动的方式
    • 给父标签设置overflow属性
    • 给父标签after伪类添加clear:both
  • css3

js

  • ES6

    • Array
      • Array.from
        • Array.from()方法就是将一个类数组对象(有length属性的)或者可遍历对象转换成一个真正的数组。
        • 比如把HTMLAllCollection对象(DOM组)转化为数组Array.from(document.all)
        • Array.from(str)对String使用,效果相当于str.split('')
        • 效果和Array.prototype.slice.call()是一样的,比[...xxx]作用范围更大
        • https://www.cnblogs.com/jf-67/p/8440758.html
      • 拉平数组flat
        • list.flat(Infinity)
      • 填默认值fill
    • 函数
      • ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
    • Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装
    var aaa = function(x){
    	return new Promise((resolve,reject)=>{
    	    resolve(x+1)
    	})
    }
    aaa(3).then(res=>{
        console.log("hhh:"+res)
    	return res+1
    }).then(res=>{
        console.log("hhh:"+res)
    	return res+1
    }).then(res=>{
        console.log("hhh:"+res)
    	return res+1
    }).then(res=>{
        console.log("hhh:"+res)
    	return res+1
    }).then(res=>{
        console.log("hhh:"+res)
    	return res+1
    })
    //hhh:4
    //hhh:5
    //hhh:6
    //hhh:7
    //hhh:8
    //Promise {<resolved>: 9}
    // 除了使用new构造新的promise之外还可以
    Promise.resolve(1)
    .then(res => {
        console.log(res) // => 1
        return 2 // 包装成 Promise.resolve(2)
    })
    .then(res => {
        console.log(res) // => 2
    })
    • 一个函数如果加上 async ,那么该函数就会返回一个 Promise
    async function test() {
        return "1"
    }
    console.log(test()) // -> Promise {<resolved>: "1"}
    • Event Loop 执行顺序
      • 首先执行同步代码,这属于宏任务
      • 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
      • 执行所有微任务
      • 当执行完所有微任务后,如有必要会渲染页面
      • 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数
    • 任务分类
      • 微任务包括 process.nextTick ,promise ,MutationObserver。
      • 宏任务包括 scriptsetTimeoutsetInterval ,setImmediate ,I/O ,UI rendering。
    • 防抖与节流

    防抖
    触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

    节流
    高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

    function debounce(fn) {
        var timeout = null; // 创建一个标记用来存放定时器的返回值
        return function () {
          clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
          timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
            fn.apply(this, arguments);
          }, 500);
        };
    }
    function throttle(fn) {
        var timeout = null; // 创建一个标记用来存放定时器的返回值
        return function () {
          timeout = timeout === null ? setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
            fn.apply(this, arguments);
            timeout = null
          }, 500) : timeout;
        };
    }
    
  • 基本类型

    • String、Number、Boolean、Null、Undefined、Symbol
  • 基本类型与引用类型的区别

    • 基本类型的值是不可变得、基本类型的比较是值的比较、基本类型的变量是存放在栈区的
    • 对象类型存储的是地址(指针)、引用类型的比较是指针的比较、引用类型的值是同时保存在栈内存和堆内存中的对象
  • undefined 和 null 区别

    • null == undefined
    • Null类型,代表“空值”,代表一个空对象指针,使用typeof运算得到 "object",所以你可以认为它是一个特殊的对象值
    • Undefined类型,当一个声明了一个变量未初始化时,得到的就是undefined。
    • undefined是访问一个未初始化的变量时返回的值,而null是访问一个尚未存在的对象时所返回的值。因此,可以把undefined看作是空的变量,而null看作是空的对象。
  • this指向

  • 闭包

    • 闭包是函数和声明该函数的词法环境的组合。
    • 当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。
    • 函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。
    • 在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。
    • 用途:保护函数内的变量安全、加强封装性、函数柯里化、缓存结果
    • 缺点:闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,
  • js异步响应原理

    • 定时器、回调函数、Promises、await/async、事件绑定/事件委托
    • 单线程+事件队列,比如定时器会有一个定时器线程,当到达定时时间时会把事件推入事件队里中,事件队列会按照事件顺序,执行并弹出队列中的事件。也就是说js的异步响应并不是那种线程互不干扰的异步响应,如果前面的事件造成了阻塞,后面的事件将不会执行。
  • 尾调用优化

    • 指某个函数的最后一步是调用另一个函数。由调用栈可知,调用栈中有a函数,如果a函数调用b函数,则b函数也随之入栈,此时栈中就会有两个函数.但是如果b函数是a函数最后一步,并且不需保留外层函数调用记录,即a函数调用位置变量等都不需要用到,则该调用栈中会只保留b函数,这就叫做"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。
  • 原型链

  • js实现instanceof

    //js实现instanceof
    function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 
        var O = R.prototype;   // 取 R 的显示原型 
        L = L.__proto__;  // 取 L 的隐式原型
        while (true) {    
            if (L === null)      
                 return false;   
            if (O === L)  // 当 O 显式原型 严格等于  L隐式原型 时,返回true
                 return true;   
            L = L.__proto__;  
        }
    }
  • new的过程中发生了什么

    • 创建一个空对象
    • 把新对象的原型指向构造函数的prototype
    • 绑定 this 并执行构造函数
    • 返回对象
  • js实现一个new

    //js实现new
    function new_(){
        //1.创建一个空对象
        let newObj = {} 
        //constructor是构造函数,args是其参数
        let [constructor,...args] = [...arguments]
        //2.把新对象的原型指向构造函数的prototype
    	newObj.__proto__ = constructor.prototype 
    	//3.绑定 this 并执行构造函数
    	let res = constructor.apply(newObj,args) 
    	//4.返回对象
    	return res instanceof Object ? res : newObj 
    }
  • 为什么 0.1 + 0.2 != 0.3?如何解决这个问题?

    • 很多十进制小数用二进制表示都是无限循环的。JS采用的浮点数标准会裁剪掉循环的数字。
    • parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true 控制保留位数

综合

  • 懒加载
    • 解释:懒加载其实就是延迟加载,是一种对网页性能优化的方式,比如当访问一个页面的时候,优先显示可视区域的图片而不一次性加载所有图片,当需要显示的时候再发送图片请求,避免打开网页时加载过多资源。
    • 原理<img>标签有一个属性是src,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求
    • 实现:我们先不给设置src,把图片真正的URL放在自定义属性中(一般是data-src,因为可以直接用dataset取出),在需要的时候也就是图片进入可视区域的之前,将URL取出放到src中。
    • 好处:比如一个页面中有很多图片,如淘宝、京东首页等等,如果一上来就发送这么多请求,页面加载就会很漫长,如果js文件都放在了文档的底部,恰巧页面的头部又依赖这个js文件,那就不好办了。更为要命的是:一上来就发送百八十个请求,服务器可能就吃不消了(又不是只有一两个人在访问这个页面)。因此优点就很明显了:不仅可以减轻服务器的压力,而且可以让加载好的页面更快地呈现在用户面前(用户体验好)。
  • 瀑布流加载
    • 主要思路:监听窗口滚动情况,当已经加载的图片的最后一张快要进入窗口的时候,开始加载下面的图片。假设所有的图片地址已经存在一个json数据中,每次读取10张图片地址,加载它们之后,插入到现有的瀑布流末尾。如此往复,直到加载完所有图片。
  • 图片预加载
    • 事先把图片就下载下来,当下次页面中需要用到这个图片的时候,就直接去读取缓存的图片
  • 网页性能管理详解
  • 什么情况下会触发重排?
    • 页面渲染初始化时;(这个无法避免)
    • 浏览器窗口改变尺寸;
    • 元素尺寸改变时;
    • 元素位置改变时;
    • 元素内容改变时;
    • 添加或删除可见的DOM 元素时。
  • 减少重排次数和重排影响范围
    • 将多次改变样式属性的操作合并成一次操作。
      • 同一个DOM的多个属性改变可以写在一起
      • js多次操作css样式的时候不如加class来的快
    • 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。
    • fragment元素的应用
      • 如果要批量添加DOM,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排。
      • 在内存中多次操作节点,完成后再添加到文档中去。例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的html片段,再一次性添加到文档中去,而不是循环添加每一行。
    • 由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。
    • 减少dom属性查询【这些属性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle等】
      • 在需要经常取那些引起浏览器重排的属性值时,要缓存到变量。
      • 尽量不要在布局信息改变时做查询(会导致渲染队列强制刷新)。

浏览器

  • 兼容性
  • 打开一个页面css怎么工作的???渲染过程
    • HTML代码转化成DOM
    • CSS代码转化成CSSOM(CSS Object Model)
    • 结合DOM和CSSOM,生成一棵渲染树(包含每个节点的视觉信息)
    • 生成布局(layout),即将所有渲染树的所有节点进行平面合成
    • 将布局绘制(paint)在屏幕上
    • image
  • 状态码
  • http请求,比如get和post有什么区别
  • 跨域
    • jsonp
    • cors 设置Access-Control-Allow-Origin
    • 反向代理
    • postMessage
    • document.domain + iframe
    • location.hash + iframe
    • window.name + iframe
  • 多标签通信
    • websocket
    • localstorage + window.onstorage
  • 缓存
  • 目前不支持冒泡的事件有哪些呢?
    • blur、focus、load、unload 、以及自定义的事件。
  • CSRF
    • image

计算机基础

  • 访问一个网页的全过程
    • 域名解析成IP地址;
    • 与目的主机进行TCP连接(三次握手);
    • 发送与收取数据(浏览器与目的主机开始HTTP访问过程);
    • 与目的主机断开TCP连接(四次挥手);
    • image
  • 子网掩码与IP
  • 网络类型
    • A: 0.---127 ~~~ 0. 0. 0----255.255.255
    • B:128. 0.----191.255. ~~~ 0. 0----255.255
    • C: 192. 0. 0.----192.255.255. ~~~ 0---255
  • Cache
    • 命中率与Cache的容量,替换算法,块的大小,运行程序特性等有关,尤其是cache的容量和块大小
    • Cache的设计**是在合理成本下提高命中率

其他

  • 模块化

    • 解决命名冲突
    • 提供复用性
    • 提高代码可维护性
  • 基于比较和非比较的排序

    • 基于比较的常用排序算法:插入排序、冒泡排序、选择排序、希尔排序、快速排序、堆排序、归并排序。
    • 非基于比较的排序算法:基数排序、桶排序
  • 组件化与模块化

    • 组件必定是模块化的,并且往往需要实例化,也应当赋有生命周期,而模块化往往是直接引用。
  • v-for 的 key

    • 使用v-for更新已渲染的元素列表时,默认用就地复用策略;列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素;
    • 我们在使用的使用经常会使用index(即数组的下标)来作为key,但其实这是不推荐的一种使用方法;
    • 最好的办法是使用数组中不会变化的那一项作为key值
  • 进程线程是什么,进程间的通信有哪些方法

    • 进程:进程是一个程序在一个数据集合上的一次运行过程,操作系统分配资源的最小单位,每一个进程独立的占有内存空间。
    • 线程:是进程中的一个实体,程序执行的最小单位,同一进程下的各个线程之间共享程序的内存空间。
    • 进程相当于线程的一个容器,多个线程共享在一个进程中的地址空间,可以高效的共享数据;但是多个进程只能共享代码段,而不能共享数据。
    • 进程间通信(IPC)方式:共享内存、消息队列、管道、信号量、互斥器、信号
  • OSI七层模型

  • DNS的运行过程,DNS性能优化有哪些方法

    • 浏览器将接收到的url中抽取出域名字段(访问的主机名),并传送给DNS客户端
    • DNS客户机端向DNS服务器端发送一份查询报文,报文中包含着要访问的主机名字段
    • DNS客户机最终会收到一份回答报文,其中包含有该主机名对应的IP地址
    • 一旦该浏览器收到来自DNS的IP地址,就可以向该IP地址定位的HTTP服务器发起TCP连接
    • DNS性能优化:减少 DNS 查询、预加载(link标签里可以设置)
  • https协议加密具体过程

    • http和tcp链接中加了一层ssl
    • https是一种加密传输协议,基于非对称加密算法和对称加密算法的协作使用。
    • 在交换密钥阶段使用公开密钥加密方式,之后建立通信交换报文阶段则使用共享密钥加密方式。
    • image
  • http1.0 、http1.1和http2.0的区别

    • http1.0:keep-alive建立一个长连接
    • http1.1:默认长链接、节约带宽(支持只传送header)、HOST域
    • HTTP2.0:多路复用、Header压缩、服务器推送、内容安全、二进制格式
  • 在地址栏里输入一个URL,到这个页面呈现出来,中间会发生什么?

    • DNS解析
    • TCP连接
    • 发送HTTP请求
    • 服务器处理请求并返回HTTP报文
    • 浏览器解析渲染页面
    • 连接结束

True

  • HTTP是无状态
  • 域名系统DNS采用的是有连接的TCP传输协议
  • iframe标签会阻塞页面的的加载
  • 网卡实现的主要功能是物理层与数据链路层的功能
  • linux系统下有多个文件目录,/etc: 主机特定的系统配置
  • HTTP协议工作在应用层
  • 带有id属性的DOM元素会创建同名的全局变量(命名访问,不推荐)
  • alt和title同时设置的时候,alt作为图片的替代文字出现,title是图片的解释文字
  • 在一个窗口的生命周期内,窗口载入的所有的页面都是共享一个window.name
  • 前端优化
  • 不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定
  • [] == ![] //true
  • 前端请求如果携带Cookie信息,那么后端Access-Control-Allow-Origin不能为*
  • ["1","2","3"].map(parseInt) //[1,NaN,NaN]这是因为parseInt默认两个参数,第二个参数是进制
  • about:blank或window.open()执行的脚本会继承打开该 URL 的文档的源
  • 计数排序辅助数组长度:max-min+1
  • 筛选法建堆:由无序序列筛选变成有序序列从第 【i/2】(取下界)个开始。
  • margin、padding的百分比都是相对最近父级块级元素的width的

element UI 头像截取模块

<template>
  <div class="upload-head">
    <el-upload
      class="upload-image"
      action="/"
      single-file
      accept="image/jpeg, image/png, image/bmp"
      :multiple="false"
      :show-file-list="false"
      :auto-upload="false"
      :on-change="onChange"
    >
      <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
    </el-upload>

    <img title="图像预览处" :src="base64" alt="图像预览处" style="float:left" />

    <el-dialog title="提示" :visible.sync="dialogVisible" :area="730" @close="handleClose">
      <div
        class="backImg"
        :style="{backgroundImage:`${url}`,backgroundSize:backgroundSize,backgroundPosition:`${backPosX}px ${backPosY}px`}"
        @mousedown="startDrag"
        @mouseup="stopDrag"
      >
        <div class="clipMask"></div>
      </div>

      <div class="zoomSet">
        <el-button icon="h-icon-zoom_in" class="rsbtn" v-repeat-click="zoomRatioAdd" />
        <el-slider v-model="zoomRatio" vertical :max="100" height="100px"></el-slider>
        <el-button icon="h-icon-zoom_out" class="rsbtn" v-repeat-click="zoomRatioDec" />
      </div>

      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="clipImg()">裁 剪</el-button>
        <el-button @click="dialogVisible = false">关 闭</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "UploadHead",
  data() {
    return {
      //界面原始属性
      radius: 185, //截取圆的半径
      marginX: 168, //截取圆离左右边界的间隔
      marginY: 18, //截取圆离上下边界的间隔
      //---------
      dialogVisible: false, //
      url: "",
      img: new Image(),
      backgroundSize: "",

      imgHeight: 0,
      imgWidth: 0,

      backPosX: 150,
      backPosY: 0,
      clickX: 0,
      clickY: 0,

      zoomRatio: 0,
      base64: "",
    };
  },
  directives: {
    RepeatClick: {
      bind(el, binding, vnode) {
        let interval = null;
        let startTime;
        const handler = vnode.context[binding.expression].bind(); //() => vnode.context[binding.expression].apply();
        const clear = () => {
          if (new Date() - startTime < 100) {
            handler();
          }
          clearInterval(interval);
          interval = null;
        };
        el.addEventListener("mousedown", (e) => {
          if (e.button !== 0) return;
          startTime = new Date();
          document.addEventListener("mouseup", clear, { once: true });
          clearInterval(interval);
          interval = setInterval(handler, 100);
        });
      },
    },
  },
  computed: {
    cutBoxHeight() {
      return 2 * (this.radius + this.marginY);
    },
    cutBoxWidth() {
      return 2 * (this.radius + this.marginX);
    },
    limitX() {
      return (
        this.marginX +
        2 * this.radius -
        this.imgWidth * (this.zoomRatio / 100 + 1)
      );
    },
    limitY() {
      return (
        this.marginY +
        2 * this.radius -
        this.imgHeight * (this.zoomRatio / 100 + 1)
      );
    },
  },
  watch: {
    zoomRatio() {
      this.zooming();
    },
  },
  methods: {
    //上传图片时触发
    onChange(file, fileList) {
      console.log(file, fileList);
      this.url = `url(${file.url})`;
      this.getImageSize(file.url);
      fileList.shift();
    },
    //设置头像截取时,图像的初始大小及位置
    getImageSize(url) {
      this.img.src = url;
      window.iii = this.img;
      // 加载完成获取宽高
      this.img.onload = () => {
        this.dialogVisible = true;
        let width = this.img.width;
        let height = this.img.height;
        if (width > height) {
          width = (width * this.cutBoxHeight) / height;
          height = this.cutBoxHeight;
          this.backgroundSize = `auto ${this.cutBoxHeight}px`;
          this.backPosX = this.marginX + this.radius - width / 2;
          this.backPosY = 0;
        } else {
          height = (height * this.cutBoxHeight) / width;
          width = this.cutBoxHeight;
          this.backgroundSize = `${this.cutBoxHeight}px`;
          this.backPosY = this.marginY + this.radius - height / 2;
          this.backPosX = this.marginX - this.marginY;
        }
        this.imgHeight = height;
        this.imgWidth = width;
      };
    },
    //移动图像方法
    startDrag(e) {
      e.preventDefault();
      this.clickX = e.pageX;
      this.clickY = e.pageY;
      document.addEventListener("mousemove", this.dragPic);
      document.addEventListener("mouseup", this.stopDrag);
    },
    stopDrag() {
      document.removeEventListener("mousemove", this.dragPic);
    },
    dragPic(e) {
      let moveX = e.pageX - this.clickX;
      let moveY = e.pageY - this.clickY;
      this.clickX = e.pageX;
      this.clickY = e.pageY;
      this.handleMoveX(moveX);
      this.handleMoveY(moveY);
    },
    handleMoveX(moveX) {
      if (this.backPosX + moveX > this.marginX) {
        return false;
      }
      if (this.backPosX + moveX < this.limitX) {
        return false;
      }
      this.backPosX += moveX;
    },
    handleMoveY(moveY) {
      if (this.backPosY + moveY > this.marginY) {
        return false;
      }
      if (this.backPosY + moveY < this.limitY) {
        return false;
      }
      this.backPosY += moveY;
    },
    //放缩图像方法相关
    zoomRatioAdd() {
      this.zoomRatio++;
    },
    zoomRatioDec() {
      this.zoomRatio--;
    },
    zooming() {
      let ratio = this.zoomRatio / 100 + 1;
      let width = this.imgWidth * ratio;
      let height = this.imgHeight * ratio;
      this.backgroundSize = width > height ? `auto ${height}px` : `${width}px`;

      if (this.backPosX < this.limitX) {
        this.backPosX = this.limitX;
      }
      if (this.backPosY < this.limitY) {
        this.backPosY = this.limitY;
      }
    },
    //关闭截取窗口的回调
    handleClose() {
      console.log("原始尺寸:", this.img.width, this.img.height);
      console.log("比例尺寸:", this.imgWidth, this.imgHeight);
      console.log(
        "放缩尺寸:",
        this.imgWidth * (this.zoomRatio / 100 + 1),
        this.imgHeight * (this.zoomRatio / 100 + 1)
      );
      this.zoomRatio = 0;
    },
    clipImg() {
      let zoomWidth = this.imgWidth * (this.zoomRatio / 100 + 1);
      let zoomHeight = this.imgHeight * (this.zoomRatio / 100 + 1);
      let canvas = document.createElement("canvas"); //不append到html中,会视为变量,随GC回收
      canvas.width = 2 * this.radius;
      canvas.height = 2 * this.radius;
      let naturalWidth = this.img.width;
      let naturalHeight = this.img.height;
      if (canvas.getContext) {
        let ctx = canvas.getContext("2d");
        ctx.clearRect(0, 0, 2 * this.radius, 2 * this.radius);
        ctx.beginPath();
        ctx.arc(this.radius, this.radius, this.radius, 0, 2 * Math.PI, false);
        ctx.clip();
        ctx.drawImage(
          this.img,
          0,
          0,
          naturalWidth,
          naturalHeight,
          this.backPosX - 168,
          this.backPosY - 18,
          zoomWidth,
          zoomHeight
        );
      }
      this.base64 = canvas.toDataURL("image/png", 0.8);
      console.log(this.base64);
    },
  },
};
</script>

<style lang="scss" scoped>
$radius: 185px; //截取圆的半径
$marginX: 168px; //截取圆离左右边界的间隔
$marginY: 18px; //截取圆离上下边界的间隔
.backImg {
  height: 2 * ($radius + $marginY); //406px;
  width: 2 * ($radius + $marginX); //706px;
  cursor: pointer;
  overflow: hidden;
  background-repeat: no-repeat;
}
.clipMask {
  position: relative;
  top: $marginY;
  left: $marginX;
  width: 2 * $radius;
  height: 2 * $radius;
  border-radius: $radius;
  box-shadow: 0px 0px 0px 500px rgba(0, 0, 0, 0.4);
  border: 2px solid #ffffff80;
}
.zoomSet {
  width: 40px;
  height: 200px;
  padding-left: 3px;
  background-color: #ffffff0f;
  position: absolute;
  right: 30px;
  bottom: 90px;
  color: #ffffff;
  font-weight: 600;
  .rsbtn {
    color: rgb(255, 255, 255);
    margin: 10px 2px;
  }
}
</style>

让博客实现 marked 语法高亮

marked文档中有这段示例

// Create reference instance
const marked = require('marked');

// Set options
// `highlight` example uses `highlight.js`
marked.setOptions({
  renderer: new marked.Renderer(),
  highlight: function(code) {
    return require('highlight.js').highlightAuto(code).value;
  },
  pedantic: false,
  gfm: true,
  tables: true,
  breaks: false,
  sanitize: false,
  smartLists: true,
  smartypants: false,
  xhtml: false
});

// Compile
console.log(marked(markdownString));

与highlight.js不同,pygmentize .js库使用异步高亮显示。 这个例子表明,对于您使用的highlighter(语法高亮解析器),marked是不可知的。

marked.setOptions({
  highlight: function(code, lang, callback) {
    require('pygmentize-bundled') ({ lang: lang, format: 'html' }, code, function (err, result) {
      callback(err, result.toString());
    });
  }
});

console.log(marked(markdownString));

在这两个例子中,code都是一个String,表示要传递给highlighter的代码段。在本例中,lang是一个字符串,它通知荧光笔要为code使用什么编程语言,而callback是异步highlighter完成后将调用的函数。


pygmentize使用失败了,作者没更新了

image

看了下pygmentize的文档示例

var pygmentize = require('pygmentize-bundled')

pygmentize({ lang: 'js', format: 'html' }, 'var a = "b";', function (err, result) {
  console.log(result.toString())
})

在vue中使用会报错

vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in mounted hook: "TypeError: exec is not a function"

found in

---> <About> at src/views/About.vue
       <VContent>
         <VApp>
           <App> at src/App.vue
             <Root>
warn @ vue.runtime.esm.js?2b0e:619
logError @ vue.runtime.esm.js?2b0e:1884
globalHandleError @ vue.runtime.esm.js?2b0e:1879
handleError @ vue.runtime.esm.js?2b0e:1839
invokeWithErrorHandling @ vue.runtime.esm.js?2b0e:1862
callHook @ vue.runtime.esm.js?2b0e:4213
insert @ vue.runtime.esm.js?2b0e:3139
invokeInsertHook @ vue.runtime.esm.js?2b0e:6340
patch @ vue.runtime.esm.js?2b0e:6559
Vue._update @ vue.runtime.esm.js?2b0e:3942
updateComponent @ vue.runtime.esm.js?2b0e:4060
get @ vue.runtime.esm.js?2b0e:4473
run @ vue.runtime.esm.js?2b0e:4548
flushSchedulerQueue @ vue.runtime.esm.js?2b0e:4304
(anonymous) @ vue.runtime.esm.js?2b0e:1980
flushCallbacks @ vue.runtime.esm.js?2b0e:1906
Promise.then (async)
timerFunc @ vue.runtime.esm.js?2b0e:1933
nextTick @ vue.runtime.esm.js?2b0e:1990
queueWatcher @ vue.runtime.esm.js?2b0e:4396
update @ vue.runtime.esm.js?2b0e:4538
notify @ vue.runtime.esm.js?2b0e:730
reactiveSetter @ vue.runtime.esm.js?2b0e:1055
(anonymous) @ vue-router.esm.js?8c4f:2555
(anonymous) @ vue-router.esm.js?8c4f:2554
updateRoute @ vue-router.esm.js?8c4f:2013
(anonymous) @ vue-router.esm.js?8c4f:1891
(anonymous) @ vue-router.esm.js?8c4f:2000
step @ vue-router.esm.js?8c4f:1730
step @ vue-router.esm.js?8c4f:1737
runQueue @ vue-router.esm.js?8c4f:1741
(anonymous) @ vue-router.esm.js?8c4f:1995
step @ vue-router.esm.js?8c4f:1730
(anonymous) @ vue-router.esm.js?8c4f:1734
(anonymous) @ vue-router.esm.js?8c4f:1980
(anonymous) @ vue-router.esm.js?8c4f:1773
(anonymous) @ vue-router.esm.js?8c4f:1849
Promise.then (async)
(anonymous) @ vue-router.esm.js?8c4f:1796
(anonymous) @ vue-router.esm.js?8c4f:1817
(anonymous) @ vue-router.esm.js?8c4f:1817
flatMapComponents @ vue-router.esm.js?8c4f:1816
(anonymous) @ vue-router.esm.js?8c4f:1752
iterator @ vue-router.esm.js?8c4f:1959
step @ vue-router.esm.js?8c4f:1733
step @ vue-router.esm.js?8c4f:1737
runQueue @ vue-router.esm.js?8c4f:1741
confirmTransition @ vue-router.esm.js?8c4f:1988
transitionTo @ vue-router.esm.js?8c4f:1890
init @ vue-router.esm.js?8c4f:2546
beforeCreate @ vue-router.esm.js?8c4f:553
invokeWithErrorHandling @ vue.runtime.esm.js?2b0e:1854
callHook @ vue.runtime.esm.js?2b0e:4213
Vue._init @ vue.runtime.esm.js?2b0e:4998
Vue @ vue.runtime.esm.js?2b0e:5079
(anonymous) @ main.js?56d7:13
./src/main.js @ app.js:7783
__webpack_require__ @ app.js:772
fn @ app.js:130
0 @ app.js:7905
__webpack_require__ @ app.js:772
(anonymous) @ app.js:948
(anonymous) @ app.js:951
vue.runtime.esm.js?2b0e:1888 TypeError: exec is not a function
    at pythonVersion (index.js?b376:127)
    at spawnPygmentize (index.js?b376:105)
    at pygmentize (index.js?b376:78)
    at VueComponent.mounted (About.vue?c330:41)
    at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
    at callHook (vue.runtime.esm.js?2b0e:4213)
    at Object.insert (vue.runtime.esm.js?2b0e:3139)
    at invokeInsertHook (vue.runtime.esm.js?2b0e:6340)
    at VueComponent.patch [as __patch__] (vue.runtime.esm.js?2b0e:6559)
    at VueComponent.Vue._update (vue.runtime.esm.js?2b0e:3942)

直接在js文件里面run也失败了,返回的result是undefined

c:\Users\Hp\Desktop\vuetifydemo\a.js:4
  console.log(result.toString())
                     ^

TypeError: Cannot read property 'toString' of undefined

使用highlight.js体验良好

github:https://github.com/highlightjs/highlight.js

使用说明: https://highlightjs.org/usage/

配置文档:https://highlightjs.readthedocs.io/en/latest/

安装:
npm install highlight.js --save

配合marked使用:

import marked from "marked";
import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';

marked.setOptions({
  renderer: new marked.Renderer(),
  highlight: function(code) {
    return hljs.highlightAuto(code).value;
  },
  gfm: true,
  tables: true,
  breaks: true
});

vue全局变量和Vue.prototype

一、使用Vue.prototype实现全局变量

  • 对于一个vue脚手架项目来说,在main.js里使用Vue.prototype声明的变量,实际上是为Vue对象添加了一个原型属性,而不是一个全局变量。但是如果这个原型属性的值是引用类型的,我们就可以借此实现全局变量

当你在main.js里声明了Vue.prototype.a = 1后,因为你的每一个vue组件都是一个Vue对象的实例,所以即使你没有在组件内部使用data(){return{……}}声明a,你依然可以在组件中通过this.a来访问。
当然,你也可以在组件中添加一个变量a,这时你访问的就是你在组件中添加的a,而不再是之前在原型中添加的a了,当然你对组件的a继续修改即不会影响原型中的a和其他组建中的a,就类似于下面这段代码(Form是一个自定义对象类型,Vue也可以看作一个自定义对象类型,而每个.vue文件就是一个对象的实例

function Form(){
}
Form.prototype.a= 0

var f1 = new Form()
var f2 = new Form()

console.log(f1)  //Form {}
console.log(f1.a)   //0
console.log(f2)  //Form {}
console.log(f2.a)   //0

f1.a++
console.log('f1.a++之后')  //f1.a++即 f1.a = f1.a + 1,在f1中添加了a属性,其值为原型中a的值+1

console.log(f1)  //Form {a: 1}
console.log(f1.a)  //1
console.log(f2)  //Form {}
console.log(f2.a)  //0

当然,上面说的是针对于 a的值是 基本的数据类型(undefinedbooleannumberstringnull
如果a是引用类型的值情况就不一样了。就如同下面代码一样。

function Form(){
}
Form.prototype.a= {value:0}

var f1 = new Form()
var f2 = new Form()

console.log(f1)  //Form {}
console.log(f1.a.value)   //0
console.log(f2)  //Form {}
console.log(f2.a.value)   //0

f1.a.value++
console.log('f1.a++之后')  //f1.a++之后

console.log(f1)  //Form {}
console.log(f1.a.value)  //1
console.log(f2)  //Form {}
console.log(f2.a.value)  //1

原型中a的值是一个Object类数据,在实例中使用f1.a.value++并没有修改f1.a指向的指针,实例中依然是访问的原型中的a,同时改变的也是原型中a.value的值。而f2.a.value访问的也是原型中a.value的值,因此f2.a.value的值变化了。
由此可以想到。

由此可以得到启发,如果我们在main.js中声明Vue.prototype.global={a:0}
那我们只要不在组件中重新添加global这个变量,就能在所有组件中使用this.global.a这个值了,同时在一个组件中修改了this.global.a的值,其他所有组件中访问的this.global.a的值也会变化。
这样就相当于使用Vue.prototype实现了vue的全局变量

二、在根节点中创建变量来实现全局变量

  • 当然还有另外一种方法也可以实现vue的全局变量,那就是在根组件中创建这个变量,这样在子组件中就可以通过$root.xxx来访问这个变量了。

在main.js里通过

new Vue({
  ……
  data(){
      return{
          a:1
      }
  },
 ……
})

声明,然后在所有子组件中都可以通过$root.a访问到相同的变量。
68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303230353135353833333831352e706e67

三、使用vuex

https://vuex.vuejs.org/zh/

本blog支持的Markdown语法

Markdown语法示例

字体大小

# This is an <h1> tag
## This is an <h2> tag
### This is an <h3> tag
#### This is an <h4> tag
##### This is an <h5> tag
###### This is an <h6> tag

This is an h1 tag

This is an h2 tag

This is an h3 tag

This is an h4 tag

This is an h5 tag
This is an h6 tag

突出重点

*这是斜体*
_这是斜体_
**这是黑体**
__这是黑体__
*斜体里加**黑体***
**黑体里加*斜体***

这是斜体
这是斜体
这是黑体
这是黑体
斜体里加黑体
黑体里加斜体


清单

无序

* Item 1
* Item 2
  * Item 2a
  * Item 2b
  • Item 1
  • Item 2
    • Item 2a
    • Item 2b

有序

1. Item 1
1. Item 2
1. Item 3
   1. Item 3a
   1. Item 3b
  1. Item 1
  2. Item 2
  3. Item 3
    1. Item 3a
    2. Item 3b

待办

 - [ ] List item
  • List item

图片

![Yaktocat的图片](https://octodex.github.com/images/yaktocat.png)
Yaktocat的图片


链接

http://github.com - automatic!
[GitHub](http://github.com)

http://github.com - automatic!
GitHub


引用文字

As Kanye West said:
> We're living the future so
> the present is our past.

As Kanye West said:

We're living the future so
the present is our past.


内联代码

I think you should use an
`<addr>` element here instead.

I think you should use an
<addr> element here instead.


表格

First Header | Second Header
------------ | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column
First Header Second Header
Content from cell 1 Content from cell 2
Content in the first column Content in the second column

删除线

~~this~~

this


表情符号

😝🌟🐫✨🚶 
:sparkles: :camel: :boom:

😝🌟🐫✨🚶
✨ 🐫 💥


高亮

<mark>高亮</mark>

高亮


CodePen

<iframe height="300" style="width: 100%;" src="//codepen.io/pma934/embed/byaMOZ/?height=300&theme-id=dark&default-tab=css,result"></iframe>
<iframe height="300" style="width: 100%;" src="//codepen.io/pma934/embed/byaMOZ/?height=300&theme-id=dark&default-tab=css,result"></iframe>

隐藏

<details>
<summary>题目</summary>
内容
</details>
题目 内容

跳转锚点

<span  id="XXXX">XXXX</span>
[跳转XXXX](#XXXX)
[回到顶部锚点](#顶部锚点)

回到顶部锚点

setTimeout引出的一些问题

一、setTimeout(fn,0)的延时到底是怎样的?

之前不知道在哪看到了setTimeout(fn,0)和setTimeout(fn,1)效果是一样的
这句话,结果导致后来越记越歪,记成了setTimeout(fn,0)和setTimeout(fn)都会被当作setTimeout(fn,1)执行。

现在想想,其实应该就按字面意思理解,仅仅是setTimeout(fn,0)和setTimeout(fn,1)的执行效果是一样的,其原因如下:

setTimeout(function(){console.log(123)},0)
for(let i=1;i<10;i++){
    i--
}

二、setTimeout与Even Loop

for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); });
}

上面的打印结果大家都知道,就是10个10

但是如果假设setTimeout(fn,0)真的能到达0秒延时又会怎么样呢?

是不是会打印0到9呢?

  • 答案是否定的
  • 因为for循环是一个同步任务,在循环完之前是不会去查看任务队列的,
  • 而对于setTimeout来说,它的延时是针对于将回调事件放入任务队列来说的
  • 因此即使真的达到了0秒延时,也只是立即把setTimeout中的回调事件放入了任务队列,而任务队列中的内容还是得等到执行栈中内容执行完成之后(for循环完毕),才会进入执行栈

因此即使setTimeout(fn,0)真的能到达0秒延时,输出依然是10个10

同理,下面代码输出的是9、8、7、6、5、4、3、2、1、0而不是死循环

for (var i = 0; i < 10; i++) {
setTimeout(function() { i--; console.log(i); }, 0);
}

事实性上面说的这些都和浏览器的事件循环有关

为此,我们可以顺便看看浏览器的事件循环机制

  • 首先要说的一点是js是没有多线程的,js引擎的执行是单线程执行,但是浏览器是多线程的,js引擎只是浏览器的一个线程而已。定时器计时,网络请求,浏览器渲染等,都是由不同的线程去完成的。
  • js引擎与GUI引擎是互斥的
    • 如果在GUI渲染的时候,js改变了dom,那么就会造成渲染不同步
    • 为了避免这种情况发生,在GUI引擎在渲染时会阻塞js引擎计算
  • 宏任务scriptsetTimeoutsetInterval、setImmediate、I/O、UI rendering。
  • 微任务promiseprocess.nextTick、MutationObserver。
  • even loop
    • 执行执行栈中的代码(若执行栈为空则从任务队列里取),这是一个宏任务
    • 如果遇到异步操作,浏览器会开启异步线程,并在线程结束后将回调事件放入任务队列
    • 如果遇到微任务(rosolve、reject),会将微任务放入微任务队列。
    • 执行栈代码执行完毕后,会执行微任务队列中的所有微任务(依次执行)
    • 执行完所有微任务后,如果页面需要渲染,GUI线程接管渲染
    • 渲染完毕后,JS线程接续接管,开始下一个宏任务(从任务队列中获取一个宏任务放入执行栈)
  • 我用自己的理解说一下宏任务
    • 宏任务和微任务到底是什么,我也说不太清楚。
    • 看面经的时候也是直接就开始提宏任务和微任务的概念了。
    • 要说的话,一次事件循环所执行的整个代码就是一个宏任务,然后宏任务中又有一些微任务,这些微任务会在宏任务执行完毕后执行。
    • 有一种这种感觉,宏任务和微任务都是异步执行的,但是微任务是在一次事件循环中的异步,而宏任务则是事件循环间的异步

三、setTimeout与requestAnimationFrame的对比

  • 在浏览器页面不活动时setTimeout的延时会被限制到最小为1000ms,即最多1s一次,但是不会停止,而requestAnimationFrame会停止
  • requestAnimationFrame 不需要设置时间,它会在浏览器下次重绘之前调用指定的回调函数更新动画,这样能使的动画的更新和浏览器重绘频率一致,达到最佳的动画效果(丝滑);而setTimeout即使把延迟设置为16.7ms也不能保证一定能与浏览器重绘频率同步。
  • 使用requestAnimationFrame可以最大程度的利用系统性能;setTimeout的回调会在该执行时执行,不会考虑系统性能。
<iframe height="300" style="width: 100%;" src="//codepen.io/pma934/embed/OJLXbdp/?height=300&theme-id=dark&default-tab=js,result"></iframe>

webpack学习笔记 ——未完待续

webpack 安装

安装 webpack 、webpack-cli

  • npm install webpack --save-dev

  • npm install webpack-cli --save-dev

webpack 可以进行 0 配置

直接npx webpack

打包工具 => 输出后的结果(js 模块)

手动配置 webpack

默认配置文件名称 webpack.config.js

const path = require('path')

module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        filename: 'index.js',
        path: path.resolve(__dirname, 'dist')
    }
}

开发服务

  • npm install webpack-dev-server -D
  • -D要大写,小写无效
 //npm常用设置
  "scripts": {
    "dev": "webpack-dev-server"
  },
module.exports ={
    ……
    devServer:{
        port:3000,
        progress:true,
        contentBase:'./dis',
        open:true,
    }
    ……
}

由 html 模板 生成 html

安装插件 html-webpack-plugin

  • npm install html-webpack-plugin -D
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports ={
    ……
    plugins:[
        new HtmlWebpackPlugin({
            template:'./src/index.html',
            filename:'index.html',
            hash:true,
            minify:{
                removeAttributeQuotes:true,     //去双引号
                collapseWhitespace:true,        //折叠空行
            }
        })
    ]
    ……
}

hash 值的使用

const path = require('path')

module.exports ={
    entry:'./src/index.js',
    output:{
        filename:'bundle.[hash:8].js',       //使用了hash,更改文件后build的新文件不会覆盖旧文件
        path:path.resolve(__dirname,'dist')
    }
}

css 模块化

不要在 html 里引入

ERROR in ./src/index.css 1:4 Module parse failed: Unexpected token (1:4) You may need an appropriate loader to handle this file type.

body{ | background-color: red | } @ ./src/index.js 5:0-22

安装 css-loader 和 style-loader
css-loader 接续@import
style-loader 插入html

希望单个 loader 功能单一
多个 loader 组合出多种功能
单个可以直接字符串引入,多个使用数组;其中单个也可以用对象,能穿参数
loader 有顺序,右向左执行 css-loader -> style-loader
从下向上执行

module: { //模块
    rules: [ //规则
        {

            test:/\.css$/,
            use:['style-loader','css-loader']
        }
    ]
}

安装 less-loader 同时记得安装 less

{
    test: /\.less$/,
    use: [{
            loader: 'style-loader',
            options: {
                insertAt: 'top'
            }
        },
        'css-loader',
        'less-loader'
    ]
},

抽离css

mini-css-extract-plugin
替换直接插入的style-loader使用

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

new MiniCssExtractPlugin({
    filename:'main.css',
})

{
    test: /\.css$/,
    use: [
        MiniCssExtractPlugin.loader,
        'css-loader'
    ]
},

自动添加前缀

安装postcss-loader、autoprefixer

放在css-loader下面(
需要配置文件,否则

Error: No PostCSS Config found in: C:\Users\Hp\Desktop\webpack\src at config.load.then (C:\Users\Hp\Desktop\webpack\node_modules\postcss-load-config\src\index.js:55:15)

压缩css

安装 optimize-css-assets-webpack-plugin

该配置 似乎只有在mode为 production 时优化项才会触发

const OptimizeCss = require('optimize-css-assets-webpack-plugin')

    optimization: { //优化项
        minimizer: [
            new OptimizeCss()
        ]
    },

压缩js

配置optimization会导致默认js压缩失效,因此需要安装
uglifyjs-webpack-plugin

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html',
            hash: true,
            minify: {
                removeAttributeQuotes: true, //去双引号
                collapseWhitespace: true, //折叠空行
            }
        }),
        new MiniCssExtractPlugin({
            filename: 'main.css',
        })
    ],

还需要babel,不然会报错(下面说)

ERROR in index.js from UglifyJs Unexpected token: keyword «const» [./src/index.js:1,0][index.js:91,0]

js 模块化

安装 babel-loader @babel/core @babel/preset-env

module: { //模块
    rules: [ //规则
        {
            test:/\.js$/,
            use:{
                loader:'babel-loader',
                options:{   //用babel-loader需要把es6->es5
                    presets:[
                        '@babel/preset-env'  //语法转化
                    ]
                }
            }
        },

ES7 安装plugin-proposal-class-properties

class A{
     a=1;
}

Add @babel/plugin-proposal-class-properties (https://git.io/vb4SL) to the 'plugins' section of your Babel config to enable transformation.

  • npm install @babel/plugin-proposal-class-properties -D
module: { //模块
    rules: [ //规则
        {
            test:/\.js$/,
            use:{
                loader:'babel-loader',
                options:{   //用babel-loader需要把es6->es5
                    presets:[
                        '@babel/preset-env'  //语法转化
                    ],
                    plugins:[
                        '@babel/plugin-proposal-class-properties'
                    ]
                }
            }
        },

装饰器 安装plugin-proposal-decorators

@log  //装饰器
class A{
     a=1;
}

ERROR in ./src/index.js Module build failed (from ./node_modules/babel-loader/lib/index.js): SyntaxError: C:\Users\Hp\Desktop\webpack_study\src\index.js: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (16:1)

  • npm install @babel/plugin-proposal-decorators -D
        options:{   //用babel-loader需要把es6->es5
            presets:[
                '@babel/preset-env'  //语法转化
            ],
            plugins:[
                ["@babel/plugin-proposal-decorators", { "legacy": true }],
                ["@babel/plugin-proposal-class-properties", { "loose" : true }]
            ]
        }

一些高级函数还是会报错

Generator 函数的语法 浏览器会报错

function  * gen(params){
    yield 1;
}

Uncaught ReferenceError: regeneratorRuntime is not defined at eval (a.js:3) at Object../src/a.js (index.js?73d00fef3436265b63dd:383) at __webpack_require__ (index.js?73d00fef3436265b63dd:20) at eval (index.js:5) at Object../src/index.js (index.js?73d00fef3436265b63dd:405) at __webpack_require__ (index.js?73d00fef3436265b63dd:20) at eval (webpack:///multi_(:8080/webpack)-dev-server/client?:2:18) at Object.0 (index.js?73d00fef3436265b63dd:438) at __webpack_require__ (index.js?73d00fef3436265b63dd:20) at index.js?73d00fef3436265b63dd:84

使用插件plugin-transform-runtime

  • npm install --save-dev @babel/plugin-transform-runtime
  • npm install --save @babel/runtime
{
  "plugins": ["@babel/plugin-transform-runtime"]
}

但是还是会报错

WARNING in (webpack)/buildin/global.js 13:53-60 "export 'default' (imported as '_typeof') was not found in '@babel/runtime/helpers/typeof'

需要设置js查找的范围

{
    test:/\.js$/,
    use:{
        loader:'babel-loader',
        options:{   //用babel-loader需要把es6->es5
            presets:[
                '@babel/preset-env'  //语法转化
            ],
            plugins:[
                ["@babel/plugin-proposal-decorators", { "legacy": true }],
                ["@babel/plugin-proposal-class-properties", { "loose" : true }],
                "@babel/plugin-transform-runtime"
            ]
        }
    },
    include:path.resolve(__dirname,'src'),  //指定包括
    exclude:/node_modules/                  //指定排除
}

在a.js里会报错,es6语法不应该使用module.exports

class B{

}

function  * gen(params){
    yield 1;
}

console.log(gen().next())

module.exports = "123"

安装 @babel/polyfill

使用"foobar".includes("foo")这种语法 ==不懂==

  • npm install --save @babel/polyfill

js校验

eslint

  • npm install eslint eslint-loader
{
    test:/\.js$/,
    use:{
        loader:'eslint-loader',
        enfore:'pre'    //强制先执行,先给js检查错误
    }
},

全局变量引入问题

import $ from 'jquery';
console.log($)
ƒ ( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery…
import $ from 'jquery';
console.log(window.$)

undefined

使用expose-loader暴露全局变量

import $ from 'expose-loader?$!jquery';  //内联loader
console.log(window.$)

如果觉得上面写法恶心,可以在webpack.config.js里设置

  module: { //模块
        rules: [ //规则
            {
                test:require.resolve('jquery'),
                use:'expose-loader?$!jquery'
            },
            ……
            ]
        }

在每个模块注入

const webpack = require('webpack')
……

module.exports = {
    ……
    plugins: [
        ……
        new webpack.ProvidePlugin({
            jquery:'$'
        })
    ],
    

图片处理

js里
npm i file-loader -D
css里
css-loader 已经处理过了
html里
npm i html-withimg-loader -D

把图片变成base64
npm i url-loader -D

{
    test: /\.(png|jpg|gif)$/,
    use: {
        loader:'url-loader',
        options: {
            limit: 1
        }
    }
},

文件分类

img

{
    test: /\.(png|jpg|gif)$/,
    use: {
        loader:'url-loader',
        options: {
            limit: 1,
            outputPath:'img/'   //路径
        }
    }
},

css

plugins: [
    ……
    new MiniCssExtractPlugin({
        filename: 'css/main.css',
    }),
    ……
]

给输出都加上某个路径(为CDN服务)

output: {
    filename: 'index.js', //'bundle.[hash:8].js',
    path: path.resolve(__dirname, 'dist'),
    publicPath:'http://xxx.xxx.xxx'  //给输出的东西都加上某个路径
},

多页

xxx/
|
+——src/
|   |
|   +——index.js
|   |
|   +——other.js
|
+——index.html
|
+——……
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    mode: 'development',
    entry: {
        home: './src/index.js',
        other: './src/other.js'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './index.html',
            filename: 'home.html',
            chunks: ['home'] //代码块,指定引入特定的
        }),
        new HtmlWebpackPlugin({
            template: './index.html',
            filename: 'other.html',
            chunks: ['other']
        })
    ]
}

source map

  • // devtool:'source-map',//增加映射文件,可以调试源代码,显示行列
  • // devtool:'eval-source-map',//不增加映射文件,可以调试源代码,显示行列
  • // devtool:'cheap-module-source-map',//不会显示列,但是是单独的映射文件
  • // devtool:'cheap-module-eval-source-map',//不会产生文件,不显示列

实时

watch:true,   //实时监控
watchOptions:{  //监控的选项
    poll:1000,  //每秒 问我 1000次
    aggregateTimeout:500,    //防抖
    ignored:/node_modules/ //
},

小插件

// 1) leanWebpackPlugin  清除
// 2) copyWebpackPlugin  复制
// 3) bannerPlugin 内置 版权声明

使用mock + koa2 搭建mock server

为什么要搭建mock server?

1.与线上环境一致的接口地址,每次构建前端代码时不需要修改调用接口的代码

2.不同于使用mock直接拦截ajax请求,使用mock server能更好的模拟 POST、GET 请求(在控制台的Network选项页能看到真实的ajax请求信息)

3.mock 数据可以由工具生成不需要自己手动写,同时可以灵活的修改接口数据来适应开发

1.创建koa2项目

  1. 安装 koa-generator脚手架

npm install koa-generator -g

  1. 一键生成koa2项目

koa2 xxx

  1. 安装依赖

npm install

生成的koa2项目目录如下

xxx/
|
+——bin/
|   |
|   +—— www     程序入口
|
+——public/      静态资源
|   |
|   +—— image/   
|   +—— javascripts/
|   +—— stylesheets/
|
+——routes/      路由配置
|   |
|   +—— index.js
|   +—— users.js
|
+——views/       页面模板引擎
|   |
|   +—— error.pug
|   +—— index.pug
|   +—— 
|
+——app.js       使用koa的js
|
+——package.json

bin/www 是入口文件,打开端口监听(默认3000),启动koa服务

app.js 是使用koa的js,里面已经配置好了,解析http请求、使用静态资源、log埋点、使用路由等中间件。

2.搭建mock server

  1. 安装mockjs

npm install mockjs --save

  1. 在routes中创建一个mockapi.js文件 (mockapi是名字,可以自己随便起)
const router = require('koa-router')()
const Mock = require('mockjs')

router.prefix('/mockapi')   //路由路径

//ctx.params 路由传值
//ctx.query  参数传值
//ctx.request.body Post参数

//people
const mockPeople = Mock.mock({
  'peoples|5000': [{
    'id|+1': 1,
    'guid': '@guid',
    'name': '@cname',
    'age': '@integer(20, 50)',
    'birthday': '@date("MM-dd")',
    'address': '@county(true)',
    'email': '@email',
  }]
});
router.get('/people', async (ctx, next) => {
  ctx.body = ctx.query['id'] ? mockPeople['peoples'][ctx.query['id'] - 1] : mockPeople['peoples']
})

router.get('/people/:id', async (ctx, next) => {
  ctx.body = mockPeople['peoples'][ctx.params['id'] - 1]
})

router.post('/people', async (ctx, next) => {
  let postData = ctx.request.body
  let id = postData.id ? postData.id : 1
  ctx.body = mockPeople['peoples'][id - 1]
})

module.exports = router
  1. 在app.js里使用上面创建的路由
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')

const index = require('./routes/index')
const users = require('./routes/users')
const mockapi = require('./routes/mockapi')         //<--引用模块

// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))

app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
app.use(mockapi.routes(), mockapi.allowedMethods()) //<--配置路由

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

module.exports = app

完成上述步骤之后,你就成功搭建了一个mock server了。

使用npm start启动程序后,可以使用postman测试一下效果

get请求

58771584-99748280-85e7-11e9-9631-c2b95b8bf965
58771586-9b3e4600-85e7-11e9-8f02-357aef2b2cba

post请求

58771587-9da0a000-85e7-11e9-9e19-3cd25d3c8117

3.more

虽然到上面mock server的搭建就结束了,但是你还可以做一些事情。
比如:

  • 编写更多的mock数据接口,和一下常用接口
  • 编写pug模板,让你的mock server有一个好看的首页
  • 安装nodemon,使用nodemon启动mock server,使拥有热更新的能力
  • 使用pm2将其部署到服务器上

我的mockApi:http://www.coolan.win:3333/mockapi

项目github:https://github.com/pma934/koa2-mock-server

javascript 设计模式 学习

之前没认真看设计模式,感觉只是一些构造函数的定义,结果真被问到的时候发现,因为不太清楚所以不知道要做什么

就像前几天的面试,面试官问了我vue双向绑定的原理,然后我说了数据劫持和发布-订阅模式的结合。然后又说了Object.defineProperty()设置set、get实现的数据劫持,Event.addEventListener()设置的发布订阅,然后面试官就叫我写一下发布订阅是怎么实现的,我以为是要写个dom绑定事件,结果面试官强调是写这个addEventListener是怎么实现的。。。当时因为对设计模式不太清楚,以为是要定义一个类,然后添加一个类似update这种的方法,然后创建一个类的实例,当实例的值更新时,就会自动触发这个update方法什么的。。。然后就没写出来。。。

下来之后网上搜索了一下设计模式的相关内容

设计模式是可重用的用于解决软件设计中一般问题的方案。设计模式如此让人着迷,以至在任何编程语言中都有对其进行的探索。
其中一个原因是它可以让我们站在巨人的肩膀上,获得前人所有的经验,保证我们以优雅的方式组织我们的代码,满足我们解决问题所需要的条件。
设计模式同样也为我们描述问题提供了通用的词汇。这比我们通过代码来向别人传达语法和语义性的描述更为方便。

什么是设计模式?

一个模式就是一个可重用的方案,可应用于在软件设计中的常见问题 。模式的另一种解释就是一个我们如何解决问题的模板,那些可以在许多不同的情况里使用的模板。 设计模式有以下三点好处:

  • 模式是行之有效的解决方法:他们提供固定的解决方法来解决在软件开发中出现的问题,这些都是久经考验的反应了开发者的经验和见解的使用模式来定义的技术。

  • 可以很容易地重用:一个模式通常反映了一个可以适应自己需要的开箱即用的解决方案。这个特性让它们很健壮。

  • 善于表达:当我们看到一个提供某种解决方案的模式时,一般有一组结构和词汇可以非常优雅地帮助表达相当大的解决方案。

设计模式的种类

创建型设计模式

  • 创建型设计模式关注于对象创建的机制方法,通过该方法,对象以适应工作环境的方式被创建。基本的对象创建方法可能会给项目增加额外的复杂性,而这些模式的目的就是为了通过控制创建过程解决这个问题。
  • 属于这一类的一些模式是:构造器模式(Constructor),工厂模式(Factory),抽象工厂模式 (Abstract),原型模式 (Prototype),单例模式 (Singleton) 以及 建造者模式(Builder)

结构设计模式

  • 结构模式关注于对象组成和通常识别的方式实现不同对象之间的关系。该模式有助于在系统-的某一部分发生改变的时候,整个系统结构不需要改变。该模式同样有助于对系统中某部分没有达到某一目的的部分进行重组。
  • 在该分类下的模式有:装饰模式外观模式享元模式适配器模式代理模式

行为设计模式

  • 行为模式关注改善或精简在系统中不同对象间通信。

  • 行为模式包括:迭代模式中介者模式观察者模式访问者模式

设计模式分类概览表
SN 描述
Creational 根据创建对象的概念分成下面几类。
Class
Factory Method(工厂方法) 通过将数据和事件接口化来构建若干个子类。
Object
Abstract Factory(抽象工厂) 建立若干族类的一个实例,这个实例不需要具体类的细节信息。(抽象类)
Builder (建造者) 将对象的构建方法和其表现形式分离开来,总是构建相同类型的对象。
Prototype(原型) 一个完全初始化的实例,用于拷贝或者克隆。
Singleton(单例) 一个类只有唯一的一个实例,这个实例在整个程序中有一个全局的访问点。
--- ---
Structural 根据构建对象块的方法分成下面几类。
Class
Adapter(适配器) 将不同类的接口进行匹配,调整,这样尽管内部接口不兼容但是不同的类还是可以协同工作的。
Bridge(桥接模式) 将对象的接口从其实现中分离出来,这样对象的实现和接口可以独立的变化。
Composite(组合模式) 通过将简单可组合的对象组合起来,构成一个完整的对象,这个对象的能力将会超过这些组成部分的能力的总和,即会有新的能力产生。
Decorator(装饰器) 动态给对象增加一些可替换的处理流程。
Facada(外观模式) 一个类隐藏了内部子系统的复杂度,只暴露出一些简单的接口。
Flyweight(享元模式) 一个细粒度对象,用于将包含在其它地方的信息 在不同对象之间高效地共享。
Proxy(代理模式) 一个充当占位符的对象用来代表一个真实的对象。
--- ---
Behavioral 基于对象间作用方式来分类。
Class
Interpreter(解释器) 将语言元素包含在一个应用中的一种方式,用于匹配目标语言的语法。
Template Method(模板方法) 在一个方法中为某个算法建立一层外壳,将算法的具体步骤交付给子类去做。
Object
Chain of Responsibility(响应链) 一种将请求在一串对象中传递的方式,寻找可以处理这个请求的对象。
Command(命令) 封装命令请求为一个对象,从而使记录日志,队列缓存请求,未处理请求进行错误处理 这些功能称为可能。
Iterator(迭代器) 在不需要知道集合内部工作原理的情况下,顺序访问一个集合里面的元素。
Mediator(中介者模式) 在类之间定义简化的通信方式,用于避免类之间显式的持有彼此的引用。
Observer(观察者模式) 用于将变化通知给多个类的方式,可以保证类之间的一致性。
State(状态) 当对象状态改变时,改变对象的行为。
Strategy(策略) 将算法封装到类中,将选择和实现分离开来。
Visitor(访问者) 为类增加新的操作而不改变类本身。

观察者模式

观察者模式是这样一种设计模式。一个被称作被观察者的对象,维护一组被称为观察者的对象,这些对象依赖于被观察者,被观察者自动将自身的状态的任何变化通知给它们。

一个或者更多的观察者对一个被观察者的状态感兴趣,将这种兴趣通过附着自身的方式注册在被观察者身上。当被观察者发生变化,而这种变化也是观察者所关心的,就会产生一个通知,这个通知将会被送出去,最后将会调用每个观察者的更新方法。当观察者不在对被观察者的状态感兴趣的时候,它们只需要简单的将自身剥离即可。 ———— 《设计模式:可重用的面向对象软件的元素》

观察者模式确实很有用,但是在javascript时间里面,通常我们使用一种叫做发布/订阅模式的变体来实现观察者模式。这两种模式很相似,但是也有一些值得注意的不同。

  • 观察者模式要求想要接受相关通知的观察者必须到发起这个事件的被观察者上注册这个事件(观察者需要对特定事件进行注册)。
  • 发布/订阅模式使用一个主题/事件频道,这个频道处于想要获取通知的订阅者和发起事件的发布者之间。这个事件系统允许代码定义应用相关的事件,这个事件可以传递特殊的参数,参数中包含有订阅者所需要的值(事件可以传参)。这种想法是为了避免订阅者和发布者之间的依赖性。
  • 这种和观察者模式之间的不同,使订阅者可以实现一个合适的事件处理函数,用于注册和接受由发布者广播的相关通知。
    image

一个简单的观察者模式js代码实现

//被观察者以及其增加,删除,通知在观察者列表中的观察者的能力进行建模
function Subject() {
    this.observerList = [];
}
Subject.prototype.addObserver = function (observer) {
    this.observerList.push(observer)
}
Subject.prototype.removeObserver = function (observer) {
    let index = this.observerList.indexOf(observer)
    this.observerList.splice(index, 1)
}
Subject.prototype.notify = function (context) {
    for (let i in this.observerList) {
        this.observerList[i].update(context);
    }
}
//观察者的一个框架。这里的update 函数之后会被具体的行为覆盖。
function Observer() {
    this.update = function () {
        // ...
    };
}

一个简单的发布/订阅模式代码实现

class Publisher {
    constructor() {
        this.subscribers = {}
    }
    on(topic, event) {
        if (!this.subscribers[topic]) {
            this.subscribers[topic] = []
        }
        if(typeof event !== 'function'){
            event = ()=>{}
        }
        this.subscribers[topic].push(event)
    }
    remove(topic, event) {
        let events = this.subscribers[topic]
        if (events) {
            let i = events.indexOf(event)
            if (i !== -1) {
                events.splice(i, 1)
            }
        }
    }
    emit(topic, ...args) {
        let events = this.subscribers[topic]
        if (events) {
            for (let key in events) {
                events[key](...args)
            }
        }
    }
}

单例模式

单例模式的定义:单例模式之所以这么叫,是因为它限制一个类只能有一个实例化对象。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。

一个简单的单例模式实现

var Singleton = (function () {
    function myObject(name, age) {
        this.name = name
        this.age = age
    }
    myObject.prototype.getName = function () {
        console.log(this.name)
    }
    var instance;
    return function (...args) {
        if (!instance) {
            instance = new myObject(...args)
        }
        return instance
    }
})()

var a = Singleton('小明', 11)
var b = Singleton('小刚', 12)

a.getName() //小明
b.getName() //小明
console.log(a===b)  //true

工厂模式

工厂起到的作用就是隐藏了创建实例的复杂度,只需要提供一个接口,简单清晰。有点类似与函数柯里化的感觉。

一个简单的工厂模式实现

//可以生产各种各样的齿轮
class GearFactory {
    constructor(radius, thickness, material) {
        this.radius = radius
        this.thickness = thickness
        this.material = material
    }
}

//生产半径5cm、厚度2mm的铁齿轮的接口
function createGear(){
    return new GearFactory('5cm','Fe','2mm')
}

中介者模式

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的 相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知 中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系,避免了对象之间相互的引用关系导致的逻辑混乱的问题。

让人联想到了星型拓扑结构

中介者模式聊天室示例

class User {
    constructor(name) {
        this.name = name
        this.log = []
        this.room = null
    }
    showLog() {
        this.log.forEach(x => console.log(x))
    }
    into(chatRoom) {
        this.room = chatRoom
        this.room.into(this)
    }
    send(msg, user) {
        if (this.room) {
            this.room.send(this, msg, user)
            this.log.push(`you send ${user&&user.name||'all'}:${msg}`)
        }
    }
    receive(msg, from) {
        this.log.push(`${from.name} to you:${msg}`)
    }
}

class ChatRoom {
    constructor() {
        this.user = []
        this.log = []
    }
    into(user) {
        this.user.push(user)
    }
    send(from, msg, to) {
        if (to) {
            this.log.push(`${from.name} to ${to.name}:${msg}`)
            if (this.user.indexOf(to) !== -1) {
                to.receive(msg, from)
            }
        } else {
            this.log.push(`${from.name} to all:${msg}`)
            this.user.forEach(user => {
                if (user !== from) {
                    user.receive(msg, from)
                }
            })
        }
    }
    showLog() {
        this.log.forEach(x => console.log(x))
    }
}

var room = new ChatRoom()
var a = new User('A')
var b = new User('B')
var c = new User('C')

a.into(room)
a.send('有人吗?')
b.into(room)
c.into(room)
b.send('有人吗?')
c.send('有人吗?')
a.send('有', b)
c.send('有')

console.log('--room-')
room.showLog()
console.log('---a---')
a.showLog()
console.log('---b---')
b.showLog()
console.log('---c---')
c.showLog()

适配器模式

适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。

这个例子可能没表达好,主要作用就是包装接口对象,在不改变原对象的情况,将对象包装一层,来保证其能适应不同的接口

class Person {
    constructor(name, brithday) {
        this.name = name
        this.brithday = brithday
    }
}
//Person不能直接使用
function getAge(brithYear, brithMonth, brithDay) {
    let now = new Date()
    let nowYear = now.getFullYear(),
        nowMonth = now.getMonth(),
        nowDay = now.getDay()

    let age = nowYear - brithYear

    if (nowMonth < brithMonth) {
        age--
    } else if (nowMonth === brithMonth) {
        if (nowDay < brithDay) {
            age--
        }
    }
    return age
}

class AgePerson {
    constructor(name, brithday) {
        this.person = new Person(name, brithday)
        this.brithday = brithday.split('/').map(d => +d)
    }
    age() {
        console.log(getAge(...this.brithday))
    }
}

var ming = new AgePerson('小明', '1999/9/9')

ming.age()

装饰模式

装饰器模式并不去深入依赖于对象是如何创建的,而是专注于扩展它们的功能这一问题上。不同于只依赖于原型继承,我们在一个简单的基础对象上面逐步添加能够提供附加功能的装饰对象。它的想法是,不同于子类划分,我们向一个基础对象添加(装饰)属性或者方法,因此它会是更加轻巧的。

AOP(Aspect-Oriented Programming)装饰函数

// 前置代码
Function.prototype.before = function (fn) {
    const self = this
    return function () {
        fn.apply(this, arguments)
        return self.apply(this, arguments)
    }
}

// 后置代码
Function.prototype.after = function (fn) {
    const self = this
    return function () {
        let res = self.apply(this, arguments)
        fn.apply(this, arguments)
        return res
    }
}

const wear1 = function () {
    console.log('穿上第一件衣服')
}

const wear2 = function () {
    console.log('穿上第二件衣服')
}

const wear3 = function () {
    console.log('穿上第三件衣服')
}

const wear = wear2.before(wear1).after(wear3)
wear()
// 穿上第一件衣服
// 穿上第二件衣服
// 穿上第三件衣服

外观模式

外观模式提供了一个接口,隐藏了内部的逻辑,更加方便外部调用。

比如jquery的$ ,不论何时我们使用jQuery的$(el).css或者$(el).animate()方法,我们实际上都是在使用一个门面——更加简单的公共接口让我们避免为了使得行为工作起来而不得不去手动调用jQuery核心的内置方法。

比如红宝书中定义的createXHR就是一个使用外观模式的示例

function createXHR() {
    if (typeof XMLHttpRequest != "undefined") {
        return new XMLHttpRequest();
    } else if (typeof ActiveXObject != "undefined") {
        if (typeof arguments.callee.activeXString != "string") {
            var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
                i, len;
            for (i = 0, len = versions.length; i < len; i++) {
                try {
                    new ActiveXObject(versions[i]);
                    arguments.callee.activeXString = versions[i];
                    break;
                } catch (ex) {
                    //跳过
                }
            }
        }
        return new ActiveXObject(arguments.callee.activeXString);
    } else {
        throw new Error("No XHR object available.");
    }
}

代理模式

在我们需要在一个对象后多次进行访问控制访问和上下文,代理模式是非常有用处的。
当实例化一个对象开销很大的时候,它可以帮助我们控制成本,提供更高级的方式去关联和修改对象,就是在上下文中运行一个特别的方法。

JS中的事件委托(事件代理)就是使用了代理模式。

<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
<script>
    let ul = document.querySelector('#ul')
    ul.addEventListener('click', (event) => {
        console.log(event.target);
    })
</script>

资料参考:JavaScript 设计模式

手写实现Promise

之前的内容因为没有考虑到微任务队列的问题,所以全部折叠了。

自己实现一个promise是前端常见的面试题之一(虽然我还没遇到过),同时也是深入理解promise的一种途径,通过自己实现,来更加深刻的理解其中的原理

要自己实现promise,首先得了解Promises/A+条例到底有哪些内容~

  • promise表示异步操作的最终结果。
  • 与promise进行交互的主要方式是通过其then方法,该方法注册回调以接收promise的value或无法履行promise的reason。
  • 该规范详细说明了该then方法的行为。

一、实现一个同步版的promise

同步版先不考虑其他的,直接实现能按照 promise --> then 的顺序执行

  • 先按照规范,定几个标记量
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
  • promise.then同步执行
function Promise(fn){
    let that = this
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    
    let resolve = function(value){
        that.value = value
        that.status = FULFILLED
    }
    let reject = function(reason){
        that.reason = reason
        that.status = REJECTED
    }
    
    try{
        fn(resolve,reject)
    }catch(e){
        reject(e)
    }
}

Promise.prototype.then = function(onFulfilled,onRejected){
    if(this.status === FULFILLED){
        onFulfilled(this.value)
    }
    if(this.status === REJECTED){
        onRejected(this.reason)
    }
}

二、实现异步

接下来在同步版的基础上再解决两个问题

  1. 同步版本在执行异步代码时什么都没有返回
new Promise((resolve,reject)=>{
    setTimeout(()=>{resolve(1)})
}).then(x=>console.log(x))
  • 这是因为在执行then时,resolve还没有执行,status还是PENDING的状态
  1. 如果then中的参数不是Function时的应该有一个默认的回调函数

因此如果执行到then时,需要如下操作:

  • 先判断 onFulfilled与onRejected是否是函数
  • 然后判断status的状态,如果status还是PENDING的话可以先将then中的两个回调函数存起来,待到执行resolve或者reject时再执行
function Promise(fn){
    let that = this
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.resolvedCb = []
    this.rejectedCb = []
    
    let resolve = function(value){
        that.value = value
        that.status = FULFILLED
        that.resolvedCb.forEach(cb=>cb(that.value))
    }
    let reject = function(reason){
        that.reason = reason
        that.status = REJECTED
        that.rejectedCb.forEach(cb=>cb(that.reason))
    }
    
    try{
        fn(resolve,reject)
    }catch(e){
        reject(e)
    }
}

Promise.prototype.then = function(onFulfilled,onRejected){
    onFulfilled = onFulfilled instanceof Function?onFulfilled:()=>{}
    onRejected = onRejected instanceof Function?onRejected:()=>{}

    if(this.status === FULFILLED){
        onFulfilled(this.value)
    }
    if(this.status === REJECTED){
        onRejected(this.reason)
    }
    if(this.status === PENDING){
        this.resolvedCb.push(onFulfilled)
        this.rejectedCb.push(onRejected)
    }
}

其中的关键代码如下:

  • 如果状态是PENDING,先保存回调函数
    if(this.status === PENDING){
        this.resolvedCb.push(onFulfilled)
        this.rejectedCb.push(onRejected)
    }
  • 异步的情况下resolve或者reject会在then之后执行,因此执行时,回调函数已经放入Cb数组中,可以遍历执行Cb中的回调函数
 that.resolvedCb.forEach(cb=>cb(that.value))
 that.rejectedCb.forEach(cb=>cb(that.reason))
  • 如果onFulfilled与onRejected不是函数,应该使用一个默认的函数
onFulfilled = onFulfilled instanceof Function?onFulfilled:()=>{}
onRejected = onRejected instanceof Function?onRejected:()=>{}

三、then的链式调用

根据Promises/A+协议,then也应该返回一个promise,同时这也是then链式调用的条件

  • 先考虑情况如下
Promise.prototype.then = function(onFulfilled,onRejected){
    onFulfilled = onFulfilled instanceof Function?onFulfilled:()=>{}
    onRejected = onRejected instanceof Function?onRejected:()=>{}

    if(this.status === FULFILLED){
        onFulfilled(this.value)
        return new Promise(()=>{})
    }
    if(this.status === REJECTED){
        onRejected(this.reason)
        return new Promise(()=>{})
    }
    if(this.status === PENDING){
        this.resolvedCb.push(onFulfilled)
        this.rejectedCb.push(onRejected)
        return new Promise(()=>{})
    }
}

然后FULFILLED与REJECTED状态下,then返回的内容会直接成为下一个then的回调函数的输入

  • return的内容是下一个then的onFulfilled的输入,应该用resolve包裹
  • throw的内容是下一个then的onRejected的输入,应该用reject包裹

首先要能够捕获throw,然后根据结果类型判断使用resolve还是reject

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            try {
                let res = onFulfilled(this.value)
                resolve(res)
            } catch (e) {
                reject(e)
            }
        })
    }

这样就实现了同步版的then链式调用

function Promise(fn) {
    ……
}

Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
    onRejected = onRejected instanceof Function ? onRejected : () => {}

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onFulfilled(this.value))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === REJECTED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onRejected(this.reason))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === PENDING) {
        this.resolvedCb.push(onFulfilled)
        this.rejectedCb.push(onRejected)
        return new Promise(() => {

        })
    }
}

接着考虑异步的情况下,异步的情况下,执行到then时状态还是PENDING,之后的then也是在PENDING状态下返回的promise的基础上调用的


考虑异步,没有链式调用时只需要把onFulfilled、onRejected放入回调数组中,链式调用的话,应该把FULFILLED、REJECTED状态的promise中的回调函数放入回调数组中(这里逻辑感觉挺复杂的,主要是保证执行resolvedCb或rejectedCb中内容执行时,有和FULFILLED或REJECTED中promise的回调执行相同的效果)

这里注意一点,onFulfilled和onRejected的输入是新promise的value或者reason属性,因此直接用this,但是整个回调是放在上一个promise的数组中的,因此用that标识原来的this

Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
    onRejected = onRejected instanceof Function ? onRejected : () => {}

    //let that = this
    ……
    if (this.status === PENDING) {
        return new Promise((resolve, reject) => {
            this.resolvedCb.push(() => {
                try {
                    resolve(onFulfilled(this.value))
                } catch (e) {
                    reject(e)
                }
            })
            this.rejectedCb.push(() => {
                try {
                    resolve(onRejected(this.reason))
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}

这一步的完整程序如下

const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"

function Promise(fn) {
    let that = this
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.resolvedCb = []
    this.rejectedCb = []

    let resolve = function (value) {
        that.value = value
        that.status = FULFILLED
        that.resolvedCb.forEach(cb => cb(that.value))
    }
    let reject = function (reason) {
        that.reason = reason
        that.status = REJECTED
        that.rejectedCb.forEach(cb => cb(that.reason))
    }

    try {
        fn(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
    onRejected = onRejected instanceof Function ? onRejected : () => {}

    let that = this

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onFulfilled(this.value))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === REJECTED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onRejected(this.reason))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === PENDING) {
        return new Promise((resolve, reject) => {
            that.resolvedCb.push(() => {
                try {
                    resolve(onFulfilled(this.value))
                } catch (e) {
                    reject(e)
                }
            })
            that.rejectedCb.push(() => {
                try {
                    resolve(onRejected(this.reason))
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}

这个版本基本上能和标准的promise一致了,但是还有一些细节问题,比如下面代码的执行结果

new Promise((resolve, reject) => {
    resolve(new Promise((rs, rj) => {rs(9)}))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//res:9
new Promise((resolve, reject) => {
    resolve(new Promise((rs, rj) => {rj(9)}))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//err:9

因此下一步就是解决resolve的内容是promise的情况

ps:下面代码效果是一致的,即只有resolve会解析内部promise的内容

new Promise((resolve, reject) => {
    reject(new Promise((rs, rj) => {rs(9)}))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//err:[object Promise]
new Promise((resolve, reject) => {
    reject(new Promise((rs, rj) => {rj(9)}))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//err:[object Promise]

同理then中return返回的promise也会解析,throw返回的promise不会解析

new Promise((resolve, reject) => {
    resolve(1)
}).then(res => {
    return new Promise(resolve=>resolve(res))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//res:1

new Promise((resolve, reject) => {
    resolve(1)
}).then(res => {
    throw new Promise(resolve=>resolve(res))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
// err:[object Promise]

四、解决resolve的内容是promise的情况

只有resolve和return中的promise会被解析,即只用this.value会被解析,而this.reason则不会

因此只需要修改if (this.status === FULFILLED) {}that.resolvedCb.push(() => {})中的内容

先考虑同步状态,执行到this.status === FULFILLED时,如果this.value是一个promise的话,相当于之后then的链式调用都是接到这个promise后面的,因此:

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            if (this.value instanceof Promise) {
                this.value.then(onFulfilled, onRejected)
            } else {
                try {
                    resolve(onFulfilled(this.value))
                } catch (e) {
                    reject(e)
                }
            }
        })
    }

再考虑异步的,直接和上面一步同理

    if (this.status === PENDING) {
        return new Promise((resolve, reject) => {
            that.resolvedCb.push(() => {
                if (this.value instanceof Promise) {
                    this.value.then(onFulfilled, onRejected)
                } else {
                    try {
                        resolve(onFulfilled(this.value))
                    } catch (e) {
                        reject(e)
                    }
                }
            })
            that.rejectedCb.push(() => {
                try {
                    resolve(onRejected(this.reason))
                } catch (e) {
                    reject(e)
                }
            })
        })
    }

完整版如下:

const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"

function Promise(fn) {
    let that = this
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.resolvedCb = []
    this.rejectedCb = []

    let resolve = function (value) {
        that.value = value
        that.status = FULFILLED
        that.resolvedCb.forEach(cb => cb(that.value))
    }
    let reject = function (reason) {
        that.reason = reason
        that.status = REJECTED
        that.rejectedCb.forEach(cb => cb(that.reason))
    }

    try {
        fn(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
    onRejected = onRejected instanceof Function ? onRejected : () => {}

    let that = this

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            if (this.value instanceof Promise) {
                this.value.then(onFulfilled, onRejected)
            } else {
                try {
                    resolve(onFulfilled(this.value))
                } catch (e) {
                    reject(e)
                }
            }
        })
    }
    if (this.status === REJECTED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onRejected(this.reason))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === PENDING) {
        return new Promise((resolve, reject) => {
            that.resolvedCb.push(() => {
                if (this.value instanceof Promise) {
                    this.value.then(onFulfilled, onRejected)
                } else {
                    try {
                        resolve(onFulfilled(this.value))
                    } catch (e) {
                        reject(e)
                    }
                }
            })
            that.rejectedCb.push(() => {
                try {
                    resolve(onRejected(this.reason))
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}

效果对比:
image

五、promise的部分API实现

Promise.resolve

Promise.resolve = function(value){
    return new Promise(resolve=>{
        resolve(value)
    })
}

Promise.reject

Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason)
    })
}

Promise.all

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

p的状态由p1、p2、p3决定,分成两种情况。

  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.all = function (promises) {
    let resolveList = []
    return new Promise((resolve, reject) => {
        if(promises.length === 0){ //promises为空数组的情况下,会返回resolve([])
            resolve(resolveList)
        }
        promises.forEach((p,i) => {
            Promise.resolve(p).then(re => {
                resolveList[i] = re
                if (promises.length === resolveList.length) {
                    //因为promise异步的原因,还是得放里面
                    resolve(resolveList)
                }
            }, rj => {
                reject(rj)
            })
        })
    })
}
ps:有个bug,如下(已经修改)
var a = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('3')
    },300)
})
var b = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('2')
    },200)
})
var c = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('1')
    },100)
})
var d = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('4')
    },400)
})

Promise.all([a, b, c, d]).then(res => console.log(res), res => console.log(res))

真正的promise.all返回是[3,2,1,4],我的返回是[1,2,3,4]

Promise.race

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

Promise.race = function (promises) {
    let flag = true
    return new Promise((resolve, reject) => {
        promises.forEach(p => {
            Promise.resolve(p).then(re => {
                if (flag) {
                    flag = false
                    resolve(re);
                }
            }, rj => {
                if (flag) {
                    flag = false
                    reject(rj);
                }
            })
        })
    })
}

借由queueMicrotask实现promise

function Promise(fn) {
    let that = this
    this.resolvedCb = []
    this.rejectedCb = []
    this.state = 'pending'

    let resolve = function (value) {
        if (value instanceof Promise) { //解决resolve包裹promise的问题
            value.then(resolve, reject)
        } else {
            queueMicrotask(() => {
                if (that.state === 'pending') {
                    that.state = 'resolved'
                    that.resolvedCb.forEach(cb => cb(value))
                }
            })
        }
    }
    let reject = function (reason) {
        queueMicrotask(() => {
            if (that.state === 'pending') {
                that.state = 'rejected'
                that.rejectedCb.forEach(cb => cb(reason))
            }
        })
    }
    try {
        fn(resolve, reject)
    } catch (e) {
        reject(e)
    }
}
Promise.prototype.then = function (onFulfilled, onRejected) {
    //解决透传的问题
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : r => r
    onRejected = onRejected instanceof Function ? onRejected : r => {
        throw r
    }
    return new Promise((resolve, reject) => {
        this.resolvedCb.push(function (value) {
            try {
                resolve(onFulfilled(value))
            } catch (e) {
                reject(e)
            }
        })
        this.rejectedCb.push(function (reason) {
            try {
                resolve(onRejected(reason))
            } catch (e) {
                reject(e)
            }
        })
    })
}
  • 事件循环执行到微任务时,会把微任务放入微任务队列,待到执行栈代码执行完毕之后才会执行微软栈中的内容;因此使用了html的微任务apiqueueMicrotask将resolve和reject的回调函数放入微任务队列。

  • 之前代码then中的FULFILLED和REJECTED状态部分是同步执行的现在不需要了

  • 之前漏考虑了new Promise((resolve)=>{resolve(1)}).then().then((res) => console.log(res))这种透传的问题,现在修正了

  • 之前是在then中解决resolve包裹promise的问题,现在同步执行到then时是得不到value的内容的,因此将处理这个问题的代码放到了resolve函数中

  • 参考资料

关于是否所有函数都有prototype一说

关于是否所有函数都有prototype一说

参见ES国际标准

image

  • 使用Function.prototype.bind创建的函数对象
function abc(){console.log('abc')}
var binded = abc.bind(null)
binded() //abc
console.log(binded.prototype) //undefined
  • 箭头函数也没有
var abc = ()=>{console.log('abc')}
abc() //abc
console.log(abc.prototype) //undefined
附加一点内容:理清__proto__、prototype、constructor

首先,我们需要牢记两点:①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有__proto__和constructor属性

__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象)

constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数

prototype属性,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是用这个构造函数创建的实例的原型对象

每个对象都有构造函数是指每个对象都可以找到其对应的constructor,
这个constructor可能是对象自己本身显式定义的或者通过__proto__在原型链中找到的
而单从constructor这个属性来讲,只有prototype对象才有。
函数创建的对象.__proto__ === 该函数.prototype该函数.prototype.constructor===该函数本身

  • 总结
  1. 我们需要牢记两点:①__proto__constructor属性是对象所独有的;② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__constructor属性。
  2. __proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链
  3. prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype
  4. constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function

https://blog.csdn.net/cc18868876837/article/details/81211729

2019-8-10更新之前的内容 今天看了[这个视频](https://www.imooc.com/video/7054),发现反例是这样的
function abc(){console.log('abc')}
abc() // abc
console.log(abc.prototype) //abc {}
var binded = abc.bind(null)
console.log(typeof binded) //function
console.log(binded instanceof Function) //true
console.log(Object.prototype.toString.call(binded)) //[object Function]
console.log(binded.prototype) //undefined
binded() //abc

同学原话没错,但是我和他都理解错了。。。

之前无意中看到 并不是所有函数都有 prototype 属性这句话,但是想了半天就是想不出到底哪个函数没有 prototype 属性

然后问了一下同学,同学告诉我反例是Function.prototype.bind()

Function.prototype.bind().prototype     //undefined

bind一般的用法是fn.bind(obj),然后返回的是绑定obj内容上下文(this指向obj)的fn。如果obj为空的话,则this指向window,如下:

var age = 50
var child = {age:1}
function getAge(){
	console.log(this.age)
}
var childGetAge = getAge.bind(child)
var _getAge = getAge.bind()
getAge()        //50
childGetAge()   //1
_getAge()       //50

因此Function.prototype.bind()其实就是Function.prototype


于是我现在有了一个疑问Function.prototype到底是不是函数呢?

Function.prototype instanceof Function  //false

typeof Object.prototype //object

Object.prototype.toString.call(Function.prototype)  //[object Function]

Function.prototype.bind.call(Function.prototype)   //ƒ () { [native code] }

js继承

不得不说红宝书第六章真的很重要,也真的很难QAQ

原型链继承

  • 子类的原型指向父类的实例
原型链继承
function Pet(){
    this.master = "zxz"
}
Pet.prototype.about = function(){
    console.log(`这只宠物的主人是${this.master}`)
}
function Cat(color){
    this.color = color
}
Cat.prototype = new Pet()  
Cat.prototype.constructor = Cat  

var coco = new Cat()
coco.about() //这只宠物的主人是zxz

构造函数继承

  • 子类内部调用父类构造函数
构造函数继承
function Pet(name,age){
    this.name = name
    this.age = age
}
function Cat(name,age,color){
    this.color = color
    Pet.call(this,name,age) 
}

var coco = new Cat("coco",3,"白")
console.log(coco) //Cat { color: '白', name: 'coco', age: 3 }

组合继承

组合继承
  1. 通过call或者apply,继承父类构造函数中的属性
    • 如果不写的话,还是可以通过原型链获得属性;但是对于需要向构造函数传入参数的属性,因为没法传参,值只能undefined
  2. 替换原型,子类的原型对象设置为父类的一个实例
  3. 保持构造函数的一致性
  • 第一步是基于构造函数的继承,第二步是基于原型链的继承,两者组合使用
  • 规避只使用原型链继承时 引用类型 原型属性被所有实例共享的问题
  • 规避只使用构造函数继承时 实例的方法没有复用 的问题
function Pet(name,age){
    this.name = name
    this.age = age
}

Pet.prototype.about = function(){
    console.log(`这只宠物叫${this.name},今年${this.age}岁了`)
}

function Cat(name,age,color){
    this.color = color
    Pet.call(this,name,age) //通过call或者apply定义Pet中以及定义的属性
}

Cat.prototype = new Pet()   //继承原型,coco.__proto__ => Cat.prototype  ; coco.__proto__.__proto__ => new Pet().__proto__  =>  Pet.prototype
Cat.prototype.constructor = Cat  // 保持构造函数的一致性,不然构造函数指向Pet的

Cat.prototype.about = function(){ //重写对象方法
    console.log(`这只${this.color}色猫猫叫${this.name},今年${this.age}岁了`)
}


var wowo = new Pet("wowo",4)
wowo.about()//这只宠物叫wowo,今年4岁了

var coco = new Cat("coco",3,"白")
coco.about()//这只白色猫猫叫coco,今年3岁了
console.log(coco.__proto__.constructor) //[Function: Cat]
console.log(coco instanceof Cat) //true
console.log(coco instanceof Pet) //true

缺点:原型上有一些多余的属性

console.log(coco)

image


原型式继承

  1. 创造一个新的空的类
  2. 新的类的原型指向要继承的类
  3. 返回一个新的类的实例
原型式继承
function object(o){
    function F(){}
    F.prototype = o
    return new F();
}

ES5使用object.create()规范了原型式继承

(method) ObjectConstructor.create(o: object): any (+1 overload)

有重载,第二个参数格式类似Object.definedProperties()方法


寄生式继承

寄生式继承
function createAnother(original){
    var clone = object(original); // object是上面的原型式继承
    clone.sayHi = function(){
        console.log('hi')
    }
    return clone
}

寄生组合式继承

寄生组合式继承

1.通过call或者apply,继承父类构造函数中的属性
2.使用Object.create(Pet.prototype)替换原型,子类的原型对象设置为父类的一个原型式继承
3.保持构造函数的一致性

好像就是把组合继承中的Cat.prototype = new Pet()换成了Cat.prototype = Object.create(Pet.prototype)
避免了二次调用new SuperType()

function Pet(name, age) {
    this.name = name
    this.age = age
}

Pet.prototype.about = function () {
    console.log(`这只宠物叫${this.name},今年${this.age}岁了`)
}

function Cat(name, age, color) {
    this.color = color
    Pet.call(this, name, age) //通过call或者apply定义Pet中以及定义的属性
}
/***** 继承步骤上组合继承不同的地方 ***********************/
Cat.prototype = Object.create(Pet.prototype)
/***** **************************************************/
Cat.prototype.constructor = Cat // 保持构造函数的一致性,不然构造函数指向Pet的

Cat.prototype.about = function () { //重写对象方法
    console.log(`这只${this.color}色猫猫叫${this.name},今年${this.age}岁了`)
}

var coco = new Cat("coco", 3, "白")

/***** 这部分得到的结果都和组合继承相同 *******************/
var wowo = new Pet("wowo", 4)
wowo.about() //这只宠物叫wowo,今年4岁了

var coco = new Cat("coco", 3, "白")
coco.about() //这只白色猫猫叫coco,今年3岁了
console.log(coco.__proto__.constructor) //[Function: Cat]
console.log(coco instanceof Cat) //true
console.log(coco instanceof Pet) //true
/***** **************************************************/
console.log(coco)

image
相比于组合继承,在Pet原型上少了很多多余的属性


ES6中的继承

ES6中的继承

class 实现继承的核心在于使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,因为这段代码可以看成 Pet.call(this, name, age)

class Pet {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    about() {
        console.log(`这只宠物叫${this.name},今年${this.age}岁了`)
    }
}

class Cat extends Pet {
    constructor(name, age, color) {
        super(name, age)
        this.color = color
    }
    about() { //重写对象方法
        console.log(`这只${this.color}色猫猫叫${this.name},今年${this.age}岁了`)
    }
}

/***** 这部分得到的结果都和组合继承相同 *******************/
var wowo = new Pet("wowo", 4)
wowo.about() //这只宠物叫wowo,今年4岁了

var coco = new Cat("coco", 3, "白")
coco.about() //这只白色猫猫叫coco,今年3岁了
console.log(coco.__proto__.constructor === Cat) //[Function: Cat]
console.log(coco instanceof Cat) //true
console.log(coco instanceof Pet) //true
/***** **************************************************/
console.log(coco)

image
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

Object.keys(Pet.prototype)  //[]

红宝书 笔记6 (重点章节)

有道云
面向对象的程序设计
面向对象 Object-Oriented OO
标志:有类的概念
数据类型
[[Configurable]]: 表示能否通过delete删除属性从而重新定义属性,能够修改属性的特性,或者能否把属性修改为访问器属性
[[Enumerable]]: 表示能否通过for-in循环返回属性
[[Writable]]: 表示能否修改属性的值
[[Value]]: 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值undefined
要修改属性默认的特性,必须使用ECMAScript 5的Object.defineProperty()方法。这个方法接受三个参数:属性所在对象,属性名和一个描述符对象。其中描述符对象的属性值必须是:configurable、enumerable、writable和value。设置其中一个或多个。
var person = {};
Object.defineProperty(person, 'name', {
writable: false,
value: 'Yeaseon'
});

Object.defineProperty()方法不能对configurable: false的对象进行修改。
即,一旦把对象设置成不可配置的之后就不能再改回可配置的了

调用Object.defineProperty()方法创建一个新属性时,如果不指定,configurable、enumerable 和 writable 特性的默认值都是false,即不可删除,不可遍历,不可修改

创建对象
工程模式:
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

构造器模式:
构造器函数应该始终以一个大写字母开头,而非构造器函数始终以小写字母开头
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

原型模式:
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
person1.sayName(); //"Nicholas"

原型模式有一个致命问题就是 当属性是引用类型时
修改任何一个实例的该属性都会改变原型的属性
Person.prototype //原型函数获取原型
Object.getPrototypeOf(person1) //实例对象获取原型

结合使用构造函数和原型模式
用构造函数模式定义实例属性,用原型模式定义方法和共享属性。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

动态原型模式:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job =job;

if(typeof this.sayName != "function"){
    Person.prototype.sayName = function(){
        alert(this.name);
    };
}

}

注意:使用动态原型模式时,不能使用对象原型字面量重写原型
不然,如下会报错
function Person(){
}
var friend = new Person();
Person.prototype = { //重写
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error

寄生构造函数模式:
稳妥构造函数模式:

继承
继承是通过原型链实现的,使用最多的继承模式是组合继承,使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。

原型链
function SuperType () {
this.property = true;
}

SuperType.prototype.getSuperValue = function () {
return this.property;
};

function SubType () {
this.subproperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function () {
return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue()); // true

移动端前端统一框架

出于种种原因,项目组需要构建新的移动端统一框架,用以作为之后的H5应用的开发基座

之前的移动端框架存在以下几点问题。

  1. 完全是一个单页面H5应用(这点不算缺点哈),页面直接通过hash路由切换,而且页面的header也是前端实现的,不方便手势操作的实现,手势操作一个左滑直接关闭了webview,而不是退回上一个路由(虽然可以通过监听touch相关事件实现,但是经过评估,实现后的效果也不如移动端自己的手势操作,还存在误触操作。) 因此新框架改成了由移动端提供header,每次切页面就打开新的webview加载新页面(个人理解类似浏览器新开tab页)
    (移动端提供header也不是必要条件,只是以前因为header方面的适配问题,弹出软键盘阿,弹出半屏阿之类的问题,评估后决定移动端提供header是一个不错的优化方案,减小了不同手机上的一些表现差异性)
    (弹出软键盘后,屏幕缩小,有些型号的手机,header就在固定不住,ios的webview还存在代理样式)

  1. 既然全部重新实现,也顺便重新约定js与app的通信协议
    先说一下之前的通信协议,移动端定义了很多方法名,同时还定义了模块名。
    以activeDevice(激活设备)协议为例。

ios会在浏览器上注入window.webkit.messageHandlers.sadp.postMessage的方法,该方法用于调用sadp模块下的方法,调用方式则是提供cmd传递方法名,再传递一个sequence,和入参data。代码如下

  //ios
  window.webkit.messageHandlers.sadp.postMessage({
      cmd: "activeDevice",
      sequence: sequence,//生成一个uuid
      data: data
  });

安卓则会直接注入window.模块名.方法名的方法供js调用,不过入参需要转换为JSON字符串,代码如下

 //安卓
 const jsonStr = JSON.stringify({
     sequence:sequence,//生成一个uuid
     data:data
 });
 window.sadp.activeDevice(jsonStr);

在调用移动端注入的方法后,移动端会接收到参数,然后通过调用window.activeDeviceCallBack({data,sequence})来返回结果
然后之前为了统一两端调用,在js上封装的方法大概如下

  const request = (模块名,方法名,keepAlive=false) => {
      const cbList = {};
      
      return function(data,cb) {
          if(!window[方法名+'CallBack']) {
              window[方法名+'CallBack'] = function(obj) {
                  cbList[obj.sequence](obj.data)
                  if(!keepAlive){
                      delete cbList[obj.sequence];
                  }
                  if(Object.keys(cbList).length === 0){
                      delete window[方法名+'CallBack'];
                  }
              }
          }
          const sequence = getUUID();
          cbList[sequence] = cb;
          console.log(模块名,方法名,sequence)
          if(安卓){
              const jsonStr = JSON.stringify({
                  sequence:sequence,
                  data:data
              });
              window[模块名][方法名](jsonStr);
          }else if(IOS){
              window.webkit.messageHandlers.模块名.postMessage({
                  cmd: "方法名",
                  sequence: sequence,
                  data: data
              });
          }
          
      }
  }

api封装

  //activeDevice,单次回调
  export const activeDevice = request('sadp', 'activeDevice', false);
  //getDeviceList, 长连接
  export const getDeviceList = request('sadp', 'getDeviceList', true);
  ```
  方法调用
  ```xxx.vue
  import {getDeviceList} from './api.js';
  
  method:{
      showDevice() {
          //调用方式为,方法(参数,回调函数)
          getDeviceList({type:1},(deviceList)=>{
              //操作deviceList
          })
      }
  }

之前封装的js方法存在一个问题,就是当出现镶套调用移动端方法时,由于是通过回调函数的写法,会出现回调地狱的情况。此外移动端两端也反映,定义不同的模块意义不大,而且每次调用callback方法写起来也麻烦。
于是一不做,二不休,重新约定了协议,并对js方法重新进行了封装。
新协议如下:主要是只设置了唯一的模块名App,并且android也通过cmd传递所调用的方法,而调用时统一使用postMessage

    //ios
    window.webkit.messageHandlers.App.postMessage({
        cmd: "方法名",
        sequence: sequence,
        data: data
    });
    //android
    window.App.postMessage(JSON.stringify({
        cmd: "方法名",
        sequence:sequence,
        data:data
    }));

并且约定app通过window.App.reviceMessage方法返回回调数据。app回调时传参为一个res对象,res对象包括cmd、sequence和回调data三个参数。实际上sequence就可以确定唯一的回调,而cmd一般用作console数据,便于程序运行时的调试分析

    window.App.reviceMessage = (res) => {
        
    }

同时,为了解决回调地狱的问题,重新封装了js的调用方法。
封装的方法为callNative,每次调用callNative方法,都会先通过app注入的方法向app对应的cmd进行请求,然后返回一个promise,并将该promise的resolve方法存储到resolves对象中,key值为sequence。
app回调前,promise的状态为pendding.
app回调时,会调用js定义好的reviceMessage方法,进而会调用resolves[sequence],而resolves[sequence]被赋值为promise的resolve方法,因此promise会变为resolved状态,同时回调的参数通过resolves[sequence]方法传递给then方法,然后我们在then中删除resolves[sequence]解除资源占用,并结合promise的链式调用,将res传递给下一个then方法(或者通过await获取到res)
长连接方法类似,同样定义了一个promise,并通过await使得promise状态变为resolved后,才被yield返回出去,调用时由于每次返回promise,因此使用for await of

   const resolves = {};
   
   const reviceMessage = ({cmd,sequence,data})=>{
       const res = isIOS? data:JSON.parse(data);
       resolves[sequence]?.(res);
       console.log(`native call js cmd:${cmd}`, res)
   }
   
   if (!window.App) {
       window.App = {
           receiveMessage
       };
   } else if (!window.App.receiveMessage) {
       window.App.receiveMessage = receiveMessage;
   }
   
   const postMessage = (cmd,sequence,data) => {
       if(IOS) {
           window.webkit.messageHandlers.App.postMessage({
               cmd: cmd,
               sequence: sequence,
               data: data
           });
       } else if(Android) {
           window.App.postMessage(JSON.stringify({
               cmd: cmd,
               sequence:sequence,
               data:data
           }));
       }
       console.log(`js call native cmd:${cmd}`, data)
   }
   
   //单次调用
   const callNative = (cmd,data) => {
       const sequence = getUUID();
       postMessage(cmd,sequence,data);
       return new Promise(resolve=>{
           resolves[sequence] = resolve;
       }).then(res=>{
           delete resolves[sequence];
           return res; //利用then链式
       })
   }
   
   //多次调用
   const callNativeKeepalive = *(cmd,data) => {
       const sequence = getUUID();
       postMessage(cmd,sequence,data);
       do {
           const p = new Promise(resolve=>{
               resolves[sequence] = resolve;
           }).then(res=>{
               delete resolves[sequence];
               return res; //利用then链式
           });
           yield await p;
       } while(true); //可以对true添加控制,方便在beforeDestroy销毁
   }
   
   module.exports = {
       callNative,
       callNativeKeepalive
   }

api封装

    export function getDeviceList(data) {
        return callNative('getDeviceList', data)
    }
    export function getDeviceList_L(data) {
        return callNativeKeepalive('getDeviceList', data);
    }

方法调用

    import api from '.api'
      
    methods: {
        //单次调用
        async getDeviceList() {
          const data = await api.getDeviceList();
          //操作devicelist
        },
        //长连接
        async getDeviceList_L(){
          for await (let data of api.getDeviceList_L()) {
            //操作devicelist
          }
        }
    }

重新封装后,单次调用的,调用方式变成了async/await的形式,避免了回调地狱,长连接用for await of调用更具语义性。


  1. 统一对屏幕Safe Area(安全距离)进行了处理。

对于ios,通过safe-area-inset-xxx 设置安全距离xxx为top、left、right和bottom
对于android,通过getAndroidSafeArea回调返回安全区域距离,然后覆盖到css变量的值,使得样式在body下生效

body {
    --safe-top: 0px;
    --safe-left: 0px;
    --safe-right: 0px;
    --safe-bottom: 0px;
    padding-top: calc(10px + constant(safe-area-inset-top) + var(--safe-top));
    padding-top: calc(10px + env(safe-area-inset-top) + var(--safe-top));
    padding-left: calc(10px + constant(safe-area-inset-left) + var(--safe-left));
    padding-left: calc(10px + env(safe-area-inset-left) + var(--safe-left));
    padding-right: calc(10px + constant(safe-area-inset-right) + var(--safe-right));
    padding-right: calc(10px + env(safe-area-inset-right) + var(--safe-right));
    padding-bottom: calc(10px + constant(safe-area-inset-bottom) + var(--safe-bottom));
    padding-bottom: calc(10px + env(safe-area-inset-bottom) + var(--safe-bottom));
}
const data = await getAndroidSafeArea();
if(data.safeTop) {
    window.document.body.style.setProperty('--safe-top',data.safeTop);
    window.document.body.style.setProperty('--safe-left',data.safeLeft);
    window.document.body.style.setProperty('--safe-right',data.safeRight);
    window.document.body.style.setProperty('--safe-bottom',data.safeBottom);
}

4.打包封装
之前也说了,该项目是作为之后H5开发的基座,因此还应具有对应用进行打包的能力,而且最终的目标是将所有H5开发的应用都并入到该项目下,因此该项目需要能打包多个应用。

将src/Page作为应用目录,目录下APP1、APP2、APP3则分别是一个个独立的H5应用

function getConfig() {
    let config;
    if (process.env.NODE_ENV === 'development') {
        console.log('开发模式');
        const list = fs.readdirSync('./src/Page')
        const pages = {}
        list.map((folder) => {
            pages[folder] = {
                entry: `src/Page/${folder}/main.js`,
                template: 'public/index.html',
                filename: `${folder}.html`,
                title: folder
            }
        })
        config = {
            pages
        }
    } else {
        console.log('生产模式');
        const folder = process.argv[3];
        config = {
            outputDir: `dist/${folder}`,
            publicPath: `./`,
            pages: {
                index: {
                    entry: `src/Page/${folder}/main.js`,
                    title: folder,
                },

            }
        };
    }
    return config;
}

module.exports = {
    ...getConfig(),
    configureWebpack: () => {
        const plugins = [...];
        return {
            resolve: {
                alias: {
                    ...
                },
            },
            plugins
        }
    },
}

对vue.config.js进行如上配置。
开发环境中,NODE_ENV为development,生成multi-page模式的cli配置,每个APP作为一个单页面应用被创建于缓存中,并可以通过/{folderName}.html访问,即APP1.html访问APP1的单页面应用,APP2.html访问APP2,方便进行调试。
生产环境中,通过process.argv[3]获取npm run build XXX中的XXX,想要打包APP1就运行npm run build APP1,打包APP2就运行npm run build APP2……因为设置了outputDir,对应的应用会打包到dist下对应的文件夹中,因此不用担心不同的应用出现路径冲突的情况,并且方便使用npm-run-all的run-p命令进行并行打包


5.顺便说说多语言,和自定义webpack plugin进行关键字替换,还有最长多语言的实现
多语言使用vue-i18n实现。通过new VueI18n创建多语言实例,结合axios和setLocaleMessage进行多语言懒加载
由于使用的axios,多语言并不会被打包到bundle包中,多语言文件存放于public文件夹下,会直接被打包到dist目录下

import VueI18n from 'vue-i18n';
import Vue from 'vue';
import axios from 'axios';

Vue.use(VueI18n)

const i18n = new VueI18n({
    locale: 'en',
    fallbackLocale: 'en',
    messages: {}
});

export const changeLanguage = (language) => {
    return new Promise((resolve, reject) => {
        if (i18n.messages[language]) {
            i18n.locale = language;
            resolve();
        } else {
            axios.get(`./locals/${language}.json`)
                .then(({ data }) => {
                    console.log('加载多语言:', language);
                    i18n.setLocaleMessage(language, data);
                    i18n.locale = language;
                    resolve();
                }).catch(() => {
                    console.warn('加载多语言失败:', language)
                    reject();
                });
        }
    })
}

const init = () => {
    changeLanguage('en')
}

init();

export default i18n;

由于打包完成后,多语言直接放置在dist目录下,因此可以在afterEmit时进行处理,通过nodejs的文件系统方法fs,在afterEmit时访问dist/应用名/多语言文件夹并对多语言进行关键字替换和最长多语言提取操作。

插件实现如下,先定义插件类,然后封装apply方法,并调用afterEmit钩子,然后借由fs访问多语言文件,并通过正则匹配对要替换的字符串进行选中和替换。同时在遍历多语言文件时,存放各个key值对应的最长字符串,并最后写入到lang.json中

const path = require('path');
const folder = process.argv[3];
const localsDir = path.resolve(`dist/${folder}/locals`)
const fs = require('fs');

const wordMap = {
    'str1': '产品'
}

function replaceLanguage(name) {
    const pathanme = localsDir + '/' + name;
    const res = fs.readFileSync(pathanme, { encoding: 'utf8' });
    const reStr = '(' + Object.keys(wordMap).join('|') + ')';
    const re = new RegExp(reStr, 'g');
    const newStr = res.replace(re, (it) => wordMap[it]);
    fs.writeFileSync(pathanme, newStr, 'utf8');
    return newStr;
}

class MyPlugin {
    apply(compiler) {
        compiler.hooks.afterEmit.tapAsync('MyPlugin', (compilation, callback) => {
            const files = fs.readdirSync(localsDir)
            const longStr = {};
            files.forEach(file => {
                const str = replaceLanguage(file);
                const obj = JSON.parse(str);
                //获取最长多语言
                Object.keys(obj).forEach(key => {
                    if(key.includes('------')){
                        return;
                    }
                    if (!longStr[key]) {
                        longStr[key] = obj[key]
                    } else {
                        if (longStr[key].length < obj[key].length) {
                            longStr[key] = obj[key]
                        }
                    }
                })
            })
            fs.writeFileSync(localsDir + '/lang.json', JSON.stringify(longStr,null,4), 'utf8');
            callback();
        }
        );
    }
}


module.exports = MyPlugin;

jquery和js的事件绑定的个人理解

html代码

<html>
<head>
    <style>
        div {
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.055);
        }
    </style>
    <script src="http://code.jquery.com/jquery-latest.js"></script>
</head>

<body>
    <div id="out">
        <div id="mid">
            <div id="inz">

            </div>
        </div>
    </div>
    <script>
		//…………
    </script>
</body>
</html>

一、事件发生顺序

事件发生顺序图
68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303131373130323633313734342e706e67

其中最里层的冒泡和捕获(即上图5和6)在同一时间片,前后顺序根据代码顺序而定。

  <script>
        var out = document.getElementById("out")
        var mid = document.querySelector("#mid")
        var inz = document.querySelector("#inz")

        out.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
        mid.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
        inz.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)

        out.addEventListener('click', function () {
            console.log('捕获:' + this.id)
        }, true)
        mid.addEventListener('click', function () {
            console.log('捕获:' + this.id)
        }, true)
        inz.addEventListener('click', function () {
            console.log('捕获:' + this.id)
        }, true)

    </script>

68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303131373131313633343430392e706e67

 <script>
        var out = document.getElementById("out")
        var mid = document.querySelector("#mid")
        var inz = document.querySelector("#inz")

        out.addEventListener('click', function () {
            console.log('捕获:' + this.id)
        }, true)
        mid.addEventListener('click', function () {
            console.log('捕获:' + this.id)
        }, true)
        inz.addEventListener('click', function () {
            console.log('捕获:' + this.id)
        }, true)

        out.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
        mid.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
        inz.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
    </script>

68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f323031393031313731313139333537362e706e67

事件冒泡、事件捕获和事件委托详细内容可参考该网页:https://www.cnblogs.com/Chen-XiaoJun/p/6210987.html


二、jquery绑定事件

jquery常用绑定事件方法有.on() .bind() .one()
.one() 使用one绑定的事件只会触发一次。
.bind() 截至jQuery 3.0,.bind()已被弃用。它被.on()自jQuery 1.7以来将事件处理程序附加到文档的方法所取代,因此不鼓励使用它。
.on() 可以通过第二个参数绑定子控件实现事件委托,如

 $("#mid").on('click',"#inz",function () {
            console.log("jquery on:" + this.id)
        })

on() .bind() .one()都是可以通过事件名称绑定事件。
事件名包括click绑定单击、dblclick绑定双击、mousemove绑定鼠标移动等。
jquery还有.click(f).dblclick(f).mousemove(f)等方法,相当于.on('click',f).on('dblclick',f).on('mousemove',f)的简写

jQuery 事件方法:http://www.runoob.com/jquery/jquery-ref-events.html

jquery的on() bind() click() one()等方法绑定的事件,都是在事件冒泡阶段触发。

<script>
        var out = document.getElementById("out")
        var mid = document.querySelector("#mid")
        var inz = document.querySelector("#inz")

        $("#mid").on('click',function(){
            console.log('on:' + this.id)
        })

        $("#mid").bind('click',function(){
            console.log('bind:' + this.id)
        })

        $("#mid").one('click',function(){
            console.log('one:' + this.id)
        })

        $("#mid").click(function(){
            console.log('click:' + this.id)
        })
 
        mid.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
        inz.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
    </script>

68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f323031393031313731313233353636372e706e67


三、jquery事件委托

用on()的参数绑定选择器虽然可以给子控件绑定事件,但是事件还是在冒泡到的父控件时才执行(即将绑定到子控件的事件委托给父控件执行);同时,该事件先于该父控件用jquery绑定的其他事件
1.当inz的冒泡事件阻止事件传输后,通过事件委托绑定到inz上的事件未触发。这是因为用$("#mid").on('click','#inz',f)虽然把事件绑定到了inz上,但是事件的执行却被委托给了父控件mid

 <script>
        var out = document.getElementById("out")
        var mid = document.querySelector("#mid")
        var inz = document.querySelector("#inz")

        $("#mid").on('click',function(){
            console.log('on:' + this.id)
        })

        $("#mid").on('click','#inz',function(){
            console.log('on:' + this.id)
        })
 
        mid.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
        inz.addEventListener('click', function (e) {
            console.log('冒泡:' + this.id)
            e.stopPropagation()
        }, false)
    </script>

68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f323031393031313731313238353533342e706e67

2.委托事件先于自身绑定事件(通过jquery绑定的)。

 <script>
        var out = document.getElementById("out")
        var mid = document.querySelector("#mid")
        var inz = document.querySelector("#inz")

        $("#mid").on('click',function(){
            console.log('on:' + this.id)
        })

        $("#mid").on('click','#inz',function(){
            console.log('on:' + this.id)
        })
 
        mid.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
        inz.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
    </script>

68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303131373131323830343238342e706e67

 <script>
        var out = document.getElementById("out")
        var mid = document.querySelector("#mid")
        var inz = document.querySelector("#inz")
 
        mid.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)
        inz.addEventListener('click', function () {
            console.log('冒泡:' + this.id)
        }, false)

        $("#mid").on('click',function(){
            console.log('on:' + this.id)
        })

        $("#mid").on('click','#inz',function(){
            console.log('on:' + this.id)
        })
    </script>

68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303131373131333032333234382e706e67


四、在绑定事件中调用函数

1.this指向
2.call、apply参数区别

给DOM绑定事件时,事件函数的this是指向该DOM的,但是在事件函数中再调用其他函数,这时其他函数的this是指向window的。要想其他函数的this也指向DOM,可以使用call()apply()
我顺便用这个例子记一下call()apply()参数的区别

<script>
        var out = document.getElementById("out")
        var mid = document.querySelector("#mid")
        var inz = document.querySelector("#inz")

        mid.addEventListener('click', function () {
            ttt.call(this, '冒', '泡')
            ttt.apply(this, ['冒', '泡'])
            ttt('冒', '泡')
        }, false)

        function ttt(x, y) {
            console.log(x + y + ':' + this)
        }
 </script>

68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303131373130303834333430352e706e67

在npm上发布自己的vue组件库(使用npm install 或者 CDN的方式引用)

在codepen上看到了一个好看的pen,于是把它重写成了一个vue的组件;
为了能在其他地方使用该组件,准备创建一个自己的vue组件库发布到npm上;
因为之前没有发布过,于是就先上网上搜索了一下教程

主要是参考了一下这篇教程还有iviewelement这两个组件库的package.json、index.js、webpack文件。

只看创建流程可以直接看第三部分

一、npm publish

  • 发布包到npm库的命令是npm publish

  • npm publish发布包,需要先配置webpack.json文件,如果没有webpack.json文件,可以通过npm init命令初始化一个

  • package.json的部分字段简介如下

name:发布的包名,默认是上级文件夹名。不得与现在npm中的包名重复。包名不能有大写字母/空格/下滑线!
version:你这个包的版本,默认是1.0.0。对于npm包的版本号有着一系列的规则,模块的版本号采用X.Y.Z的格式,具体体现为:
  1、修复bug,小改动,增加z。
  2、增加新特性,可向后兼容,增加y
  3、有很大的改动,无法向下兼容,增加x
description:项目简介
mian:入口文件,默认是index.js
scripts:包含各种脚本执行命令
test:测试命令。
author:自己的账号名
license:开源文件协议
private:是否私有
  • 如果要发布的话需要把private字段设为false

  • 发布的包的资源可以通过https://unpkg.com/$name@$version/找到。

  • 我们可以通过创建文件npmignore或者 pkg.files来设置上传时过滤某些文件和文件夹,如果我们不设置的话,有些文件和文件夹也是会默认忽略上传的,比如node_modules文件夹、package-lock.json文件等

image
image

二、我们如何通过npm引用组件的

先看一下node.js中的模块调用的规则 https://nodejs.org/api/modules.html#modules_accessing_the_main_module

主要注意以下两部分
image

require(X) from module at path Y
1. If X is a core module,
   a. return the core module
   b. STOP
2. If X begins with '/'
   a. set Y to be the filesystem root
3. If X begins with './' or '/' or '../'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
4. LOAD_NODE_MODULES(X, dirname(Y))
5. THROW "not found"

LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text.  STOP
2. If X.js is a file, load X.js as JavaScript text.  STOP
3. If X.json is a file, parse X.json to a JavaScript Object.  STOP
4. If X.node is a file, load X.node as binary addon.  STOP

LOAD_INDEX(X)
1. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
3. If X/index.node is a file, load X/index.node as binary addon.  STOP

LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
   a. Parse X/package.json, and look for "main" field.
   b. If "main" is a falsy value, GOTO 2.
   c. let M = X + (json main field)
   d. LOAD_AS_FILE(M)
   e. LOAD_INDEX(M)
   f. LOAD_INDEX(X) DEPRECATED
   g. THROW "not found"
2. LOAD_INDEX(X)

LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
   a. LOAD_AS_FILE(DIR/X)
   b. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = [GLOBAL_FOLDERS]
4. while I >= 0,
   a. if PARTS[I] = "node_modules" CONTINUE
   b. DIR = path join(PARTS[0 .. I] + "node_modules")
   c. DIRS = DIRS + DIR
   d. let I = I - 1
5. return DIRS

以iview为例,我们npm i iview下载iview组件库后,通过import iView from 'iview'引用它,其实就是通过import iView from './node_modules/iview'引用它,然后./node_modules/iview是一个文件夹,会按照LOAD_AS_DIRECTORY(X)中的规则寻找,先是找到./node_modules/iview/package.json这个文件,然后找到其中的main字段,最后通过main字段找到./node_modules/iview/dist/iview.js这个文件。
image
iview.js这个文件是通过webpack打包得到的,直接看代码基本上是看不懂的,我们看看它是怎么打包出来的。


先找到iview.js的webpack配置文件

https://github.com/iview/iview/blob/2.0/build/webpack.dist.dev.config.js

……
process.env.NODE_ENV = 'production';
module.exports = merge(webpackBaseConfig, {
    devtool: 'source-map',
    entry: {
        main: './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, '../dist'),
        publicPath: '/dist/',
        filename: 'iview.js',
        library: 'iview',
        libraryTarget: 'umd',
        umdNamedDefine: true
    },
    ……
});

上面是在生产环节下打包iview.js的配置,我们找到它的入口文件

https://github.com/iview/iview/blob/2.0/src/index.js

import Affix from './components/affix';
……

const components = {
    Affix,
    ……
};

const iview = {
    ...components,
    iButton: Button,
    ……
};

const install = function(Vue, opts = {}) {
    if (install.installed) return;
    ……

    Object.keys(iview).forEach(key => {
        Vue.component(key, iview[key]);
    });

    ……
};

// auto install
if (typeof window !== 'undefined' && window.Vue) {
    install(window.Vue);
}

const API = {
    ……
    install,
    ……
};

……

module.exports.default = module.exports = API;   // eslint-disable-line no-undef

从该文件可以看出来,它先是将components文件夹中的组件都通过import引入,然后定义了一个components对象来存放所有组件,然后又对部分组件增加了第二个key定义了iview对象(例如<Col><Col>组件,也可以通过<i-col><i-col>来使用),然后定义了一个install函数来遍历注册组件。最后将install函数作为输出对象的install方法。

然后这个输出对象其实就是上面import iView from 'iview'得到的iView对象,因此通过Vue.use(iView)来调用iView.install方法就能成功的把iView库的组件注册到我们的Vue对象上。

另外,如果是直接通过CDN引用js文件的话,会触发auto install注释下的内容,会将组件注册到当前window.Vue上(需要先通过CDN引入vue.js)

三、流程

  1. 使用vue init webpack-simple xxx初始化项目
  2. npm install
  3. 在src目录下新建components文件夹,并将组件放入其中
  4. 在根目录下新建index.js
import XXX from './src/components/XXX'
import YYY from './src/components/YYY'
import ZZZ from './src/components/ZZZ'
……

const components = [
  XXX,YYY,ZZZ,……
];

const install = function (Vue) {
  components.forEach(component => {
    Vue.component(component.name, component);
  });
};

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}

export default {
  install,
  XXX,
  YYY,
  ZZZ,
  ……
};

这里说明一下,我是用组件内部的name属性做的组件标签名
5. 配置webpack.config.js

var path = require('path')
var webpack = require('webpack')
const NODE_ENV = process.env.NODE_ENV

module.exports = {
  //入口这里一个是测试组件时npm run dev的入口文件,一个是npm run build的入口文件
  entry: NODE_ENV === 'development' ? './src/main.js' : './index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/', //输出的文件夹
    filename: 'filename.js',//输出的文件名
    library: 'objectname',    //引用js文件时创建的全局变量名
    libraryTarget: 'umd',
    umdNamedDefine: true
  },
  ……
}

……
  1. 配置package.json
{
  "name": "package-name",
  "description": "描述",
  "version": "1.0.0",
  "author": "XXXXXXXXXXX",
  "license": "MIT",
  "private": false,
  "main": "dist/filename.js",
  ……
}
  1. npm run build
  2. npm publish
  3. 使用
//npm导入
npm i package-name

import xxx from 'package-name'
Vue.use(xxx)

//cdn导入
<script src="//vuejs.org/js/vue.min.js"></script>
<script src="//https://unpkg.com/[email protected]/dist/filename.js"></script>
  1. 如果想把css单独抽离出来还需要更新webpack、webpack-cli,和安装optimize-css-assets-webpack-plugin、uglifyjs-webpack-plugin等包。

<iframe height="600" style="width: 100%;" src="//codepen.io/pma934/embed/YmGJOe/?height=600&theme-id=dark&default-tab=js,result"></iframe> <iframe src="https://codesandbox.io/embed/vue-template-39zzw?fontsize=14" title="Vue Template" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media" style="width:100%; height:800px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

红宝书 笔记14

有道云

表单脚本

  • 提交表单
    <input type="submit" value="Submit Form">
  • 重置表单
    <input type="reset" value="Reset Form">

elements 该属性是表单中所有表单元素的集合

//每个表单字段

focus() 获得焦点

blur() 失去焦点

文本框脚本

  • 单行
    <input type="text" size=" " maxlength=" " value="init value">
  • 多行
    <textarea rows=" " clos=" ">init value</textarea>

过滤输入

通过设置keypress事件

==操作剪切板==

  • beforecopy
  • copy
  • beforecut
  • cut
  • beforepaste
  • paste

HTML5约束验证API

  • required 必填项

  • 输入类型

    • email
    • password
    • url
    • 数值类型 这类可以指定min max step属性
      • number
      • range
      • date
      • month
      • week
      • time
      • datetime
      • datetime-local
    • search
    • color

新增pattern属性,可以使用正则表达式进行约束
<input type="text" pattern="\d+" name="count">

检测有效性

使用checkValidity()方法可以检测表单中的某个字段是否有效。是否有效的判断依据是一些<input>的约束条件。

  • checkValidity() 表单验证方法

常用绑定事件

  • keypress
  • change
  • blur

==富文本选区==

==DOM选区在红宝书12章 DOM范围==

下面使用的的Range就是DOM选区

范围选择()
window.getSelection()或者document.getSelection() 建

Selection对象属性:

  • anchorNode
    返回该选区起点所在的节点(Node)。
  • anchorOffset
    返回一个数字,其表示的是选区起点在 anchorNode 中的位置偏移量。
    如果 anchorNode 是文字节点,那么返回的就是从该文字节点的第一个字开始,直到被选中的第一个字之间的字数(如果第一个字就被选中,那么偏移量为零)。
    如果 anchorNode 是一个元素,那么返回的就是在选区第一个节点之前的同级节点总数。(这些节点都是 anchorNode 的子节点)
  • focusNode
    返回该选区终点所在的节点。
  • focusOffset
    返回一个数字,其表示的是选区终点在 focusNode 中的位置偏移量。
    如果 focusNode 是文字节点,那么选区末尾未被选中的第一个字,在该文字节点中是第几个字(从0开始计),就返回它。
    如果 focusNode 是一个元素,那么返回的就是在选区末尾之后第一个节点之前的同级节点总数。

Selection对象方法:

  • isCollapsed
    返回一个布尔值,用于判断选区的起始点和终点是否在同一个位置。
  • rangeCount
    返回该选区所包含的连续范围的数量。
  • getRangeAt
    返回选区开始的节点(Node)。
  • collapse
    将当前的选区折叠为一个点。
  • extend
    将选区的焦点移动到一个特定的位置。
  • modify
    修改当前的选区。
  • collapseToStart
    将当前的选区折叠到起始点。
  • collapseToEnd
    将当前的选区折叠到最末尾的一个点。
  • selectAllChildren
    将某一指定节点的子节点框入选区。
  • addRange
    一个区域(Range)对象将被加入选区。
  • removeRange
    从选区中移除一个区域。
  • removeAllRanges
    将所有的区域都从选区中移除。
  • deleteFromDocument
    从页面中删除选区中的内容。
  • selectionLanguageChange
    当键盘的朝向发生改变后修改指针的Bidi优先级。
  • toString
    返回当前选区的纯文本内容。
  • containsNode
    判断某一个node是否为当前选区的一部分。

document.execCommand

当一个HTML文档切换到设计模式时,document暴露 execCommand 方法,该方法允许运行命令来操纵可编辑内容区域的元素。

bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)

一个 Boolean ,如果是 false 则表示操作==不被支持==或==未被启用==。

注意:在调用一个命令前,不要尝试使用返回值去校验浏览器的兼容性

比如,复制选区(选区就是上面介绍的 ==window.getSelection()== 或者==document.getSelection()== )

document.execCommand('copy')

属性和方法具体见:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand

Range补充笔记

在document中可以使用document.createRange()创建一个选区,也可以new Range()创建

也可以用Selection对象的getRangeAt方法取得

属性

  • Range.collapsed 只读
    返回一个用于判断 Range 起始位置和终止位置是否相同的布尔值。
  • Range.commonAncestorContainer 只读
    返回包含 startContainer 和 endContainer 的最深的节点。
  • Range.endContainer 只读
    返回包含 Range 终点的节点。
  • Range.endOffset 只读
    返回 endContainer 中表示Range终点位置的数字。
  • Range.startContainer 只读
    返回包含 Range 开始的节点。
  • Range.startOffset 只读
    返回 startContainer 中表示 Range 起始位置的数字。

方法

定位方法

我们没有在此列举继承方法。

  • Range.setStart()
    设置 Range 的起点。
  • Range.setEnd()
    设置 Range 的终点。
  • Range.setStartBefore()
    以其它节点 ( Node)为基准,设置 Range 的起点。
  • Range.setStartAfter()
    以其它节点为基准,设置 Range 的始点。
  • Range.setEndBefore()
    以其它节点为基准,设置 Range 的终点。
  • Range.setEndAfter()
    以其它节点为基准,设置 Range 的终点。
  • Range.selectNode()
    设定一个包含节点和节点内容的 Range 。
  • Range.selectNodeContents()
    设定一个包含某个节点内容的 Range 。
  • Range.collapse()
    向指定端点折叠该 Range 。

编辑方法

通过以下方法,可以从 Range 中获得节点,改变 Range 的内容。

  • Range.cloneContents()
    返回 Range 当中节点的文档片段(DocumentFragment)。
  • Range.deleteContents()
    从文档(Document)中移除 Range 中的内容。
  • Range.extractContents()
    把 Range 的内容从文档树移动到文档片段中。
  • Range.insertNode()
    在 Range 的起点处插入节点。
  • Range.surroundContents()
    将 Range 的内容移动到一个新的节点中。

示例

选中全文(相当于在浏览器按ctrl+a)

var select = document.getSelection()
var range = document.createRange()
range.selectNode(document.body)
select.addRange(range)

或者

必须要已经有选中区域才行,不然var range = select.getRangeAt(document.body)会报错,但是省去了插入区域到选区的步骤

var select = document.getSelection()
var range = select.getRangeAt(document.body)
range.selectNode(document.body)

红宝书 笔记 21~24

有道云21
有道云22
有道云23
有道云24

21

XMLHttpRequest

Ajax是Asynchronous Javascript + XML的简写

核心技术XMLHttpRequest对象

(旧版本 remote scripting)

var xhr = new XMLHttpRequesr();

GET
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("get", url, false);
xmlhttp.send(null);
        ···
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function(){
    if(xmlhttp.status ···){
        ···
    }else{
        ···
    }
}
xmlhttp.open("get", url, true);
xmlhttp.send(null);
POST
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("post", url, false);
xmlhttp.send(···);
        ···
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function(){
    if(xmlhttp.status ···){
        ···
    }else{
        ···
    }
}
xmlhttp.open("post", url, true);
xmlhttp.send(···);

XMLHttpRequest 2 级

FormData

超时
timeout 属性

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型

跨域资源共享

比如客户端 请求头 Origin:www.xxx.cn

如果服务器 的Access-Control-Allow-Origin是'*',或者包扩 www.xxx.cn,那服务器就会接受请求
跨域请求不带cookie

跨域资源共享

对CORS的实现

IE使用 XDomainRequest

其他浏览器 对XMLHttpRequest实现了CORS原生支持
跨域XHR对象有一些限制(为了安全)

  • 不能使用setRequestHeader()设置自定义头部
  • 不能发送和接收cookie
  • 调用getAllResponseHeaders()方法总会返回空字符串

貌似有些请求头可以自定义了
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*') 就成功了

其他跨域技术

图像Ping

JSONP

22

惰性载入函数

//举个栗子
var xxx = function(){
    if(···){
        xxx = function(){··a··}   
    }else{
        xxx = function(){··b··}  
    }
}

函数绑定

bind()用闭包怎么实现?

函数柯里化

维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

优点:

  • 参数复用
  • 优化代码量,emmm
  • 方法封装

缺点:

  • 存取arguments对象通常要比存取命名参数要慢一点
  • 一些老版本的浏览器在arguments.length的实现上是相当慢的
  • 使用fn.apply( … ) 和 fn.call( … )通常比直接调用fn( … ) 稍微慢点
  • 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上

防篡改对象

不可扩展对象<密封的对象<冻结的对象

高级定时器

javascript是单线程的,js中定时器不是线程,仅仅只是计划在未来的某个时间执行(加入队列),执行时间也并不能保证(浏览器负责排序)

javascript 中没有任何代码是立刻执行的,但一旦进入进程空闲则尽快执行

每次执行完一套代码后,javascript会返回很短的时间,防止页面锁定

函数节流(看起来是防抖)

DOM操作比非DOM操作需要更多内存和CUP时间

例子:节流

//红宝书写法
function throttle(method,context){
    clearTimeout(method.tId);
    method.tId = setTimeout(function(){
        method.call(context)
    },100)
}

自己写的

let throttle = function () {
  let flag = true
  return function (fn, x) {
    if (flag) {
      fn(x)
    }
    flag = false
    setTimeout(function () {
      flag = true
    }, 500)
  }
}()
window.onresize = function () {
  throttle(console.log, window.innerHeight)
}

例子:防抖

//借鉴红宝书节流的写法
function debounce(method,context){
    if(!method.tId){
        method.tId = setTimeout(function(){
            method.call(context)
            method.tId = null
        },100)
    }
}

自己写的

let debounce = function () {
  let settimer;
  return function (fn, x) {
    clearTimeout(settimer)
    settimer = setTimeout(function () {
      fn(x)
    }, 500)
  }
}()
window.onresize = function () {
  debounce(console.log, window.innerHeight)
}

节流:间隔n执行一次

防抖:在间隔n内执行的操作,只有最后一次有效

函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。

image

函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。如下图,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
image


23

Web存储机制

Web Storage规范包含两种对象的定义:sessionStorageglobalStorage。这两个对象在支持的浏览器中都是以windows对象属性的形式存在。

localStorage取代了globalStorage么?

//???
'globalStorage' in window
//false
'sessionStorage' in window
//true

Storage类型

Storage类型提供最大的存储空间来存储名值对。

  • clear():删除所有值
  • getItem(name):根据指定的名字name获取对应的值
  • key(index):获得index位置处的值的名字
  • removeItem(name):删除由name指定的名值对
  • setItem(name, value):为指定的name设置一个对应的值
  • sessionStorage对象

sessionStorage对象

sessionStorage对象存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。存储在sessionStorage中的数据==可以跨越页面刷新==而存在。

//使用方法存储数据
sessionStorage.setItem("name", "Name");
//使用属性存储数据
sessionStorage.book = "JavaScript";

//使用方法读取数据
var name = sessionStorage.getItem("name");
//使用属性读取数据
var book = sessionStorage.book;

//使用delete 删除一个值——在WebKit 中无效
delete sessionStorage.name;
//使用方法删除一个值
sessionStorage.removeItem("book");

可以通过结合length属性和key()方法来迭代sessionStorage中的值:

for (var i = 0, len = sessionStorage.length; i < len; i++) {
	var key = sessionStorage.key(i);
	var value = sessionStorage.getItem(key);
	console.log(key + ' = ' + value);
}
//只输出本身的属性

还可以使用for-in循环来迭代sessionStorage中的值:

for (var key in sessionStorage) {
	var value = sessionStorage.getItem(key);
	console.log(key + ' = ' + value);
}
//把原型的属性都输出来了

globalStorage对象

这个对象的目的是跨越会话存储数据,,但有特定的访问限制。要使用globalStorage,首先要指定哪些域可以访问该数据。

localStorage对象

localStorage对象是HTML5规范中作为持久保存客户端数据的方案,并且取代globalStorage。要访问同一个localStorage对象,页面必须来自同一个域名(子域名无效),必须同源。

24

性能优化

注意作用域

避免全局查找

使用全局变量和函数的开销要比局部的开销大,因为要涉及作用域链上的查找。

function(){
    var node1 = document.getElementById(xxx)
    ···
    var node1 = document.getElementById(xxx)
    ···
}

上面这段代码多次引用全局document对象,==每次都会进行作用域链接查找==,如果将document对象存在作用域链接本地的变量中,然后在余下代码中使用本地变量替换原来的doc,与原来相比就只会进行==一次作用域链接查找==,可以减少开销。

function(){
    var doc = document
    var node1 = doc.getElementById(xxx)
    ···
    var node1 = doc.getElementById(xxx)
    ···
}

==将一个函数中会多次用到的全局对象储存为局部变量总是没错的==

避免with语句

with会增加代码作用域链长度
必须使用with语句的情况很少,大多数时候可以用局部变量完成相同的事情

javascript红宝书 笔记(1~5)

有道云地址

在html中使用javascript

script标签

async :异步脚本,建议不要在加载时修改DOM 一定在load事件前执行,但是可能在DOMContentLoaded事件触发之前或之后执行

defer :脚本延迟到整个页面解析完毕再运行

基本概念

严格模式

‘use strict’

关键字和保留字

关键字(语言保留的不能用作标识符)

break、else、new、var、 case、 finally 、 return、 void 、 catch 、for 、switch 、 while 、 continue、 function 、this 、 with 、default 、 if 、 throw 、 delete 、 in 、 try 、do 、 instranceof、 typeof

let 和 const定义已经定义过的标识符会报错

a=1
let a = 2
VM1053:1 Uncaught SyntaxError: Identifier 'a' has already been declared
    at <anonymous>:1:1

保留字(目前无用途,将来可能用作关键字)

abstract 、 enum 、int 、 short 、 boolean 、export 、interface、 static、 byte 、extends 、 long 、 super 、 char 、 final 、native 、synchronized 、 class 、float 、 package 、throws 、 const 、goto 、private 、transient 、 debugger 、 implements 、protected 、 volatile 、 double 、import 、public

变量

未定义的初始化值为 undefined

数据类型

简单数据类型:Undefined、Null、Bool、Number、String

复杂数据类型:Object

函数:Function


基本的数据类型有:`undefined,boolean,number,string,null.基本类型的访问是按值访问的

javascript中除了上面的基本类型(number,string,boolean,null,undefined)之外就是引用类型


ECMAScript中所有类型都有与Boolean值等价的值,可用Boolean()转化

数据类型 转化为true的值 转化为false的值
Boolean true false
String 非空字符串 空字符串
Number 非零数字 0或者NaN
Object 任何对象 null
Undefined n/a undefined

数值

  • 负无穷:Number.NEGATIVE_INFINITY -Infinity
  • 正无穷:Number.POSITIVE_INFINITY Infinity
  • 最大数值:Number.MAX_VALUE 1.7976931348623157e+308
  • 最小数值:Number.MIN_VALUE 5e-324

10进制中 e的用法

toString(x)

在调用数值的toString方法,可以传递一个参数:输出数值的基数。没有参数则返回该值的10进制量

==!浮点数有舍入误差: console.log(0.1+0.2==0.3) //false==

isFinite()

测试一个数值是不是无穷值。

Number()

a = Number(b) 等同 a = +b

parseInt()

可以使用第二个参数,表示被解析值的基数
parseInt('111',2) // 7

parseFloat()

只解析十进制

转义字符

转义字符 意义
\b 退格(BS) ,将当前位置移到前一列
\f 换页(FF),将当前位置移到下页开头
\n 换行(LF) ,将当前位置移到下一行开头
\r 回车(CR) ,将当前位置移到本行开头
\t 水平制表(HT) (跳到下一个TAB位置)
\ 代表一个反斜线字符'''
' 代表一个单引号(撇号)字符
" 代表一个双引号字符
\unnn 以16进制代码nnnn表示一个Unicode字符
\xhh 1到2位十六进制所代表的任意字符

字符串

字符

charCodeAt() : 返回的字符的unicode编码。

'9'.charCodeAt() //57

  • ECMAScript中的字符串是不可变的
  • toString()

在调用数值的toString方法,可以传递一个参数:输出数值的基数。没有基数的则返回该值的字面量

若a有toString方法,则String(a)a.toString()返回相同结果
还可以通过+""实现转化为字符串

object类型

Object类型的属性方法:

  • constructor
  • hasOwnProperty(propertyName)
  • isPrototypeOf(obj)
  • propertyIsEnumerable(propertyName)
  • toLocalString()
  • toString()
  • valueOf()

操作符

  • ~ 按位非

  • & 按位与

  • | 按位或

  • ^ 按位异或

  • << 左移

  • >> 有符号位右移

  • >>> 无符号位右移

  • == 转换后比较

  • === 直接比较

true == 1 //true
true === 1 //false

NaN<3  //false
NaN>=3 //false

函数

arguments 在箭头函数中不能用

ECMAScript中所以函数的参数都是按值传递的

callee 与 caller

function diff_caller_callee(k) {
    return function (k) {
        console.log(arguments.callee) 
        console.log(arguments.callee.caller) 
        if (k) {
            arguments.callee(k - 1)
        }
    }(k)
}
diff_caller_callee(1)

Array

迭代方法

ECMAScript5为数组定义了5个迭代方法。

  • every(): 对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。
  • filter(): 对数组中的每一项运行给定函数,返回该函数会返回true 的项组成的数组。
  • forEach(): 对数组中的每一项运行给定函数。这个方法没有返回值。
  • map(): 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  • some(): 对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。

归并方法

ECMAScript 5 还新增了两个归并数组的方法。

  • reduce()
  • reduceRight()

reduce()和reduceRight()的函数接收4 个参数:前一个值、当前值、项的索引和数组对象。

values.reduce((prev, cur, index, array)

Date类型

使用+可以直接获取Date对象的时间戳

var a = new Date()
a.toDateString()
//"Wed May 15 2019"
a.toTimeString()
//"14:30:28 GMT+0800 (**标准时间)"
a.toLocaleDateString()
//"2019/5/15"
a.toLocaleTimeString()
//"下午2:30:28"
a.toUTCString()
//"Wed, 15 May 2019 06:30:28 GMT"

RegExp类型

3个标志符

  • g(全局模式)
  • i(不区分大小写)
  • m(多行)

所有RegExp实例含有下列属性

  • global :是否设置了g标志
  • ignoreCase :是否设置了i标志
  • lastIndex :开始搜索下一个匹配项的字符位置
  • multiline :是否设置了m标志
  • source :正则表达式的字符串表示

Function类型

函数名实际上是一个指向函数对象的指针

  • 函数声明与函数表达式

解析器会率先读取函数声明,并使其在执行任何代码之前可用;至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

// ok
alert(sum(10,10));
function sum(num1, num2){
	return num1 + num2;
}

// unexpected identifier(意外标识符)
alert(sum(10,10));
var sum = function(num1, num2){
	return num1 + num2;
};
  • 函数内部属性
    • arguments
    • this

arguments具有一个callee属性,该属性是一个指针,指向拥有这个arguments对象的函数。

ECMAScript 5也规范了一个函数对象属性:caller(看着很像callee),这个属性中保存着调用当前函数的函数的引用,如果实在全局作用域中调用当前函数,它的值为null。

function outer(){
	inner();
}
function inner(){
	alert(inner.caller);
}
outer();

inner.caller指向outer()。为了实现更松散的耦合,也可以通过argument.callee.caller来访问相同的信息。

function outer() {
	inner();
}
function inner() {
	alert(arguments.callee.caller);
}
outer();

严格模式 arguments.callee、arguments.caller 不能使用

function factorial(num){
	"use strict"
	if (num <=1) {
		return 1;
	} else {
		return num * arguments.callee(num-1)
	}
}

factorial(55)
//VM221880:6 Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

this引用的是函数执行的环境对象

基本包装类型

使用new调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。

var value = '25';
var number = Number(value);	// 转型函数
console.log(typeof number);	// 'number'

var obj = new Number(value); // 构造函数
console.log(typeof obj);	// 'object'

toFixed() 按指定小数位返回数值的字符串表示

var num = 10
num.toFixed(2) //10.00

toPrecision()

var num = 99
num.toPrecision(1) //1e+2

String 类型

charAt() 返回 字符串 指定位置的字符 //也可以直接方括号+下标访问,区别如下:

a='aafaadsf'
a.charAt(9) //""
a[9] //undefined
  • charCodeAt() :返回字符串指定位置的字符编码

  • fromCharCode()方法 :这个方法的任务是接收一个或多个字符编码,然后将它们转换成一个字符串。相当于charCodeAt()反操作。


  • concat() 拼接任意多个字符,不改变原字符
  • slice()、substring()、substr()
  • slice()、substring() 的第二个参数表示子字符串结束位置的下一个位置
  • substrate() 的第二个参数表示子字符串的长度
var stringValue = "hello ";
var result = stringValue.concat("world", "!");
alert(result); //"hello world!"

var stringValue = "hello world";
alert(stringValue.slice(3)); //"lo world"
alert(stringValue.substring(3)); //"lo world"
alert(stringValue.substr(3)); //"lo world"
alert(stringValue.slice(3, 7)); //"lo w"
alert(stringValue.substring(3,7)); //"lo w"
alert(stringValue.substr(3, 7)); //"lo worl"

// 参数是负值的情况下,它们的行为就不尽相同了。
// 其中,slice()方法会将传入的负值与字符串的长度相加,
// substr()方法将负的第一个参数加上字符串的长度,而将负的第二个参数转换为0。
// 最后,substring()方法会把所有负值参数都转换为0。
alert(stringValue.slice(-3)); //"rld"
alert(stringValue.substring(-3)); //"hello world"
alert(stringValue.substr(-3)); //"rld"
alert(stringValue.slice(3, -4)); //"lo w"
alert(stringValue.substring(3, -4)); //"hel"
alert(stringValue.substr(3, -4)); //""(空字符串)

//substring() 两个参数可以调换位置,其总是显示两个参数之间的差值的部分
stringValue.substring(1,6); //"ello "
stringValue.substring(6,1); //"ello "

字符串位置方法

  • indexOf() 从头开始寻找
  • lastIndexOf() 从尾部开始寻找
    两个方法的第二个参数,表示从字符串中哪个位置开始搜索。

trim() 删除字符串前置和后缀的所有字符

--

toUpperCase() //转大写

toLowerCase() //转小写

--

字符串的模式匹配方法

  • match()
  • search()
  • replace()
  • split()
    match()方法,在字符串上调用这个方法,本质上和调用RegExp的exec()方法相同。match()方法只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象。
var text = 'cat, bat, sat, fat';
var pattern = /.at/;

// 等价于 pattern.exec(text)
var matches = text.match(pattern);
alert(matches.index); //0
alert(matches[0]); //"cat"
alert(pattern.lastIndex); //0

search()方法的参数与match()方法相同,该方法返回字符串中第一个匹配项的索引,没有匹配项返回-1;个人认为serch()就是正则版的indexOf()

var text = "cat, bat, sat, fat";
var pos = text.search(/at/);
aler t(pos); //1

ECMAScript提供了replace()方法,该方法接受两个参数,第一个参数可以是RegExp对象或者是一个字符串,第二个参数可以是一个字符串或者一个函数。

var text = "cat, bat, sat, fat";
var result = text.replace("at", "ond");
alert(result); //"cond, bat, sat, fat"

result = text.replace(/at/g, "ond");
aler t(result); //"cond, bond, sond, fond"

Global对象
URI编码方法

encodeURI()、encodeURIComponent() 编码
decodeURI()、decodeURICompoent() 解码

==eval()==

Math对象

min、max

var a =[1,23,4]
Math.max.apply(Math,a) //23
Math.max(...a) //23

一个常用于取范围随机整数的方式 [n,m]

Math.floor(Math.random()*(m-n)+n)

ceil 向上取整、floor 向下取整、round 四舍五入

其他常用:abs 绝对值、pow 幂运算、sqrt 平方根

红宝书 笔记(10~13)

有道云

DOM

Node类型
每个节点都有一个nodeType属性,用于表明节点的类型。

  • Node.ELEMENT_NODE(1)
  • Node.ATTRIBUTE_NODE(2)
  • Node.TEXT_NODE(3)
  • Node.CDATA_SECTION_NODE(4)
  • Node.ENTITY_REFERENCE_NODE(5)
  • Node.ENTITY_NODE(6)
  • Node.PROCESSING_INSTRUCTION_NODE(7)
  • Node.COMMENT_NODE(8)
  • Node.DOCUMENT_NODE(9)
  • Node.DOCUMENT_TYPE_NODE(10)
  • Node.DOCUMENT_FRAGMENT_NODE(11)
  • Node.NOTATION_NODE(12)

为了确保跨浏览器兼容,将nodeType属性与数字值进行比较:

对于元素节点,nodeName中保存的始终是元素的标签名,nodeValue的值始终为null

childNodes属性
每个节点都有一个childNodes属性,其中保存着一个NodeList对象,该对象是一种==类数组对象==。==不是快照,会实时改变==
Array.prototype.slice.call(someNode.childNodes)
可以将其转换为数组(IE8及其之前无效)

节点关系
  • parentNode 父节点
  • previousSibling 前一个兄弟
  • nextSibling 后一个兄弟
  • firstChild //会访问到多种类型节点
  • lastChild //会访问到多种类型节点
  • hasChildNodes(比使用childNodes.length方便)
  • ownerDocument 根文档
操作节点
  • appendChild()

用于向childNodes列表的末尾添加一个节点。

  • insertBefore()

把节点放在指定位置,该方法接受两个参数:要插入的节点和作为参考的节点。插入节点后,被插入的节点会变成参照节点的前一个兄弟节点。参照节点是null的话,insertBefore与appendChild执行相同的操作,都插入列表末尾。

  • replaceChild()

替换节点,接受两个参数:要插入的节点和要替换的节点。

  • removeChild()

移除节点,接受一个参数:要被移除的节点。

  • cloneNode()

复制节点,接受一个布尔值,表示是否深复制。复制后返回的节点没有父节点,可以通过插入等操作手动指定。

  • normalize()

处理文档树的文本节点

Document类型

document.all

document.bdoy -> body

==document.documentElement== -> html

document.head -> head

document.domain 域名

document.URL

Element类型

操作特性的方法:

  • getAttribute()
  • setAttribute()
  • removeAttribute()

对于style属性和onclick类型属性在通过getAttribute访问和直接访问时不同
someNode.getAttribute('style') !== someNode.style

someNode.getAttribute('onclick') !== someNode.onclick

someNode.getAttribute('class') === someNode.className

操作表格
// create table
var table = document.createElement('table');
table.border = 1;
table.width = '100%';

// create tbody
var tbody = document.createElement('tbody');
table.appendChild(tbody);

// create row1
var row1 = document.createElement('tr');
tbody.appendChild(row1);

var cell1_1 = document.createElement('td');
cell1_1.appendChild(document.createTextNode('Cell 1,1'));
row1.appendChild(cell1_1);

var cell2_1 = document.createElement('td');
cell2_1.appendChild(document.createTextNode('Cell 2,1'));
row1.appendChild(cell2_1);

// create row2
var row2 = document.createElement('tr');
tbody.appendChild(row2);

var cell1_2 = document.createElement('td');
cell1_2.appendChild(document.createTextNode('Cell 1,2'));
row2.appendChild(cell1_2);

var cell2_2 = document.createElement('td');
cell2_2.appendChild(document.createTextNode('Cell 2,2'));
row2.appendChild(cell2_2);

document.body.appendChild(table);

DOM扩展

选择符 API

  • querySelector()方法

querySelector()方法接受一个CSS选择符,返回与该模式匹配的第一个元素,若没有,返回null。

可以通过Document类型调用,也可以通过Element类型调用,后者只会在该元素后代元素的范围内查找匹配的元素。

  • querySelectorAll()方法

querySelectorAll()方法返回的是所有匹配的元素,是一个NodeList实例。

元素遍历

  • childElementCount:返回子元素(不包含文本节点和注释)的个数
  • firstElementChild:指向第一个子元素
  • lastElementChild:指向最后一个子元素
  • previousElementSibling:指向前一个兄弟元素
  • nextElementSibling:指向后一个兄弟元素

HTML5

  • getElementsByClassName()方法

  • classList属性,这个属性是新集合类型DOMTokenList的实例。

    • add(value)
    • contains(value)
    • remove(value)
    • toggle(value)

div.classList.remove("user")

有了classList属性,除非你需要全部删除所有类名,或者完全重写元素的class属性,否则也就用不到className了

焦点管理

document.activeElement属性,始终会引用DOM中前端获得了焦点的元素。

data-自定义数据属性

dataset

document.querySelector('address')
>> <address>​address​</address>​
document.querySelector('address').dataset.myid = '123'
>> "123"
document.querySelector('address')
>> <address data-myid=​"123">​address​</address>​
document.querySelector('address').attributes
>> NamedNodeMap {0: data-myid, data-myid: data-myid, length: 1}
    0: data-myid
    length: 1
    data-myid: data-myid
    __proto__: NamedNodeMap

==插入标记==

  • innerHTML
  • outerHTML
  • insertAdjacentHTML
    • 第一个参数如下
    • 'beforebegin'
    • 'afterbegin'
    • 'beforeend'
    • 'afterend'
//作为前一个同辈元素插入
element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>");

//作为第一个子元素插入
element.insertAdjacentHTML("afterbegin", "<p>Hello world!</p>");

//作为最后一个子元素插入
element.insertAdjacentHTML("beforeend", "<p>Hello world!</p>");

//作为后一个同辈元素插入
element.insertAdjacentHTML("afterend", "<p>Hello world!</p>");

尽量一次性完成innerHTML类操作,可以减少性能损失

scrollIntoView()方法

scrollIntoView方法可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果这个方法传入true作为参数,或者不传参数,那么窗口滚动之后就会让调用元素的顶部与视口顶部 尽可能平齐,如果传入false,调用元素会尽可能全部出现在视口中,不过顶部不一定平齐。

DOM2 和 DOM3

样式

访问元素样式

css属性 JavaScript属性
background-image style.backgroundImage
color style.color
display style.display
font-family style.fontFamily

多数情况下都可以通过简单地转化属性名的格式来实现转换。其中一个不能直接转换的css属性是==float==。这个是由于float是JavaScript中的保留字,因此不能作为属性名。与其对应的JavaScript属性是==cssFloat==。

DOM样式属性和方法

  • cssText 能够访问到style特性中的CSS代码
  • length 应用给元素CSS属性的数量
  • getPropertyCSSValue(propertyName) 返回给定属性值的CSSValue对象
  • getPropertyPriority(propertyName) 给定熟悉是否使用了!important
  • getPropertyValue(propertyName) 返回给定属性的字符串值
  • removeProperty(propertyName) 删除给定属性
  • setProperty(propertyName,value,priority) 将给定属性设置为相应值,并设置优先级 ==priority可以是"important"、undefined、""==

操作样式表 。。。

  • type
  • cssText
  • cssRules

元素大小

偏移量

  • offsetHeight:元素在垂直方向上占用的空间大小。包括元素的高度,(可见的)水平滚动条的高度,上边框高度和下边框高度

  • offsetWidth:元素在水平方向上占用的空间大小。包括元素的宽度,(可见的)垂直滚动条的宽度,左边框宽度和右边框宽度

  • offsetLeft:元素的左外边框至包含元素的左内边框之间的像素距离。

  • offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离。

  • offsetParent 定位最近的祖先元素

offsetParent属性不一定与parentNode的值相等。

客户区大小

  • clientWidth:元素内容区宽度加上左右内边距宽度
  • clientHeight: 元素内容区高度加上上下内边距高度

遍历

document.createNodeIterator()

let iterator = document.createNodeIterator(
  document.querySelector(".v-content"),
  NodeFilter.SHOW_ALL,
  null,
  false
);
let node = iterator.nextNode();
while (node !== null) {
  console.log(node.tagName);
  node = iterator.nextNode();
  }

事件

DOM事件流

“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段处于目标阶段事件冒泡阶段

html事件处理程序

通过event可以直接访问事件对象
this可以访问目标元素

<input type="button" onclick="console.log(event.type+' '+this.type)">
//> click button

通过html指定事件处理的最后一个缺点是html和javascript代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:html代码和javascript代码

DOM0 级事件处理程序

DOM 0级方法指定的事件处理程序被认为是元素的方法,因此,这个时候的事件处理程序是在元素的作用域中运行,也就是说程序中的this可以引用当前元素。

var btn = document.getElementById('myBtn');
btn.onclick = function () {
	console.log(this.id);  // 'myBtn'
}

以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

DOM2 级事件处理程序

  • addEventListener()
  • removeEventListener()

定义了两个方法用于处理指定和删除事件处理程序的操作。所有的DOM节点中都包含这两个方法,接受三个参数:事件名事件处理程序布尔值。最后这个布尔值如果是==true==,表示在==捕获阶段==调用事件处理程序;==false==表示在==冒泡阶段==调用事件处理程序,默认是==false==。

通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除。如果通过addEventListener()添加的匿名函数将无法移除。

btn.addEventListener('click', function () {  //匿名函数
	aler(this.id);
},false);
btn.removeEventListener('click', function () {  //没有用!
	aler(this.id);
},false);
var handler = function () { 
	aler(this.id);
};
btn.addEventListener('click',handler ,false);
btn.removeEventListener('click',handler,false); //有效!

注:大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段(false),这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。

事件对象

在触发DOM上的某个事件时,会产生一个事件对象event。

DOM中的事件对象

event对象成员

属性/方法 类型 读/写 说明
bubbles Boolean 只读 表明事件是否冒泡
cancelable Boolean 只读 表明是否可以取消事件的默认行为
currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素
defaultPrevented Boolean 只读 为true表示已经调用preventDefault()
detail Integer 只读 与事件相关的细节信息
eventPhase Integer 只读 调用事件处理程序的阶段:1 捕获,2 处于目标,3 冒泡
preventDefault() Function 只读 取消事件的默认行为。如果cancelable 是true,则可以使用这个方法
stopImmediatePropagation() Function 只读 取消事件的进一步冒泡或捕获,同时阻止任何事件处理程序被调用
stopPropagation() Function 只读 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法
target Element 只读 事件的目标
trusted Boolean 只读 为true表示事件是浏览器生成,false是开发人员创建
type String 只读 被触发的事件类型
view AbstractView 只读 与事件关联的抽象视图。等同于发生事件的window对象
stopPropagation() 停止事件冒泡和事件捕获
document.documentElement.onclick=function(){
	console.log("html")
}

>> VM74756:2 MouseEvent {isTrusted: true, screenX: 217, screenY: 255, clientX: 217, clientY: 152, …}
>> VM114375:2 html


document.body.onclick=function(){
	console.log("body")
	event.stopPropagation()
}

>> VM74756:2 MouseEvent {isTrusted: true, screenX: 279, screenY: 266, clientX: 279, clientY: 163, …}
>> VM114489:2 body

事件类型

UI事件

  • load事件
  • unload事件
  • resize事件
  • scroll事件

load
window.onload
body标签加上onload特性

焦点事件

  • blur 事件:失去焦点
  • focus 事件:获得焦点

鼠标&滚轮事件

  • click事件
  • dbclick事件
  • mousedown事件:按下鼠标
  • mouseenter事件:光标移入
  • mouseleave事件:光标移出
  • mousemove 事件:鼠标在元素内部移动重复触发
  • mouseout事件:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素
  • mouseover事件:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发
  • mouseup事件:释放鼠标按钮时触发
  • mousewhell 鼠标滚轮事件
document.body.onmouseout = function(){
	let el = event.target
	el.style.border = '2px solid blue'
	el.onmouseleave = function(){
		el.style.border = '2px solid red'
		setTimeout(function(){
			el.style.border = ''
			el.onmouseleave = null
		},500)
	}
}

页面上的所有元素都支持鼠标事件。除了mouseenter 和mouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。

只有在==同一个元素上==相继触发==mousedown== 和==mouseup== 事件,才会触发click 事件;如果mousedown 或mouseup 中的一个被取消,就不会触发click 事件。

鼠标事件属性
  • clientX、clientY 客户区坐标位置
  • pageY、pageX 页面坐标位置
  • screenX、screenY 屏幕坐标位置
  • shiftKey shift是否按下
  • ctrlKey ctrl是否按下
  • altKey alt是否按下
  • metaKey meta/windows是否按下
相关元素
<body>
    <div ……>……</div>
</body>

从div移出

div的mouseout会触发,相关元素为body

与此同时body的mouseover也会触发,相关元素为div

键盘事件

  • keydown 按下任意键
  • keypress 按下字符键
  • onkeyup 释放键
document.onkeyup = function(){
	console.log(event.keyCode)
}
//可以方便你看你按下的键的键码

HTML5事件

  • contextmenu 上下文菜单(右键菜单)
  • beforeunload 离开页面时弹窗
document.oncontextmenu = function(){  ///禁用上下文菜单
	event.preventDefault()
}

模拟事件

var event = document.createEvent('MouseEvents')

event.initMouseEvent('click',true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null)

document.body.dispatchEvent(event)

vue-cli在不同环境下赋予变量不同的值

最近在github上搭个人主页,因为要使用github的api,必须用到owner和repo的值。

https://api.github.com/repos/:owner/:repo/issues

在开发环境下,可以自己赋值;在生产环境下,我则希望直接通过window.location.pathname获得。


在创建vue-cli时会自动配置环境变量,生产环境是production,开发环境是development
68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303230353133353431383632302e706e67
然后在.js和.vue中都可以通过process.env.NODE_ENV获得环境变量的值。
因此如果要设置一个在不同环境下不同的变量,可以通过process.env.NODE_ENV作为key值。
比如:

<script>
  export default {
    name: '',
    data() {
      return {
        repo: {
          "development": "pma934.github.io",
          "production": window.location.pathname,
        }
      }
    },
    methods: {
      xxx: function () {
        console.log(this.repo[process.env.NODE_ENV])
      }
    }
  }
</script>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.