Eli's Blog

Hello World


  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

小程序授权

发表于 2019-02-21 | 分类于 小程序

特点

小程序的部分接口需要经过用户授权同意才能调用。这些接口别分成多个 scope ,用户选择对 scope 来进行授权,当授权给一个 scope 之后,其对应的所有接口都可以直接使用。
此类接口调用有以下特点:

  • 如果用户未接受或拒绝过此权限(比如在授权弹窗时退出),会弹窗询问用户,用户点击同意后方可调用接口;
  • 如果用户已授权,可以直接调用接口;
  • 如果用户已拒绝授权,则不会出现弹窗,而是直接进入接口 fail 回调。

scope 列表

scope 对应接口 描述
scope.userInfo wx.getUserInfo 用户信息
scope.userLocation wx.getLocation, wx.chooseLocation 地理位置
scope.address wx.chooseAddress 通讯地址
scope.invoiceTitle wx.chooseInvoiceTitle 发票抬头
scope.invoice wx.chooseInvoice 获取发票
scope.werun wx.getWeRunData 微信运动步数
scope.record wx.startRecord 录音功能
scope.writePhotosAlbum wx.saveImageToPhotosAlbum, wx.saveVideoToPhotosAlbum 保存到相册
scope.camera 组件 摄像头

授权涉及到的接口

  • wx.openSetting 打开权限控制页面,只能通过点击事件调用
  • wx.getSetting 获取用户的当前授权状态
  • wx.authorize 主动发起授权请求

场景

首次使用到该权限

这种时候会自动唤出授权弹窗,如果

  • 接受:下次再次使用该接口,可以直接调用
  • 拒绝:下次再次使用该接口,不会出现,直接调用失败,只能调用wx.getSetting引导用户开启权限
  • 中断操作:比如退出之类,下次使用该接口还是会唤出授权弹窗

用户手动关闭了该权限

这种情况会直接调用失败,只能调用wx.getSetting引导用户开启权限

建议与注意

  1. 在小程序加载完成后,可以提前调用wx.authorize获取对应权限
  2. wx.authorize({scope: "scope.userInfo"}),不会弹出授权窗口,要使用 <button open-type="getUserInfo"/>
  3. 需要授权 scope.userLocation 时必须在app.json里配置地理位置用途说明,此说明会在权限弹窗时展示给用户

参考

官方文档

前端面试题汇总

发表于 2019-01-30 | 分类于 面试

网络相关

http状态码?

  • 1XX:信息状态码
    • 100 Continue:客户端应当继续发送请求。这个临时相应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求万仇向客户端发送一个最终响应
    • 101 Switching Protocols:服务器已经理解力客户端的请求,并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后,服务器将会切换到Upgrade消息头中定义的那些协议。
  • 2XX:成功状态码
    • 200 OK:请求成功,请求所希望的响应头或数据体将随此响应返回
    • 201 Created:
    • 202 Accepted:
    • 203 Non-Authoritative Information:
    • 204 No Content:
    • 205 Reset Content:
    • 206 Partial Content:
  • 3XX:重定向
    • 300 Multiple Choices:
    • 301 Moved Permanently:
    • 302 Found:
    • 303 See Other:
    • 304 Not Modified:
    • 305 Use Proxy:
    • 306 (unused):
    • 307 Temporary Redirect:
  • 4XX:客户端错误
    • 400 Bad Request:
    • 401 Unauthorized:
    • 402 Payment Required:
    • 403 Forbidden:
    • 404 Not Found:
    • 405 Method Not Allowed:
    • 406 Not Acceptable:
    • 407 Proxy Authentication Required:
    • 408 Request Timeout:
    • 409 Conflict:
    • 410 Gone:
    • 411 Length Required:
    • 412 Precondition Failed:
    • 413 Request Entity Too Large:
    • 414 Request-URI Too Long:
    • 415 Unsupported Media Type:
    • 416 Requested Range Not Satisfiable:
    • 417 Expectation Failed:
  • 5XX: 服务器错误
    • 500 Internal Server Error:
    • 501 Not Implemented:
    • 502 Bad Gateway:
    • 503 Service Unavailable:
    • 504 Gateway Timeout:
    • 505 HTTP Version Not Supported:

cookie和session的区别

  • cookie数据存放在客户的浏览器上,session数据放在服务器上
  • cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。用户验证这种场合一般会用 session
  • session保存在服务器,客户端不知道其中的信息;反之,cookie保存在客户端,服务器能够知道其中的信息
  • session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie
  • session中保存的是对象,cookie中保存的是字符串
  • session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到,而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的

localStorage,sessionStorage和cookie的区别

数据存储方面

  • cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
    sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。

存储数据大小

  • 存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。
    sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大

数据存储有效期

  • sessionStorage:仅在当前浏览器窗口关闭之前有效;
  • localStorage:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据;
  • cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭

作用域不同

  • sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;
  • localstorage在所有同源窗口中都是共享的;也就是说只要浏览器不关闭,数据仍然存在
  • cookie: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭,数据仍然存在

同源策略是?

同domain(或ip),同端口,同协议视为同一个域,一个域内的脚本仅仅具有本域内的权限,可以理解为本域脚本只能读写本域内的资源,而无法访问其它域的资源。这种安全限制称为同源策略。
image

如何跨域通信

两个不同源的脚本通信就是跨域,方法主要有以下几种:

JSONP

JSONP全称为:JSON with Padding,可用于解决主流浏览器的跨域数据访问的问题。

Web 页面上调用 js 文件不受浏览器同源策略的影响,所以通过 Script 便签可以进行跨域的请求:

  1. 首先前端先设置好回调函数,并将其作为 url 的参数。
  2. 服务端接收到请求后,通过该参数获得回调函数名,并将数据放在参数中将其返回
  3. 收到结果后因为是 script 标签,所以浏览器会当做是脚本进行运行,从而达到跨域获取数据的目的。

