Giter Site home page Giter Site logo

blog-backup's People

Watchers

 avatar  avatar  avatar

blog-backup's Issues

JavaScript Ajax 的前世今生

JavaScript 异步的前世今生

目录

  1. HTTP 基础知识
  2. Ajax
  3. JSONP
  4. CROS
  5. Promise
  6. 实现与优化

HTTP 基础知识

定义

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议

URI

统一资源标识符(英文:Uniform Resource Identifier,缩写:URI),是我们经常提到的统一资源定位符的超集(英文:Uniform Resource Locator,缩写:URL

URL 通常由这些内容组成:

协议://账号:密码@域名:端口/路径?查询#哈希

目前已经很少有将账号URL上面的情况了

HTTP Request / Response

同源策略

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制

如果协议,端口(如果指定了一个)和域名对于两个页面是相同的,则两个页面具有相同的源

Ajax

AjaxAsynchronous JavaScript and XML ,翻译过来就是 异步的JavaScript与XML技术

其中 X 代表的是 XML 这种数据格式,是因为 Ajax 这项技术刚刚出现的时候,主流用于传递的数据格式是 XML,而今更加普遍的数据格式是 JSON,而 Ajax 的名称保留了下来

因而,Ajax 可以获取的数据类型不单单是 XML,可以获取所有类型的数据资源,同时支持 HTTP 协议之外的 FILEFTP 协议

前端 Ajax 的实现主要是基于 XMLHttpRequest API

现代浏览器都提供了 XMLHttpRequest 类,通过实例化 XMLHttpRequest 对象,以及调用一些实例化方法,可以发送相关的异步请求

简单的 Ajax 请求栗子

下面是个简单的发送 Ajax 请求的栗子

// 创建 XHR 请求对象
var xhr = new XMLHttpRequest()

// 启动请求(注意,此时并不会真正发送请求,而是启动请求以备发送)
xhr.open('GET', 'https://api.github.com/events', true)

// 发送请求
xhr.send()

但是仅仅发送请求无法构成完整的前后端交流,XHR 对象提供了接口和状态以获取服务端的返回状态、返回信息

XHR 对象的 readyState 属性标识了 Ajax 请求的生命周期变化

状态 描述
0 UNSET XHR对象已创建,但是还没有调用 .open()
1 OPENED .open() 已被调用
2 HEADERS_RECEIVED .send() 已被调用,同时已经可以获取到响应头的信息
3 LOADING 正在下载响应内容,responseText 属性中可以获取到部分数据
4 DONE 响应结束,异步的生命周期结束

XHR 对象的 status 属性标识了状态码

状态 描述
200 OK 请求成功
304 Not Modified 资源未修改,客户端仍然保留之前下载的副本
400 Bad Request 由于明显的客户端错误,服务器没有处理该请求
404 Not Found 请求失败,资源未在服务器上发现
405 Method Not Allowed 对应请求的资源不支持对应的请求方法
500 Internal Server Error 通用的错误消息,服务器遇到了未曾遇到的状况

XHR 对象提供了 .onreadystatechange() 接口以获取生命周期的状态变化,每次属性 readyState 的变化都会触发该接口的执行

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      // 异步请求成功
      // 获取服务端返回的数据
      var data = xhr.responseText

      // 你就可以根据返回的数据来更新页面啦
    } else {
      // 异步请求失败
    }
  }
}

拓展知识

修改请求头、响应头

GET、POST 在 Ajax 上面的区别

转义

文件上传

首先,文件上传可以通过表单提交,就是在 form 表单中加上 <input type="file" /> 标签,这种上传方式和 JavaScript 的关系不是很大,这里不过多展开

借助 Ajax 也可以实现文件上传,配合 File API 可以实现图片的预览等等

监控请求过程

JSONP

CROS

Promise

Zepto.js 的相关实现

参考资料

HTTP 请求中的 Form Data 与 Request Payload

背景

最近的开发中,使用 Vue.js 技术栈重写过去的项目,遇到如下的问题:

同样的 POST 请求,原始的代码是基于 jQuery

$.ajax({
    url: 'test.php',
    method: 'POST',
    data: {
        name: 'tom',
        age: 12,
    },
})

最后在 Network 请求的详情中,data 数据会被 Form Data 属性携带:

Form Data
    name=tom&age=12

而使用 axios 发送 POST 请求:

const res = await axios({
    url: 'test.php',
    method: 'post',
    data: {
        name: 'tom',
        age: 12,
    },
})

Network 请求的详情中,data 数据会被 Request Payload 属性携带:

Request Payload
    {"name":"tom","age":12}

因为这样的区别,导致异步接口报错

这两者有什么区别呢?

具体区别

首先是 $.ajax 对应 Network 截图

下面是 axios() 对应 Network 截图

区别如下:

  1. HTTP请求报文头部 Content-Type 不同
  2. 请求实体所在位置、结构不同
    • 位置分别是:Request Payload & Form Data
    • 结构分别是:json & query

HTTP报文头部字段 Content-Type

这个报文头一般在请求、响应中都会出现,用于指示报文携带资源的类型

参考资料:

这里值得注意的是,修改 Content-Type 的值时,会改变报文主体所处的位置

Form Data & Request Payload

首先从字面就能了解两者的区别:

  • Form Data 表单数据
  • Request Payload 请求载荷

以原始 XMLHTTPRequest 对象来说:

如果是原始的表单请求,譬如 <form></form> 表单发出的 POST 请求,那么 Content-Type 默认就是 application/x-www-form-urlencoded,报文主体内容在 Form Data 上(可以被 form 表单的 enctype 属性重写

如果请求报文的请求头 Content-Type 设置成 application/x-www-form-urlencoded,则报文主体会被 Form Data 携带

默认情况下都是将报文主体放在 Request Payload

jQuery 实现源码上,可以发现 POST 请求的默认会把 Content-Type 配置成 application/x-www-form-urlencoded

ajax.js in jQuery

{
    ...,
    contentType: "application/x-www-form-urlencoded; charset=UTF-8",
}

ajax.js in jQuery

// Set the correct header, if data is being sent
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
	jqXHR.setRequestHeader( "Content-Type", s.contentType );
}

修复

为了解决这个接口报错,有如下两种方案:

  • 后端接口支持从请求报文 Request Payload 中获取数据
  • 前端请求更换载荷携带方式

因为是前端技术栈的迭代,这里使用前端更换载荷携带方式来实现:

就是实现所有的 axios POST 请求的载荷全部使用 From Data 来取代 Request Payload

这里使用 axios 的拦截器来实现:

参考资料: axios

import axios from 'axios'
import qs from 'qs'

// 创建独立的 axios 实例
const ownAxios = axios.create()