优点:

  1. 它不像XMLHttpRequest 对象实现 Ajax 请求那样受到同源策略的限制
  2. 兼容性很好,在古老的浏览器也能很好的运行
  3. 不需要 XMLHttpRequest 或 ActiveX 的支持;并且在请求完毕后可以通过调用 callback 的方式回传结果。

缺点:

  1. 它支持 GET 请求而不支持 POST 等其它类行的 HTTP 请求。
  2. 它只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面或 iframe 之间进行数据通信的问题

CORS

CORS 是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing)它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 ajax 只能同源使用的限制。

CORS 需要浏览器和服务器同时支持才可以生效,对于开发者来说,CORS 通信与同源的 ajax 通信没有差别,代码完全一样。浏览器一旦发现 ajax 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

优点:

  1. 使用简单方便,更为安全
  2. 支持 POST 请求方式

缺点:

  1. CORS 是一种新型的跨域问题的解决方案,存在兼容问题,仅支持 IE 10 以上

Server Proxy

服务器代理,顾名思义,当你需要有跨域的请求操作时发送请求给后端,让后端帮你代为请求,然后最后将获取的结果发送给你。

从输入URL到页面加载发生了什么

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

DNS解析

DNS服务器通过url地址递归查找到服务器ip的过程

域名解析的过程是逐级查询的

  1. 浏览器缓存: 首先会向浏览器的缓存中读取上一次访问的记录,在chrome可以通过地址栏中输入chrome://net-internals/#dns查看缓存的当前状态
  2. 操作系统缓存:查找存储在系统运行内存中的缓存。在mac中可以通过下面的命令清除系统中的DNS缓存。
  3. 在host文件中查找:如果在缓存中都查找不到的情况下,就会读取系统中预设的host文件中的设置。
  4. 路由器缓存:有些路由器也有DNS缓存的功能,访问过的域名会存在路由器上。
  5. ISP DNS缓存:互联网服务提供商(如中国电信)也会提供DNS服务,比如比较著名的 114.114.114.114,在本地查找不到的情况下,就会向ISP进行查询,ISP会在当前服务器的缓存内查找是否有记录,如果有,则返回这个IP,若没有,则会开始向根域名服务器请求查询。
  6. 顶级DNS服务器/根DNS服务器:根域名收到请求后,会判别这个域名(.com)是授权给哪台服务器管理,并返回这个顶级DNS服务器的IP。请求者收到这台顶级DNS的服务器IP后,会向该服务器发起查询,如果该服务器无法解析,该服务器就会返回下一级的DNS服务器IP(nicefilm.com),本机继续查找,直到服务器找到(www.nicefilm.com)的主机。

TCP 连接

拿到了要请求的资源服务器IP后,浏览器通过操作OS的socket与服务器进行TCP连接

三次握手

  • 第一次,本机将标识位 SYN 置为 1, seq = x(Sequence number)发送给服务端。此时本机状态为SYN-SENT
  • 第二次,服务器收到包之后,将状态切换为SYN-RECEIVED,并将标识位 SYN 和 ACK都置为1, seq = y, ack = x + 1, 并发送给客户端。
  • 第三次,客户端收到包后,将状态切换为ESTABLISHED,并将标识位ACK置为1,seq = x + 1, ack = y + 1, 并发送给服务端。服务端收到包之后,也将状态切换为ESTABLISHED。

发送HTTP请求

发送HTTP请求的过程就是构建HTTP请求报文并通过TCP协议中发送到服务器指定端口(HTTP协议80/8080, HTTPS协议443)。HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文。

  • 请求行:请求行中包括请求的方法,路径和协议版本。
  • 请求头:请求头中包含了请求的一些附加的信息,一般是以键值的形式成对存在,比如设置请求文件的类型accept-type,以及服务器对缓存的设置。
  • 空行:协议中规定请求头和请求主体间必须用一个空行隔开
  • 请求主体:对于post请求,所需要的参数都不会放在url中,这时候就需要一个载体了,这个载体就是请求主题。

服务器处理请求并返回HTTP报文

服务端收到请求之后,会根据url匹配到的路径做相应的处理,最后返回浏览器需要的页面资源。浏览器会收到一个响应报文,而所需要的资源就就在报文主体上。与请求报文相同,响应报文也有与之对应的起始行、首部、空行、报文主体,不同的地方在于包含的东西不一样。

  • 响应行:响应报文的起始行同样包含了协议版本,与请求的起始行不同的是其包含的还有状态码和状态码的原因短语。
  • 响应头:对应请求报文中的请求头,格式一致,但是各自有不同的首部。也有一起用的通用首部。
  • 空行
  • 报文主体:请求所需要的资源。

浏览器解析渲染页面

至此浏览器已经拿到了一个HTML文档,并为了呈现文档而开始解析。呈现引擎开始工作,基本流程如下(以webkit为例)

  • 通过HTML解析器解析HTML文档,构建一个DOM Tree,同时通过CSS解析器解析HTML中存在的CSS,构建Style Rules,两者结合形成一个Attachment。
  • 通过Attachment构造出一个呈现树(Render Tree)
  • Render Tree构建完毕,进入到布局阶段(layout/reflow),将会为每个阶段分配一个应出现在屏幕上的确切坐标。
  • 最后将全部的节点遍历绘制出来后,一个页面就展现出来了。

连接结束

现在的页面为了优化请求的耗时,默认都会开启持久连接(keep-alive),那么一个TCP连接确切关闭的时机,是这个tab标签页关闭的时候。这个关闭的过程就是著名的四次挥手。关闭是一个全双工的过程,发包的顺序的不一定的。一般来说是客户端主动发起的关闭,过程如下。

假如最后一次客户端发出的数据seq = x, ack = y;

  • 客户端发送一个FIN置为1的包,ack = y, seq = x + 1,此时客户端的状态为 FIN_WAIT_1
  • 服务端收到包后,状态切换为CLOSE_WAIT发送一个ACK为1的包, ack = x + 2。客户端收到包之后状态切换为FNI_WAIT_2
  • 服务端处理完任务后,向客户端发送一个 FIN包,seq = y; 同时将自己的状态置为LAST_ACK
  • 客户端收到包后状态切换为TIME_WAIT,并向服务端发送ACK包,ack = y + 1,等待2MSL后关闭连接。

OSI 七层网络模型

image

http缓存

请求是浏览器的一个优化点,我们可以通过缓存来减少不必要的请求,进而加快页面的呈现。通过简单地设置http头部可以使用缓存的功能。一般来说有三种设置的方式

Last-Modify(响应头) + If-Modified-Since(请求头)

服务器在返回资源的时候设置Last-Modify当前资源最后一次修改的时间,浏览器会把这个时间保存下来,在下次请求的时候,请求头部If-Modified-Since 会包含这个时间,服务端收到请求后,会比对资源最后更新的时间是否在If-Modified-Since设置的时间之后,如果不是,返回304状态码,浏览器将从缓存中获取资源。反之返回200和资源内容。

ETag(响应头) + If-None-Match(请求头)

根据资源标识符来确定文件是否存在修改,服务器每一次返回资源,都会在Etag中存放资源的标识符,浏览器收到这个标识符,在下一次请求的时候将标识符放在If-None-Match中,服务端将判断是否匹配,如果不匹配,返回200以及新的资源,反之返回304,浏览器从缓存中获取资源

Cache-Control/Expires(响应头)

首先这不是一种方法,而是协议更替中的一种演化。
在http 1.0的时代,我们基于Pragma 和 Expires 控制缓存的生命周期。我们可以通过设置Pragma为no-cache关闭缓存功能,同样也可以在Expires中设置一个缓存失效的时间。需要注意的是,这个失效的时间是相对于服务器的实践而言的,如果人为地改变了客户端的时间,是会导致缓存失效的。

所以,为了解决这个问题,HTTP1.1的协议加入了Cache-Control,通过设置Cache-Control的max-age可以控制缓存的周期。在这个周期内,资源是新鲜的,浏览器再一次需要使用资源的时候,就不会发出请求了

TCP 为什么是三次握手,而不是两次或四次?

核心思想:既要保证数据可靠传输,又要提高传输的效率,而用三次恰恰可以满足以上两方面的需求!

四次握手的过程:

  1. A 发送同步信号SYN + A’s Initial sequence number
  2. B 确认收到A的同步信号,并记录 A’s ISN 到本地,命名 B’s ACK sequence number
  3. B发送同步信号SYN + B’s Initial sequence number
  4. A确认收到B的同步信号,并记录 B’s ISN 到本地,命名 A’s ACK sequence number

很显然2和3这两个步骤可以合并,只需要三次握手,可以提高连接的速度与效率。

二次握手的过程:

  1. A 发送同步信号SYN + A’s Initial sequence number
  2. B发送同步信号SYN + B’s Initial sequence number + B’s ACK sequence number

这里有一个问题,A与B就A的初始序列号达成了一致,这里是1000。但是B无法知道A是否已经接收到自己的同步信号,如果这个同步信号丢失了,A和B就B的初始序列号将无法达成一致。

HTML相关

html语义化?

HTML标签的语义化是指:通过使用包含语义的标签(如h1-h6)恰当地表示文档结构。

  • 去掉样式后页面呈现清晰的结构
  • 盲人使用读屏器更好地阅读
  • 搜索引擎更好地理解页面,有利于收录
  • 便团队项目的可持续运作及维护

viewport的常见设置有哪些

viewport常常使用在响应式开发以及移动web开发中,viewport顾名思义就是用来设置视口,主要是规定视口的宽度、视口的初始缩放值、 视口的最小缩放值、视口的最大缩放值、是否允许用户缩放等。一个常见的viewport设置如下:

1
<meta name="viewport"  content="initial-scale=1,maximum-scale=1,user-scalable=no,width=device-width" />

  • initial-scale:初始缩放比例
  • maximum-scale:允许缩放的最大比例
  • minimum-scale:允许缩放的最小比例
  • user-scalable:是否允许手动缩放

H5新增?

  • 新的元素:section, video, progress, nav, meter, time, aside, canvas, command, datalist, details, embed, figcaption, figure, footer, header, hgroup, keygen, mark, output, rp, rt, ruby, source, summary, wbr。

CSS相关

盒模型?

盒模型(box model)是CSS中的一个重要概念,它是元素大小的呈现方式。需要记住的是:”every element in web design is a rectangular box”。

image

常用选择器?

  • *通用选择器:选择所有元素,不参与计算优先级,兼容性IE6+
  • #X id选择器:选择id值为X的元素,兼容性:IE6+
  • .X 类选择器: 选择class包含X的元素,兼容性:IE6+
  • X Y后代选择器: 选择满足X选择器的后代节点中满足Y选择器的元素,兼容性:IE6+
  • X 元素选择器: 选择标所有签为X的元素,兼容性:IE6+
  • :link,:visited,:focus,:hover,:active链接状态: 选择特定状态的链接元素,顺序LoVe HAte,兼容性: IE4+
  • X + Y直接兄弟选择器:在X之后第一个兄弟节点中选择满足Y选择器的元素,兼容性: IE7+
  • X > Y子选择器: 选择X的子元素中满足Y选择器的元素,兼容性: IE7+
  • X ~ Y兄弟: 选择X之后所有兄弟节点中满足Y选择器的元素,兼容性: IE7+
  • [attr] 属性选择器

选择器优先级如何确定?

  • 千位:声明在style属性中,所谓的内联属性 + 1
  • 百位:ID选择器 + 1
  • 十位:类选择器+1、属性选择器+1、或者伪类+1
  • 个位:元素选择器+1、伪元素+1

如何居中?

水平居中