// 挂载请求时的拦截器
ownAxios.interceptors.request.use(config => {
  let _config = { ...config }

  // 兼容 POST 请求
  if (config.method.toLowerCase() === 'post') {
    _config = {
      ..._config,
      headers: {
        ..._config.headers,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      // 默认Form Data中的 载荷是query格式的 这边需要转换
      data: qs.stringify(_config.data),
    }
  }
  
  return _config
})

至此,前端 POST 请求可以全部跑通

更新

axios 原生支持修改 POST 请求的载荷方式:axios

只是在项目中统一使用拦截器会更加方便

JavaScript 原型

目录

  • 面向对象
  • 原型 & 原型链
  • 实现

面向对象

面向对象程序设计 - 维基百科,自由的百科全书

面向对象三大特性:封装继承多态

多数的程序语言借助 类 的概念来实现面向对象编程

JavaScript 中没有经典类的概念:

  • 封装可以借助函数实现
  • 多态可以借助 JavaScript this 的特性来实现

可以通过其他的语言特性实现面向对象的封装多态

继承基本上只能通过 mixins 混合这种方式来处理,同时还伴随着覆盖、先后执行的问题,不是优雅高效的解决方案

JavaScript 通过原型优雅解决了继承的实现问题

原型 & 原型链

JavaScript 中没有类的概念,创建原型对象是基于函数与 new 关键词

function Cat () { this.type = 'cat' }
const cat = new Cat()

一般这种被 new 执行的函数被称为 构造函数

new 关键词做了什么?(new 关键词后面的函数一般称为构造函数

  • 创建空对象 new Object() 或者 {}
  • 设置对象 __proto__ 属性指向构造函数的原型对象
  • 以该对象为 this 执行构造函数
  • 关注构造函数执行的返回值
    • 存在返回值是引用类型,则返回值
    • 返回值是简单类型或者没有返回值,则返回之前创建的对象

其中最需要关注的就是:设置对象 __proto__ 属性指向构造函数的原型对象

proto 做了什么?

首先我们需要解释下 __proto__ 做了什么?

通常来说,每个 JavaScript 对象都会有 __proto__ 这个属性, 这个属性指向当前对象构造函数的原型对象

当代码执行 const value = obj.a ,需要去 obj 对象上寻找 a 这个属性的值,首先会在对象本身的属性中寻找,如果没有找到,则会前往 __proto__ 属性所指向的对象上寻找

既然 __proto__ 所指向的也是对象,那么这个对象也是会有 __proto__ 属性的,那样就会有一直向上回溯的流程

  • 如果最后没有找到所需要的属性,则返回 undefined
  • 一旦找到,则停止搜索返回结果

差不多的流程是下面这样的:

(我是图)

对应的伪代码:

function find(obj, name) {
  for (let key in obj) {
    if (key === name) {
      return obj[name]
    }
  }
  
  let temp = obj
  while (temp = temp.__proto__) {
    for (let key in temp) {
      if (key === name) {
        return temp[name]
      }
    }
  }

  return undefined
}

这样就构成了链式的结构,这大致就是原型链的雏形

(注意:__proto__ 不属于语法标准,但是各大浏览器都实现了这个特性,规范的语法是:一个指向 [[Prototype]]的引用。)

构造函数的原型对象

上面我们了解到:通过 new 关键词创建出来的实例对象会有个 __proto__ 属性,这个属性指向构造函数的原型对象。当获取实例对象上的属性的时候,会随着 __proto__ 属性的指向回溯搜索

下面我们聊聊对象 __proto__ 所指向的 构造函数的原型对象 这个概念

原型对象 prototype

每个函数都会有原型对象:prototype

默认来说,这个对象只会有如下的两个属性:

  • constructor 指向构造函数
  • __proto__ 指向构造函数

constructor 给了我们知道实例对象构造函数的机会,而__proto__则是用于将原型串联起来

复用

构造函数的原型对象很大意义上解决了复用的问题

一般我们在原型对象上实现一些通用的函数/方法

因为通过 __proto__ 属性,每个 new 关键字生成的对象都可以获取对构造函数原型对象的引用

function A() {}
A.prototype.hey = function() { console.log('hey, this is from prototype') }

const a1 = new A()
const a2 = new A()

// 对象 a1 a2 共用相同的 hey 函数
a1.hey === a2.hey // true
保持灵活

而因为 JavaScript 函数中的 this 是运行时决定的值,所以相同的原型对象的函数,会因为执行对象不同,而产生各式的结果

function A(name) {
  this.name = name
}
A.prototype.hey = function() { console.log('hey, tis is ' + this.name) }

const tom = new A('Tom')
const jerry = new A('Jerry')

tom.hey() 	// hey, this is Tom
jerry.hey()	// hey, this is Jerry

通过 new 关键词创造出来的所有实例对象,在没有魔改的情况下,它们都保留了 __proto__ 属性,即对构造函数原型对象的引用

这样就避免了大量对方法/函数的拷贝工作

注意

不过这样也带来了可以通过实例对象修改原型对象的弊端:

function A(name) {
  this.name = name
}
A.prototype.hey = function() { console.log('hey, this is ' + this.name) }

const tom = new A('Tom')
tom.hey() 	// hey, this is Tom

tom.__proto__.hey = function() { console.log("wow, what's wrong?") }

tom.hey() 	// wow, what's wrong?'

继承

至此,我们差不多了解了这样的流程:

(我是图)

简单概括来说,只有下面两个概念:

  • __proto__ 属性可以用来回溯搜索对象的属性
  • new 关键字创造出来的实例对象的 __proto__ 属性指向构造函数的 prototype 属性对象

继承的通俗来说就是把已经存在的类所定义的内容作为自己的内容,并加入若干新的内容

这里需要完善

现在我们要做的目标就是将子构造函数的 prototype 对象的 __proto__ 属性指向父构造函数的 prototype 对象

简单粗暴

function Father () {}
Father.prototype.say = function() { console.log('This is father') }

function Child () { Father.call(this) }
Child.prototype.__proto__ = Father.prototype

其实这个方案,除了不是标准支持之外,都蛮好的

目前我们知道的可以指定 __proto__ 属性的方案,只有使用 new 关键词

借助 new

function Father () {}
Father.prototype.say = function() { console.log('This is father') }

function Child () { Father.call(this) }
Child.prototype = new Father()
Child.prototype.constructor = Child

联系我们上面提到的 new 关键字的作用,prototype 赋值的对象会有__proto__ 属性指向父构造函数的原型对象

但是这种方式会将父构造函数的实例属性也赋值到子构造函数的原型对象上,这不是我们预想的结果

规避父构造函数的实例属性

function Father () {}
Father.prototype.say = function() { console.log('This is father') }

function Middle () {}
Middle.prototype = Father.prototype

function Child () { Father.call(this) }
Child.prototype = new Middle()
Child.prototype.constructor = Child

借助空的中间函数,可以避免污染子构造函数的原型对象

标准方案

function Father () {}
Father.prototype.say = function() { console.log('This is father') }

function Child () { Father.call(this) }
Child.prototype = Object.create(Father.prototype)
Child.prototype.constructor = Child

JavaScript 提供的 Object.create() 翻译一下其实就是下面的代码:

Child.prototype = {
  __proto__: Father.prototype
}

当然,Object.create() 还支持第二个参数,可以追加其他的属性描述对象

参考资料

移动端的 1px 问题

总结、记录下 iOS 移动端 1px 的问题

产生的原因

devicePixelRatio

devicePixelRatio in MDN

这里有两个概念:

  • 物理像素
  • CSS像素

物理像素其实就是买手机的时候,手机参数中的一项,以下面这个 iPhone X 的参数为例:

WX20180320-110923

CSS 像素就是页面布局的容器尺寸

这里 devicePixelRatio 就是 物理像素 / CSS像素 的值

换句话来说,就是布局中的一个像素点对于物理像素中多少个像素点来显示

iPhone 4 的时代,屏幕还没有引入视网膜屏,此时的 devicePixelRatio 的值还是 1

随着视网膜屏以及HiDPI屏的出现普及,devicePixelRatio 的值从 1 到 4 甚至到了 9

也就是编写网页 1px 的元素,在手机屏幕上可能是以 3 * 3 的像素点在进行渲染,会让图片更加清晰

所以这个属性本质上是描述的:

  • 当前显示设备的物理分辨率与CSS像素分辨率的比值
  • CSS像素的大小相对物理像素大小的比值

1px 的问题

所以为了在移动端得到最佳的效果,一般设计稿都是按照移动端设备的物理分辨率来设计的

很多的边框都会被设计成 1px 的宽度,但是在还原设计稿的时候,CSSpx 最小单位通常来说都是 1px

.border-bottom {
    border-bottom: 1px solid #000;
}

随之真机上因为 视网膜屏或HiDPI屏 的原因,就会显示 2px 或者是 3px 的宽度,和期待的效果出入很大

解决问题

了解了出现的原因,下面就看看如何解决这个问题

0.5px

其实第一反应就是能不能出现比 1px 小情况,譬如 CSS 中编写 0.5px ,那么在 devicePixelRatio = 2 的屏幕上真实渲染物理像素就是 1px,这样就可以达到还原设计稿的效果

但是 0.5px 这个属性值只有 Safari+和Firefox 支持

可以在 MacSafari 上测试下面的代码:

.common-border {
    border-bottom: 1px solid #000;
}

.half-border {
    border-bottom: 0.5px solid #000;
}

可以看到明显的粗细不同

但是这个方法不具备普适性,只有在特定的浏览器中才会有效果,一般需要配合浏览器检测使用

if (window.devicePixelRatio && devicePixelRatio >= 2) {
  const ele = document.createElement('div')
  ele.style.border = '.5px solid transparent'
  document.body.appendChild(ele)
  if (ele.offsetHeight === 1)
  {
    document.querySelector('html').classList.add('half')
  }
  document.body.removeChild(ele)
}

border-image

这个是之前部门广泛推广的一种方式,原理就是借助 border-image-slice 来实现

本质上就是借助图片边框(图片一般如下图所示),然后其中截取的内容是一半白色一半边框色的内容,最终物理像素渲染的就是真实的物理 1px

.border-1px {
    border-width: 1px;
    border-image: url(border.png) 2 repeat;
}

border.png 如下图,是 6 * 6 的边框图

borde

通常实践中因为边框图片尺寸都比较小的原因,都会在打包过程中使用 base64 编码来节省网络请求

借助 border-image 实现的主要问题在于:如果修改边框的颜色,则需要修改对应的边框图片颜色,在开发过程中也不是普遍的解决方案

但是在熟悉 border-image 样式的过程中,还是了解了很多有趣的点,具体可以参考下面张鑫旭大神的文章

参考资料

伪元素配合 transform 缩放

之前我们提到过 0.5px 这种解决方案,类似的,按比例缩小也是一种解决方案

缩小自然就是使用

transfrom: scale(0.5);
transform-origin: left top;

越来越多的现代前端组件库中会使用这种方式来兼容 1px 的问题,尤其是借助各种CSS预处理语言,可以方便的实现边框颜色自定义,支持圆角这些特性

譬如使用 Less 这样实现颜色的自定义(摘自 Vux - 1px 中的实现)

.setLine(@c: #C7C7C7) {
  content: " ";
  position: absolute;
  left: 0;
  top: 0;
  width: 200%;
  height: 200%;
  border: 1px solid @c;
  color: @c;
  transform-origin: left top;
  transform: scale(0.5);
}

.vux-1px, .vux-1px-t, .vux-1px-b, .vux-1px-tb, .vux-1px-l, .vux-1px-r {
  position: relative;
}

.vux-1px {
  &:before {
    /*
    ** 这里可以自定义配置颜色
    ** .setLine(#000);
    */
    .setLine();
  }
}

在此基础上,我们可以写出一版支持边框圆角的

.setLine(@c: #C7C7C7, @r: 0) {
  content: " ";
  position: absolute;
  left: 0;
  top: 0;
  width: 200%;
  height: 200%;
  border: 1px solid @c;
  border-radius: @r;
  transform-origin: left top;
  transform: scale(0.5);
}

.vux-1px {
  position: relative;
}

.vux-1px {
  &:before {
    /*
    ** 这里增加支持自定义边框圆角
    ** .setLine(#000, 2px);
    */
    .setLine();
  }
}

相比之下,这种解决方案需要兼容 devicePixelRatio = 3 的情况也是比较简单的

都可以通过CSS预处理语言来进行配置

奇技淫巧

关注下手淘首页是如何解决 1px 边框问题的

手淘首页是使用 rem 作为页面布局单位的,但是其中涉及到 1px 的地方却是使用元素来模拟的

WX20180322-165116@2x-w375

对应的样式如下

element.style {
  box-sizing: border-box;
  line-height: 0;
  background-color: rgb(232, 232, 232);
  width: 10rem;
  height: 1px;
}

为什么这里是 height: 1px;

我们回到页面顶部关注这样一个 meta 标签(模拟器是 iPhone X 环境):

<meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">

当前页面都被缩放 1/3 显示,那么换句话说,就是 height: 1px 被压缩了 1/3

最后映射到物理像素上,也就完美的呈现了 1px 的内容

当然,因为设置了这个 <meta> 标签,所有的其他元素的大小自然也要对应的放大,不过借助 rem,只需要改变根节点的 font-size 就行

综合来看,还是手淘首页这种解决方案最符合解决 1px 问题的初衷

为了追求完美的前端呈现效果,还是要不断综合不断尝试

Todos

  • canvas 相关的问题

深入浅出 JavaScript 事件

目录

  1. 定义
  2. 事件流
  3. 事件处理程序
  4. 事件对象
  5. 修复特殊事件
  6. 委托 & 移除
  7. 模拟触发事件
  8. zepto.js 中的 event.js
  9. 调试

定义

事件系统:文档或者浏览器窗口中发生的一些特定的交互瞬间,可以使用侦听器(或处理程序)来预定事件,以便发生事件时执行相应的代码

事件系统是传统软件工程观察者模式的实现,浏览器通过事件系统实现了页面行为(JavaScript代码)与页面外观(HTML&CSS)之间的松散耦合

通俗的来说,事件就是浏览器的某种自身行为或者是用户的某些操作,譬如来说: readyloadclick 或者 mouseover,而对应相应某个事件的函数就是事件处理程序

通过事件系统,开发者可以在页面特定的生命周期进行交互、对用户的操作做出反馈等等

事件流

事件流出现的意义是为了规范同心圆的问题:

当你点击了页面上面的按钮,本质上你也点击了按钮的容器,并且以此递归,以上级别的容器都被点击了,最终可以认为你也点击了整个页面

那么事件触发是从页面开始,然后传播到这个按钮? 还是从这个按钮开始传播到整个页面呢?

事件流描述的是从页面中接受事件的顺序

DOM事件流包括三个阶段,他们按照顺序发生:

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

通俗的来说,就是用户点击页面上的按钮:

  1. 点击事件是从最顶层的 document 开始传播,逐步详细直到用户真正点击的元素之前 (捕获阶段)
  2. 然后实际的元素接收到点击事件 (处于目标阶段)
  3. 接着冒泡阶段发生,事件又回传到最顶层的文档 (冒泡阶段)

因为所有的现在浏览器都支持事件冒泡的事件流(老版本的IE不支持事件捕获),所以一般都在事件冒泡阶段对事件进行响应

事件处理程序

1. HTML 事件处理程序

借助 HTML 元素的特性,直接将事件处理程序绑定在 HTML 结构中

特性名称是该元素支持的事件名称,特性的值是能够执行的 JavaScript 代码

<input type="button" onclick="alert('Clicked')" />

或者是这样:

<script>
  function showMessage() {
    alert('Hello World')
  }
</script>
<input type="button" onclick="showMessage()" />

2. DOM 0级事件处理程序

JavaScript 指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性

DOM 0级事件处理处理程序被认为是元素的方法,因此事件的处理程序是在元素的作用域中运行,所以事件处理程序中的 this 引用当前的元素

var btn = document.getElementById('myBtn')
btn.onclick = function() {
  console.log(this) // this 指向 btn 这个 DOM 对象
}

通过将对应的属性设置为 null 可以删除DOM 0级事件处理程序(同名 HTML 事件处理程序也会被移除

btn.onclick = null

注意

DOM 0级事件处理程序与 HTML 事件处理程序本质上一样的,都是操作 DOM 元素(对象)的属性(特性),两者是覆盖作用,只会保留最后赋值的处理程序

但是不会影响 DOM 2级事件处理程序

3. DOM 2级事件处理程序

DOM 2级事件定义了两个方法 addEventListener removeEventListener,用于处理指定和删除事件处理程序的操作

他们都接受相同的参数:要处理的事件名,作为事件处理程序的函数与(可选的)描述事件处理特点的对象(或者是布尔值)

其中事件处理程序的 this 也是引用当前绑定事件监听的函数

第三个参数在之前只支持布尔类型 - capturetrue 表示在捕获阶段调用事件处理程序,false 表示在冒泡阶段,默认值是 false

后期第三个参数被拓展,传递对象,其中可用的选项如下:

  • capture true 表示捕获阶段, false 表示冒泡阶段
  • once 表示事件监听被添加之后最多只会调用一次 被调用后事件监听会被自动移除
  • passive 表示事件监听其中不会调用 preventDefault()

DOM 2级事件处理程序可以添加多个事件处理程序,不会出现之前的覆盖的现象

注意

DOM 2级事件处理程序删除对应的事件处理程序需要保证传入的参数和绑定时参数一致:事件名称、绑定函数的引用以及相同的附加参数

或者换句话说,被绑定的匿名事件处理程序是无法通过相同函数体的匿名函数来移除的(相同的函数体不是相同的函数引用)

function handler() { console.log(1) }

btn.addEventListener('click', handler)

// 这样是无法成功解除事件绑定的
btn.removeEventListener('click', handler, true)

事件对象

当触发DOM事件时,浏览器环境会将一个 event 对象传递到事件处理程序中,这个对象包含了与事件相关的各种信息:导致事件的元素、触发的事件类型以及其他与触发的事件相关的特定信息

同时因为触发的事件类型不同,事件对象中包含的属性也会随之变化

事件对象的通用属性

虽然事件对象的熟悉会随着触发的事件类型变化而变化,但是所有支持 DOM 的事件对象都会包含下面的属性

属性

  • bubbles
  • cancelable
  • currentTarget
  • defaultPrevented (DOM3新增)
  • eventPhase
  • target
  • type

其中比较特殊的就是 target 代表事件触发的真实 DOM 对象,currentTarget 代表事件监听绑定的对象

也就是 this === event.currentTarget

this.contains(event.target) === true

然后 eventPhase 表示当前处于什么状态:捕获阶段 - 1, 处于目标 - 2,冒泡阶段 - 3

方法

  • preventDefault() 阻止默认事件
  • stopPropagation() 阻止冒泡
  • stopImmediatePropagation()DOM3新增) 阻止冒泡的同时,阻止后续绑定的事件监听

委托

事件委托利用了事件冒泡,只需要指定一个事件处理程序就可以管理绑定事件元素以及其子代元素所触发的某一类型的所有事件

尤其是在需要监听动态添加的DOM元素的事件的情景下,或者是处理点击外侧关闭这种需求,使用委托更加常见(动态生成的对话框/动态添加的列表等等

document.addEventListener('click', function(event) {
  switch(event.target) {
    case xx:
      // 判断是对话框元素内部,不关闭对话框      
    case xx:
      // 判断是对话框外侧阴影,关闭对话框
    ...
  }
})

[转] 能力成长模型

原文链接:能力成长模型 - 梁飞的博客

最近看了温伯格1986年出版的《技术领导之路》,
很老的书,讲的都是一些浅显但容易被忽视的道理,
就像第一章,讲作者自己玩弹子球的水平提升,
时间长了,以为自己的水平提升像下图这样,每年都在逐步提升:

而实际上往往不是,能力的提升过程通常都是“高原-突破”式的,
在高原时期沉淀和思考,在学会新方法后突破,
不善于思考和总结的人,高原期就会特别长,而且人在高原期总会觉得很安逸:

并且在突破前一般还会有低谷期,就像下图的“高原-低谷-突破”模型,
要想突破,就必须努力打破安逸的现状,实践新想法、新知识、新方法,
在新的方法没有成熟之前,你会觉得还不如以前好,这就是低谷期,
总想退回老办法上去,尤其是在和别人对比的时候,一定要Hold住:

当然,实际数据不会像上面模型那样平滑,下图是作者玩弹子球水平的数据,
因为作者从小就买了一台弹子球机,上面记录了他所有的成绩:

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.