块级元素

  1. margin: auto, 设置块级元素的 width 可以防止它从左到右撑满整个容器。然后你就可以设置左右外边距为 auto 来使其水平居中。元素会占据你所指定的宽度,然后剩余的宽度会一分为二成为左右外边距。

    1
    2
    3
    4
    #main {
    width: 600px;
    margin: 0 auto;
    }
  2. flex布局

    1
    2
    3
    4
    .parent {
    display: flex;
    justify-content: center;
    }
  3. CSS3 transform

    1
    2
    3
    4
    5
    6
    7
    8
    .parent {
    position: relative;
    }
    .child {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    }
  4. display设置为tabel-cell

内联元素

1
text-align: center

垂直居中

块级元素

  • 不知道自己高度和父容器高度

    1
    2
    3
    4
    5
    6
    7
    8
    parentElement{
    position:relative;
    }
    childElement{
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    }
  • 父容器下只有一个元素,且父元素设置了高度

    1
    2
    3
    4
    5
    6
    7
    8
    parentElement{
    height:xxx;
    }
    .childElement {
    position: relative;
    top: 50%;
    transform: translateY(-50%);
    }
  • Flex布局

    1
    2
    3
    4
    5
    parentElement{
    display:flex;/*Flex布局*/
    display: -webkit-flex; /* Safari */
    align-items:center;/*指定垂直居中*/
    }
  • table布局(不推荐),display:table-cell,同时设置vertical-align:middle

单行元素

  • line-height = height
  • inline或者table-cell
    vertical-align:middle;

BFC?

BFC(Block formatting context)直译为”块级格式化上下文”。它是一个独立的渲染区域,只有 Block-level box 参与, 它规定了内部的 Block-level Box 如何布局,并且与这个区域外部毫不相干。

简单来说,CSS 里的布局和渲染都是以 Box 为单位的,不同的 Box 有不同的布局规则,而 BFC 是其中的一种,相似的还有 IFC 和 CSS3 中增加的 GFC 和 FFC,这里就不深入介绍了。

BFC布局规则

BFC 的布局遵从如下规则:

  • 内部的 Box 会在垂直方向,一个接一个地放置。
  • Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
  • 每个元素的 margin box 的左边, 与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
  • BFC 的区域不会与 float box 重叠。
  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  • 计算BFC的高度时,浮动元素也参与计算。

哪些元素会生成BFC

  • 根元素。
  • float 属性不为 none。
  • position 为 absolute 或 fixed。
  • display 为 inline-block, table-cell, table-caption, flex, inline-flex。
  • overflow 不为 visible(hidden, scroll, auto)。

重绘(repaint)和回流(reflow)是什么,如何避免?

DOM的变化影响到了元素的几何属性(宽高),浏览器重新计算元素的几何属性,其他元素的几何 属性和位置也会受到影响,浏览器需要重新构造渲染树,这个过程称为重排,浏览器将受到影响的部分 重新绘制到屏幕上的过程称为重绘。引起重排的原因有

  1. 调整窗口大小
  2. 字体大小
  3. 样式表变动
  4. 元素内容变化,尤其是输入控件
  5. CSS伪类激活,在用户交互过程中发生
  6. DOM操作,DOM元素增删、修改
  7. width, clientWidth, scrollTop等布局宽高的计算

减少重绘和重排的方法:

  1. 避免频繁读取元素几何属性
  2. 使用cssText或者className一次性改变属性
  3. 使用fragment
  4. 对于多次重排的元素,如动画,使用绝对定位脱离文档流,让他的改变不影响到其他元素

如何清除浮动?

  • overflow: auto
  • 伪元素
    1
    2
    3
    4
    5
    .clearfix::after {
    content:"";
    display:block;
    clear: both;
    }

JavaScript相关

什么是事件循环(EVENT LOOP)?

我们常常说js是单线程的,是指js执行引擎是单线程的,除了这个单线程,还有一个 任务队列,在执行js代码的过程中,执行引擎遇到注册的延时方法,如定时器,DOM事件, 会将这些方法交给相应的浏览器模块处理,当这些延时方法有触发条件去触发的时候, 这些延时方法会被添加至任务队列,而这些任务队列中的方法只有js的主线程空闲了才会执行, 这也就是说我们常常用的定时器定的时间参数只是一个触发条件,具体多少时间后执行其实还需要看 js主线程空闲与否

执行上下文

简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。

执行上下文的类型

JavaScript 中有三种执行上下文类型。

  • 全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文 — 每当一个函数被调用时,都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序执行一系列步骤。
  • Eval 函数执行上下文 — 执行在 eval函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。

变量对象 作用域链 this

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

变量对象

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
当进入执行上下文时,这时候还没有执行代码,
变量对象会包括:

  1. 函数的所有形参 (如果是函数上下文)
    • 由名称和对应值组成的一个变量对象的属性被创建
    • 没有实参,属性值设为 undefined
  2. 函数声明
    • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
    • 如果变量对象已经存在相同名称的属性,则完全替换这个属性
  3. 变量声明
    • 由名称和对应值(undefined)组成一个变量对象的属性被创建;
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
函数的作用域在函数定义的时候就决定了。
这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
举个例子:

1
2
3
4
5
function foo() {
function bar() {
...
}
}

函数创建时,各自的[[scope]]为:

1
2
3
4
5
6
7
8
foo.[[scope]] = [
globalContext.VO
];

bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];

this指向

  1. 作为对象调用时,指向该对象 obj.b(); // 指向obj
  2. 作为函数调用, var b = obj.b; b(); // 指向全局window
  3. 作为构造函数调用 var b = new Fun(); // this指向当前实例对象
  4. 作为call与apply调用 obj.b.apply(object, []); // this指向当前的object
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var value = 1;
    var foo = {
    value: 2,
    bar: function () {
    return this.value;
    }
    }
    //示例1
    console.log(foo.bar());//2
    //示例2
    console.log((foo.bar)());//2
    //示例3
    console.log((foo.bar = foo.bar)());//1
    //示例4
    console.log((false || foo.bar)());//1
    //示例5
    console.log((foo.bar, foo.bar)());//1

JS 原型是什么?

每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型”继承”属性。

protytype

每个函数都有一个 prototype 属性,指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型对象

proto

每一个JavaScript对象(除了 null)都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。

constructor

每个原型都有一个 constructor 属性指向关联的构造函数。

原型链

图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
prototype

JS 如何实现继承?

JavaScript深入之继承的多种方式和优缺点

原型链

1
2
3
4
5
function Child() {
Parent.call(this)//继承属性
}
Child.prototype = Object.create(Parent.prototype);//继承方法
Child.prototype.constructor = Child

ES6 class

1
2
3
4
5
6
7
8
9
class Child extends Parent {
constructor(x, y, props) {
super(x, y); // 调用父类的constructor(x, y)
this.props = props;
}
toString() {
return xxx; // 调用父类的toString()
}
}

new 做了几件事?

  1. 以构造器的 prototype 属性(注意与私有字段 [[prototype]] 的区分)为原型,创建新对象;
  2. 将 this 和调用参数传给构造器,执行;
  3. 如果构造器返回的是对象,则返回,否则返回第一步创建的对象。

闭包

MDN 对闭包的定义为:

闭包是指那些能够访问自由变量的函数。

那什么是自由变量呢?

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

由此,我们可以看出闭包共有两部分组成:

闭包 = 函数 + 函数能够访问的自由变量

依靠作用域链,闭包可以在函数已经销毁的情况下依然访问到自由变量

必考题

1
2
3
4
5
6
7
8
9
10
11
var data = [];

for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}

data[0]();
data[1]();
data[2]();

答案是都是 3,让我们分析一下原因,
当执行 data[0] 函数的时候,data[0] 函数的作用域链为:

1
2
3
data[0]Context = {
Scope: [AO, globalContext.VO]
}

data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i 为 3,所以打印的结果就是 3。

改成闭包之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
var data = [];

for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
}

data[0]();
data[1]();
data[2]();

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:

1
2
3
data[0]Context = {
Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

匿名函数执行上下文的AO为:

1
2
3
4
5
6
7
8
9
匿名函数Context = {
AO: {
arguments: {
0: 0,
length: 1
},
i: 0
}
}

data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是0。

静态作用域与动态作用域

JavaScript 采用的是词法作用域(静态作用域),函数的作用域在函数定义的时候就决定了。
而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。

Promise

概念

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ajax函数将返回Promise对象:
function ajax(method, url, data) {
var request = new XMLHttpRequest();
return new Promise(function (resolve, reject) {
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
resolve(request.responseText);
} else {
reject(request.status);
}
}
};
request.open(method, url);
request.send(data);
});
}

var p = ajax('GET', '/api/categories');
p.then(function (text) { // 如果AJAX成功,获得响应内容
console.log(text);
}).catch(function (status) { // 如果AJAX失败,获得响应代码
console.error('ERROR: ' + status;)
});

手写Ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
var request = new XLMHttpRequest();
request.setRequestHeader("Content-type","application/x-www-form-urlencoded"); //设置header
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
console.log(request.responseText);
} else {
console.log(request.status);
}
}
};
request.open(method, url);
request.send(data);

XMLHttpRequest.readyState可能值:

  • 0,表示 XMLHttpRequest 实例已经生成,但是实例的open()方法还没有被调用。
  • 1,表示open()方法已经调用,但是实例的send()方法还没有调用,仍然可以使用实例的setRequestHeader()方法,设定 HTTP 请求的头信息。
  • 2,表示实例的send()方法已经调用,并且服务器返回的头信息和状态码已经收到。
  • 3,表示正在接收服务器传来的数据体(body 部分)。这时,如果实例的responseType属性等于text或者空字符串,responseText属性就会包含已经收到的部分信息。
  • 4,表示服务器返回的数据已经完全接收,或者本次接收已经失败。

立即执行函数

立即执行函数模式是一种语法,可以让你的函数在定义后立即被执行

1
2
3
(function () {
alert('watch out!');
}());

这种模式有一些几部分组成:

  • 使用函数表达式定义一个函数(函数声明不能起作用)
  • 在结尾加上一对括号,让函数立即被执行
  • 将整个函数包裹在一对括号中(只有在你不将函数赋值给一个变量的时候才需要)

好处

这种模式是非常有用的,因为它为你初始化代码提供了一个作用域的沙箱;
考虑一下下面这种常见的场景:
你的代码在页面代码加载完成之后,不得不执行一些设置工作,比如附加时间处理器,创建对象等等,
所有的这些工作只需要执行一次,所以没有理由创建一个可复用的命名的函数,
但这些代码也需要一些临时的变量,但初始化过程结束后,就再也不会被用到了,
所以将这些变量作为全局变量不是个好主意,所以我们需要立即执行函数——去将我们所有的代码包裹在它的局部作用域中,不会让任何变量泄露成全局变量;

深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var cloneObj = function(obj){
var str, newobj = obj.constructor === Array ? [] : {};
if(typeof obj !== 'object'){
return obj;
} else if(window.JSON){
str = JSON.stringify(obj), //系列化对象
newobj = JSON.parse(str); //还原
} else {
for(var i in obj){
newobj[i] = typeof obj[i] === 'object' ? cloneObj(obj[i]) : obj[i];
}
}
return newobj;
};

数组去重

  • 用indexOf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var array = [1, 1, '1'];

    function unique(array) {
    var res = [];
    for (var i = 0, len = array.length; i < len; i++) {
    var current = array[i];
    if (res.indexOf(current) === -1) {
    res.push(current)
    }
    }
    return res;
    }

    console.log(unique(array));
  • indexOf + fliter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var array = [1, 2, 1, 1, '1'];

    function unique(array) {
    var res = array.filter(function(item, index, array){
    return array.indexOf(item) === index;
    })
    return res;
    }

    console.log(unique(array));
  • 用set

    1
    2
    3
    4
    5
    6
    7
    var array = [1, 1, '1'];

    function unique(array) {
    return Array.from(new Set(array)) // or [...new Set(array)];
    }

    console.log(unique(array));
  • 先排序再判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var array = [1, 1, '1'];

    function unique(array) {
    var res = [];
    var sortedArray = array.concat().sort();
    var seen;
    for (var i = 0, len = sortedArray.length; i < len; i++) {
    // 如果是第一个元素或者相邻的元素不相同
    if (!i || seen !== sortedArray[i]) {
    res.push(sortedArray[i])
    }
    seen = sortedArray[i];
    }
    return res;
    }

    console.log(unique(array));

用正则实现 string.trim()

1
2
3
var myTrim = function(str) {
return str.replace(/^\s+|\s$+/g, "")
}

js实现call, apply, bind

  • call

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Function.prototype.call = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');//args 会自动调用 Array.toString()

    delete context.fn
    return result;
    }
  • apply

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
    result = context.fn();
    }
    else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
    args.push('arr[' + i + ']');
    }
    result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
    }
  • bind

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
    }

防抖函数

JavaScript专题之跟着underscore学防抖

防抖的原理就是:你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行,真是任性呐!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function debounce(func, wait, immediate) {

var timeout, result;

return function () {
var context = this;
var args = arguments;

if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
}
}

节流函数

如果你持续触发事件,每隔一段时间,只执行一次事件。
根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不同。

使用时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
function throttle(func, wait) {
var context;
var previous = 0;

return function() {
var now = +new Date();
context = this;
if (now - previous > wait) {
func.apply(context, arguments);
previous = now;
}
}
}

使用定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 第二版
function throttle(func, wait) {
var timeout;
var previous = 0;

return function() {
context = this;
args = arguments;
if (!timeout) {
timeout = setTimeout(function(){
timeout = null;
func.apply(context, args)
}, wait)
}

}
}

DOM

DOM 事件模型是什么

DOM事件模型分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。

  1. 捕获阶段:事件从window对象自上而下向目标节点传播的阶段;
  2. 目标阶段:真正的目标节点正在处理事件的阶段;
  3. 冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。
    事件模型图

事件委托是什么?有什么好处?

把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
优点:

  1. 减少内存消耗
  2. 动态绑定事件

小程序登陆流程

发表于 2019-01-21 | 分类于 小程序

登录流程图

小程序登录流程可以被下图很好地概括
小程序登录流程

具体步骤

登录流程涉及到到三端

  • 小程序端
  • 开发者服务端
  • 微信服务端

登录流程大体分为5步

  1. 小程序端:调用wx.login()方法从微信端拿到小程序登录凭证code,该凭证有效期只有5分钟。
  2. 小程序端:用wx.request()方法将登录凭证code提交到开发者服务端。
    第1和第2步的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Page({
tapLogin: function() {
wx.login({
success: function(res) {
if (res.code) {
wx.request({
url: 'https://test.com/login',
data: {
username: 'zhangsan', // 用户输入的账号
password: 'pwd123456', // 用户输入的密码
code: res.code
},
success: function(res) {
// 登录成功
if (res.statusCode === 200) {
console.log(res.data.sessionId)// 服务器回包内容
}
}
})
} else {
console.log('获取用户登录态失败!' + res.errMsg)
}
}
});
}
})
  1. 开发者服务端:用拿到的登录凭证code加上appid和appsecret到微信服务端的code2Session接口去换取openid和session_key,appid和appsecret可以在小程序管理平台中查到:
    kPRu6g.md.png
  2. 开发者服务端:
    • openid就是微信用户id,开发者服务端可以将自己的用户id和openid进行一对一的绑定,方便以后登录,绑定完成后生成自己的sessionid,之后将sessionid下发给小程序端;
    • session_key是用于之后和微信服务通信的,不能泄露,也不能下发给小程序端。
  3. 小程序端:拿到sessionid后,之后和开发者服务端通信只要带上sessionid,服务端就能识别当前的用户。

微信小程序自定义组件实践

发表于 2019-01-20 | 分类于 小程序

目的

从小程序基础库版本1.6.3开始,小程序支持了简洁的组件化编程。通过编写一个自定义组件来学习小程序自定义组件的用法,这里我准备写一个如下的搜索框组件。

搜索框组件

流程

1.新建组件相关文件

在项目根目录下新建如下文件

1
2
3
4
5
6
/component/
searchbar/
index.wxml
index.js
index.wxss
index.json

2.编写组件相关文件

index.wxml

index.wxml 中编写搜索框组件的结构(偷懒直接用了WeUI的搜索框组件改了下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--index.wxml-->
<view class="my-search-bar">
<view class="my-search-bar__form">
<view class="my-search-bar__box" bindtap="showInput">
<icon class="my-icon-search_in-box" type="search" size="14" color='{{searchIconColor}}'></icon>
<input type="text" class="my-search-bar__input" placeholder="{{placeholder}}" confirm-type="search" value="{{searchValue}}" focus="{{inputFocused}}" bindinput="inputTyping" bindconfirm="getSearchValue"/>
<view class="my-icon-clear" wx:if="{{searchValue.length > 0}}" bindtap="clearInput">
<icon type="clear" size="14" color='{{clearIconColor}}'></icon>
</view>
</view>
<label class="my-search-bar__label" hidden="{{inputFocused}}" bindtap="showInput">
<icon class="my-icon-search" type="search" size="14"></icon>
<view class="my-search-bar__text">{{title}}</view>
</label>
</view>
<view class="my-search-bar__cancel-btn" hidden="{{!inputFocused}}" bindtap="hideInput">
取消
</view>
</view>

index.js

index.js中编写组件中绑定的几个事件,初始化组件的属性和数据,其中用到了Compnent构造器,相关属性见官方文档。

  • 这里注意下getSearchValue这个方法里用到了this.triggerEvent的方法触发一个自定义的事件search,并把搜索框内容通过event传输出去,具体用法参考组件事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//index.js 
Component({
/**
* 组件的属性列表
*/
properties: {
title: {
type: String,
value: "标题"
},
placeholder: {
type: String,
value: "搜索"
},
searchIconColor: {
type: String,
value: ""
},
clearIconColor: {
type: String,
value: ""
}
},
/**
* 组件的初始数据
*/
data: {
inputFocused: false,
searchValue: ""
},
/**
* 组件的方法列表
*/
methods: {
showInput() {
this.setData({
inputFocused: true
});
},
hideInput() {
this.setData({
inputFocused: false
});
},
clearInput() {
this.setData({
searchValue: ""
});
},
inputTyping(e) {
this.setData({
searchValue: e.detail.value
});
},
getSearchValue(e) {
const eventDetails = {
value: this.data.searchValue
};
this.triggerEvent("search", eventDetails)
}
}
})

index.wxss

index.wxss 编写组件样式,需要注意以下几点:

  • 组件和引用组件的页面不能使用id选择器(#a)、属性选择器([a])和标签名选择器,请改用class选择器。
  • 组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用。
  • 子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。
  • 继承样式,如 font 、 color ,会从组件外继承到组件内。
  • 除继承样式外, app.wxss 中的样式、组件所在页面的的样式对自定义组件无效。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    /*index.wxss*/
    .my-search-bar {
    position: relative;
    padding: 8px 10px;
    display: -webkit-box;
    display: -webkit-flex;
    display: flex;
    box-sizing: border-box;
    background-color: #efeff4;
    border-top: 1rpx solid #d7d6dc;
    border-bottom: 1rpx solid #d7d6dc;
    }

    .my-icon-search {
    margin-right: 8px;
    font-size: inherit;
    vertical-align: middle;
    }

    .my-icon-search_in-box {
    position: absolute;
    left: 10px;
    top: 7px;
    }

    .my-search-bar__text {
    display: inline-block;
    font-size: 14px;
    vertical-align: middle;
    }

    .my-search-bar__form {
    position: relative;
    -webkit-box-flex: 1;
    -webkit-flex: auto;
    flex: auto;
    border-radius: 5px;
    background: #fff;
    border: 1rpx solid #e6e6ea;
    }

    .my-search-bar__box {
    position: relative;
    padding-left: 30px;
    padding-right: 30px;
    width: 100%;
    box-sizing: border-box;
    z-index: 1;
    }

    .my-search-bar__input {
    height: 28px;
    line-height: 28px;
    font-size: 14px;
    }

    .my-icon-clear {
    position: absolute;
    top: 0;
    right: 0;
    padding: 7px 8px;
    font-size: 0;
    }

    .my-search-bar__label {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 2;
    border-radius: 3px;
    text-align: center;
    color: #9b9b9b;
    background: #fff;
    line-height: 28px;
    }

    .my-search-bar__cancel-btn {
    margin-left: 10px;
    line-height: 28px;
    color: #09bb07;
    white-space: nowrap;
    }
  • index.json
    1
    2
    3
    4
    {
    "component": true,
    "usingComponents": {}
    }

3.使用组件

先在需要使用组件的页面的json文件中注册组件

1
2
3
4
5
{
"usingComponents": {
"my-searchbar": "/component/searchbar/index"
}
}

在页面wxml文件中使用组件

1
<my-searchbar title="标题" placeholder="输入搜索内容" bind:search="onSearch" ></my-searchbar>

在页面js文件中编写onSearch方法用于接收索框内容

1
2
3
4
5
Page({
onSearch: function(e) {
console.log("search:", e.detail.value)
},
})

搜索框

margin叠加和BFC

发表于 2019-01-20 | 分类于 css

引子

自己最初根本不知道 BFC 和 margin 叠加的概念,但是在写 web 简历页面的时候发现个奇怪的现象,才了解到原来 CSS 还有这么个神奇的东西。先简单说说当初遇到的现象吧,就是下面这样:

1
2
3
4
5
6
<body>
<div class="first-block"></div>
<div class="second-block">
<h2>title</h2>
</div>
</body>

css:

1
2
3
4
5
6
7
8
9
10
11
12
.first-block
{
background: #F44336;
width: 200px;
height: 200px;
}
.second-block
{
background: #00BCD4;
width: 200px;
height: 200px;
}

很简单是吧,可是看到浏览器中的效果时我还是有点懵逼,是这样的:

为什么 first-block 和 second-block 之间会有这么宽的间距?我并没有给他们设置 margin 啊!

原因

外边距折叠

那么为什么会这样呢,先自己动手找下原因吧,F12审查下元素,很快找到原因了:

原来这个间距是h2的上外边距引起的,可是 h2 不是 second-block 的子元素么,为什么它的 margin 可以穿透出去顶到 first-block ?

不懂就google一下吧,没费多大劲就找到原因了:

在CSS当中,相邻的两个盒子(可能是兄弟关系也可能是祖先关系)的外边距可以结合成一个单独的外边距。这种合并外边距的方式被称为折叠,并且因而所结合成的外边距称为折叠外边距。

原来是外边距折叠造成的,以前也在head first上看到过这个概念,但是当时书上只提到说两个相邻的块元素的上下外边框直接会产生折叠现象,当时想当然地以为所谓的相邻的块元素是指两个兄弟关系的块元素,没想到也包括祖先关系。

折叠的条件

两个块元素要产生折叠现象,必须满足一个必备条件,那就是这两个元素的 margin 必须是相邻的,那么如果定义相邻呢,w3c 规范,两个 margin 是邻接的必须满足以下条件:

  • 必须是处于常规文档流(非float和绝对定位)的块级盒子,并且处于同一个 BFC 当中。
  • 没有inline盒子,没有空隙,没有 padding 和 border 将他们分隔开。
  • 都属于垂直方向上相邻的外边距,可以是下面任意一种情况:
    • 元素的 margin-top 与其第一个常规文档流的子元素的 margin-top。
    • 元素的 margin-bottom 与其下一个常规文档流的兄弟元素的 margin-top。
    • height 为 auto 的元素的 margin-bottom 与其最后一个常规文档流的子元素的 margin-bottom。
    • 高度为0并且最小高度也为0,不包含常规文档流的子元素,并且自身没有建立新的BFC的元素的 margin-top 和 margin-bottom。

BFC

好不容易才理解了折叠,怎么又跑出来个BFC?继续google吧:

BFC(Block formatting context)直译为”块级格式化上下文”。它是一个独立的渲染区域,只有 Block-level box 参与, 它规定了内部的 Block-level Box 如何布局,并且与这个区域外部毫不相干。

简单来说,CSS 里的布局和渲染都是以 Box 为单位的,不同的 Box 有不同的布局规则,而 BFC 是其中的一种,相似的还有 IFC 和 CSS3 中增加的 GFC 和 FFC,这里就不深入介绍了。

BFC布局规则

BFC 的布局遵从如下规则:

  • 内部的 Box 会在垂直方向,一个接一个地放置。
  • Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
  • 每个元素的 margin box 的左边, 与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
  • BFC 的区域不会与 float box 重叠。
  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  • 计算BFC的高度时,浮动元素也参与计算。

哪些元素会生成BFC

  • 根元素。
  • float 属性不为 none。
  • position 为 absolute 或 fixed。
  • display 为 inline-block, table-cell, table-caption, flex, inline-flex。
  • overflow 不为 visible(hidden, scroll, auto)。

结合 BFC 布局规则一看,以前学过的好多 CSS 规则方法的原理都在 BFC 这啊,比如最常用的清除浮动什么的。

解决方案

知道了问题产生的原因,那就可以对症下药了。

方法一

我们注意到折叠条件的第二条:没有inline盒子,没有空隙,没有 padding 和 border 将他们分隔开。 自然而然地就可以想到我们可以把 second-block 加上一个边框来让折叠失效:

修改css:

1
2
3
4
5
6
7
.second-block
{
background: #00BCD4;
width: 200px;
height: 200px;
border: 1px solid rgba(0,0,0,0); /*添加一个透明边框*/
}

效果:

嗯,折叠问题解决了,但是由于有1px的边框,second-block 看起来会比 first-block 宽一点,没关系,添加box-sizing: border-box属性可以解决这个问题:

修改css:

1
2
3
4
5
6
7
8
.second-block
{
background: #00BCD4;
width: 200px;
height: 200px;
border: 1px solid rgba(0,0,0,0); /*添加一个透明边框*/
box-sizing: border-box;
}

效果:

方法二

我们知道,这里之所以会产生折叠,是因为两个 block 处于同一个 BFC(根元素生成的BFC)中,那我们可以让 second-block 单独生成一个 BFC,就可以防止出现折叠了。

修改css:

1
2
3
4
5
6
7
.second-block
{
background: #00BCD4;
width: 200px;
height: 200px;
overflow: hidden; /*触发BFC*/
}

效果:

嗯,完美~!

参考文章:

  • http://www.w3cplus.com/css/understanding-bfc-and-margin-collapse.html
  • http://www.cnblogs.com/lhb25/p/inside-block-formatting-ontext.html

小程序渲染流程

发表于 2019-01-20 | 分类于 小程序

概况

小程序的整体加载流程可以概况为 启动-> 下载小程序代码包->加载小程序-> 小程序交互->卸载小程序->关闭,这里我们只讨论小程序加载过程

小程序加载流程

渲染层和逻辑层

小程序的加载是由两个层级合作完成的:渲染层和逻辑层,渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端做中转,逻辑层发送网络请求也经由客户端转发。

流程

弄清楚了渲染层和逻辑层就可以来看看具体的加载流程了

1.初始化阶段

在初始化阶段,主要是做一些准备工作:加载运行app.js,注册app生命周期事件,这些完成之后会调用app的onLaunch方法,随后调用app的onShow方法,之后会读取app.json里面的配置,获取app中需要注册的页面。

2.页面加载阶段

从app.json读取到第一个页面路径后就会进入加载阶段,渲染层和逻辑层会同步工作,流程可以由下图概括
渲染流程

  • 渲染层:
    页面层级的准备工作分为三个阶段。
    1. 第一阶段是启动一个WebView,在iOS和Android系统上,操作系统启动WebView都需要一小段时间。
    2. 第二阶段是在WebView中初始化基础库,此时还会进行一些基础库内部优化,以提升页面渲染性能。
    3. 第三阶段是注入小程序WXML结构和WXSS样式,使小程序能在接收到页面初始数据之后马上开始渲染页面(这一阶段无法在小程序启动前执行)。
  • 逻辑层:
    1. 会运行第一个页面的js文件,注册页面生命周期事件,加载页面初始数据,之后会依次调用页面的onLoad方法和onShow方法,完成后就把页面数据(this.data)发送给渲染层进行首次渲染
    2. 在页面初次渲染完成时,Page构造器参数所定义的onReady方法会被调用,onReady在页面没被销毁前只会触发1次,onReady触发时,表示页面已经准备妥当,在逻辑层就可以和视图层进行交互了。

3.页面交互阶段

在页面交互阶段主要会进行两种处理:

  1. 用户事件通信:
    • 视图层会接受用户事件,如点击事件、触摸事件等。用户事件的通信比较简单:当一个用户事件被触发且有相关的事件监听器需要被触发时,视图层会将信息反馈给逻辑层。如果一个事件没有绑定事件回调函数,则这个事件不会被反馈给逻辑层。视图层中有一套高效的事件处理体系,可以快速完成事件生成、冒泡、捕获等过程。
  2. 视图渲染:
    • 视图层在接收到初始数据(data)和更新数据(setData数据)时,需要进行视图层渲染。
    • 初始渲染中得到的data和当前节点树会保留下来用于重渲染。每次重渲染时,将data和setData数据套用在WXML片段上,得到一个新节点树。然后将新节点树与当前节点树进行比较,这样可以得到哪些节点的哪些属性需要更新、哪些节点需要添加或移除。最后,将setData数据合并到data中,并用新节点树替换旧节点树,用于下一次重渲染。
Eli Lai

Eli Lai

Eli's World

6 日志
3 分类
3 标签
GitHub E-Mail
© 2019 Eli Lai
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4