Harry的博客


  • Startseite

  • Archiv

  • Tags

  • View

关于回流和重绘

Veröffentlicht am 2020-07-31 | Visitors:

一、浏览器渲染过程

讲回流和重绘之前,我们先来了解下浏览器的渲染过程,如下图:

浏览器渲染过程

从图中看到,浏览器的渲染过程分以下几步进行:
1、将HTML解析为DOM树,将CSS解析为CSSOM树
2、将DOM树和CSSOM树进行计算合成,生成渲染树Render Tree
3、Layout(回流):根据渲染树Render Tree进行回流Layout,得到节点的几何信息(位置、大小)
4、Painting(重绘):根据渲染树Render Tree,以及回流(Layout)得到的节点几何信息,得到节点的绝对像素
5、Display:将各个节点绘制在屏幕上

其中,浏览器构建渲染树Render Tree过程,如下图:

render-tree

1、从 DOM 树的根节点开始遍历每个可见节点。

  • 某些节点不可见(例如脚本标记、元标记等),因为它们不会体现在渲染输出中,所以会被忽略。
  • 某些节点通过 CSS 隐藏,因此在渲染树中也会被忽略,例如,上例中的 span 节点不会出现在渲染树中,因为有一个显式规则在该节点上设置了“display: none”属性。备注:利用visibility和opacity隐藏的节点,还是会显示在渲染树上的

2、对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们。

3、根据每个可见节点的内容和样式,生成渲染树Render Tree

二、回流重绘的概念

1、回流

概念:回流就是浏览器根据Render Tree在视口(viewport)计算节点具体位置和大小的过程。
为弄清每个对象在网页上的确切大小和位置,浏览器从渲染树的根节点开始进行遍历。让我们考虑下面这样一个简单的实例:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Critial Path: Hello world!</title>
</head>
<body>
<div style="width: 50%">
<div style="width: 50%">Hello world!</div>
</div>
</body>
</html>

以上代码包含两个嵌套div元素:父元素div将宽度设置为视口宽度的 50%,子元素div将其宽度设置为其父元素的50%,即视口宽度的25%。而在回流这个阶段,我们就需要根据视口具体的宽度,将其转为实际的像素值。如下图:
layout-viewport

2、重绘

概念:根据渲染树和回流阶段,我们知道了可见节点的几何信息(位置、大小)和样式,那么我们就可以将渲染树的每个节点转换为屏幕上的实际像素,这个阶段就叫做重绘。

3、何时发生回流重绘

从以上我们知道了,回流阶段主要是计算节点的几何信息(位置、大小),那么当页面的布局和几何信息发生变化时,就会发生回流。比如:

  • 添加或删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  • 页面一开始渲染的时候(这肯定避免不了)
  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

总结一句话就是:回流必定会引起重绘,重绘不一定会引起回流。

三、浏览器优化机制

为了提高性能,浏览器通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你使用了如下属性或方法:

offsetTop、offsetLeft、offsetWidth、offsetHeight
clientTop、clientLeft、clientWidth、clientHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
getComputedStyle()、getBoundingClientRect

以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来。

四、如何避免、较少回流重绘

那么,我们如何避免或者减少回流重绘呢?

1、CSS

  • 使用css的calss修改样式,尽可能在DOM树的最末端改变class
  • 使用 visibility 替换 display: none,因为前者只会引起重绘,后者会引发回流
  • 避免使用table布局
  • 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
  • 将动画效果应用到position属性为absolute或fixed的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择 requestAnimationFrame
  • CSS3 硬件加速(GPU加速),使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。

2、JavaScript

  • 动态改变样式使用cssText

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 不推荐写法
    const el = document.getElementById('box');
    el.style.padding = '4px';
    el.style.borderLeft = '2px';
    el.style.width = '50px';
    // 推荐写法:
    const el = document.getElementById('box');
    el.style.cssText += 'padding: 4px; border-left: 2px; width: 50px;';
  • 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。

  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。

参考文献

  • https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=zh-cn
  • https://gist.github.com/paulirish/5d52fb081b3570c81e3a

了解JavaScript执行机制

Veröffentlicht am 2018-05-03 | Visitors:

小例子

1
2
3
4
5
6
7
8
9
10
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});

这段代码的输出是:

1
2
3
4
script start
promise1
promise2
setTimeout

浏览器兼容性:但是不同的浏览器可能会出现不同的输出顺序。

  Microsoft Edge, FireFox 40, iOS Safari 以及 Safari 8.0.8 将会在 ‘promise1’ 和 ‘promise2’ 之前输出 ‘setTimeout’。但是奇怪的是,FireFox 39 和 Safari 8.0.7 却又是按照正确的顺序输出。

原理深入

1.同步异步

  • javascript是单线程的,js执行依照代码书写顺序向下执行。
    正因为js是单线程的,js任务只能一个一个顺序执行,前一个任务未完成,后面的任务只能等待。但是,实际情况是,在渲染页面过程中,常常需要请求像图片视频这样的服务器的资源,这样的js任务很耗时且返回时间未知,会导致页面非常卡,那后面js任务就只能一直等这些资源加载回来,这会导致我们的网页体验非常差,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。
  • 同步任务
  • 异步任务
    网页的渲染过程就是一堆同步任务,比如页面的骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。

Js怎么处理同步异步任务呢???
这就要涉及到一个概念,叫Event Loop(事件循环)。

2.Javascript事件循环Event Loop

js任务执行

图中要表达的含义是:

  • 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数。
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

看个小例子:

演示:loup

1
2
3
4
5
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
},5000);
console.log('script end');

js是单线程的,执行栈先执行同步任务,同步任务执行完毕,才执行异步任务:
1
2
3
小结:事件循环是js实现异步的一种方法,也是js的执行机制。

3.宏任务和微任务

如果将之前的代码改下:

1
2
3
4
5
6
7
8
9
10
11
console.log(1) // snippet1
Promise.resolve().then(function() { // snippet2
console.log(2);
})
setTimeout(function() { // snippet3
console.log(3);
setTimeout(function() { // snippet4
console.log(4)
}, 0)
}, 0)
console.log(5) // snippet5

这段代码的输出顺序是1, 5, 2, 3, 4。
这是因为 promise 的 then 方法,被认为是在微任务队列当中。JavaScript宏观的将任务分为两种:

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick, MutationObserver

Microtask微任务 通常来说就是需要在当前 task 执行结束后立即执行的任务,例如需要对一系列的任务做出回应,或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。microtask 任务队列是一个与 task 任务队列相互独立的队列,microtask 任务将会在每一个 task 任务执行结束之后执行。每一个 task 中产生的 microtask 都将会添加到 microtask 队列中,microtask 中产生的 microtask 将会添加至当前队列的尾部,并且 microtask 会按序的处理完队列中的所有任务。

接下来的主要介绍这两个任务的概念和线程表现:
1.这两种类型的任务会进入与之对应的Event Queue
2.事件循环的顺序,决定JS代码的执行顺序
3.先是进入整体代码的宏任务,开始事件循环,然后紧接着执行当前宏任务的微任务
4.执行完当前宏任务的微任务后 进入Event Queue里面的下一个宏任务
事件循环

这段代码的执行过程是:

  1. snippet1 push 到执行栈,执行完并清空执行栈
  2. snippet2 的回调 push 到 microtask 队列中
  3. snippet3 交给 Web Apis,0ms 后将回调 push 到 marcotask 队列
  4. snippet5 的回调 push 到执行栈,执行完并清空执行栈
  5. script task 执行完后,将 snippet2 中的回调从 microtask 队列取出,push 到执行栈,执行完并清空执行栈
  6. snippet3 的回调 push 到执行栈,执行完并清空执行栈,同时将 snippet4 交给 Web Apis,0ms 后将回调 push 到任务队列
  7. snippet4 的回调 push 到执行栈,执行完并清空执行栈
  • 微任务执行场景
    microtask 通常来说就是需要在当前 task 执行结束后立即执行的任务,例如需要对一系列的任务做出回应,或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。microtask 任务队列是一个与 task 任务队列相互独立的队列,microtask 任务将会在每一个 task 任务执行结束之后执行。每一个 task 中产生的 microtask 都将会添加到 microtask 队列中,microtask 中产生的 microtask 将会添加至当前队列的尾部,并且 microtask 会按序的处理完队列中的所有任务。microtask 类型的任务目前包括了 MutationObserver 以及 Promise 的回调函数和 node 中的 process.nextTick。
  • 浏览器支持

进阶 microtask

我们来看一段代码:

1
2
3
<div class="outer">
<div class="inner"></div>
</div>

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
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
// 给 outer 添加一个观察者
new MutationObserver(function() {
console.log('mutate');
}).observe(outer, {
attributes: true
});
// click 回调函数
function callback() {
console.log('click');
setTimeout(function() {
console.log('timeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise');
});
outer.setAttribute('data-random', Math.random());
}
inner.addEventListener('click', callback);
outer.addEventListener('click', callback);
// inner.click();
1
2
3
4
5
6
7
8
9
10
.outer {
height: 100px;
width: 400px;
background: #ccc;
}
.inner {
height: 50px;
width: 200px;
background: #ddd;
}

我们看下不同浏览器输出:
浏览器运行对比图
按照上面的推导 Chrome 的输出是正确的。

通过上面的例子可以测试出,FireFox 和 Safari 能够正确的执行 microtask 队列,这一点可以通过 MutationObserver 的表现中看出,不过 Promise 被添加至事件队列中的方式好像有些不同。 这一点也是能够理解的,由于 jobs 和 microtasks 的关系以及概念目前还比较模糊,不过人们都普遍的期望他们都能够在两个事件监听器之间执行。这里有 FireFox 和 Safari 的 BUG 记录。(目前 Safari 已经修复了这一 BUG)
在 Edge 中我们可以明显的看出其压入 Promise 的方式是错误的,同时其执行 microtask 队列的方式也不正确,它没有在两个事件监听器之间执行,反而是在所有的事件监听器之后执行,所以才会只输出了一次 mutate 。Edge bug ticket (目前已修复)

接下来,将上面代码最后一行注释去掉,再执行,我们看到输出顺序是这样的:
浏览器执行对比图2

同理在之前的例子中由于我们调用 click(),使得事件监听器的回调函数和当前运行的脚本同步执行,所以当前脚本的执行栈会一直压在 JS 执行栈当中(简单来说就是click的回调并没有加入任务队列中,而是直接执行了)。所以在这个 demo 中 microtask 不会在每一个 click 事件之后执行,而是在两个 click 事件执行完成之后执行。所以在这里我们可以再次的对 microtask 的检查点进行定义:当执行栈(JS Stack)为空时,执行一次 microtask 检查点。这也确保了无论是一个 task 还是一个 microtask 在执行完毕之后都会生成一个 microtask 检查点,也保证了 microtask 队列能够一次性执行完毕。

mutate只输出一次的原因是,MutationObserver微任务只在微任务队列注册一次,因为第二次执行回调函数callback时,发现微任务队列已经注册了dom变化的监听事件,所以不再注册到微任务队列。

原理验证–借用插件演示

推荐一个JS执行的可视化工具loupe [lu:p](备注:暂时不支持微任务演示)

拓展-setTimeout

1.setTimeout
setTimeout的作用就是让异步任务延迟执行
先上一段代码:

1
2
3
4
5
6
7
setTimeout(() => {
task();
},3000)
console.log('执行console');
function task(){
console.log('task')
}

来推断一下执行结果:setTimeout是异步的,应该先执行console.log这个同步任务,所以我们的结论是:

1
2
//执行console
//task()

我们到验证器里运行一下结果,结果是正确的。
在看一段有意思的代码:

1
2
3
4
5
//代码1
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},0);

我们来分析下这段代码,延迟0秒是立即执行的意思吗?显然不是,我们按照js执行机制分析下代码怎么执行的。

  • 首先,console.log进入执行栈执行
  • 接下来解析到setTimeout,发现是异步任务,扔到解析API进行处理(比如浏览器webAPI),解析API延时0秒,把setTimeout的回调函数注册到Event Queue,等到js执行栈为空了,就把Event Queue的任务推入执行栈执行,也就是执行回调函数。

检验–两段有意思的代码

如果你能正确知道这段代码的输出,就说明你真正的理解同步异步了:

1
2
3
4
5
6
7
8
var start;
start = +new Date();
setTimeout(function(){
console.log('setTimeout',+new Date() - start);// part1
},200);
while(start + 2000 > +new Date()){
};

part1打印的时间是多少???

输出是:

1
setTimeout 2003

很明显,part1打印出的时间大于2000毫秒,但不会大于2200毫秒。我们把代码拷贝到控制台运行一下,发现结果是小于2200毫秒的。

关于同步异步另一段有意思的代码

先看一段代码:

1
<button id="btn">click me</button>

1
2
3
4
5
6
let btn = document.getElementById('btn');
console.log('script start');
btn.onclick = function() {
console.log('click')
}
console.log('script end');

如果我们把js代码改一下:

1
2
3
4
5
6
7
let btn = document.getElementById('btn');
console.log('script start');
btn.onclick = function() {
console.log('click')
}
btn.click();
console.log('script end');

这个执行顺序是:
script start
click
script end

原因是我们调用 .click(),使得事件监听器的回调函数和当前运行的脚本同步执行而不再是异步,简单来说就是click的回调并没有加入任务队列中,而是直接执行了。

参考链接:
https://www.cnblogs.com/dong-xu/p/7000139.html
http://blog.xieluping.cn/2018/03/08/event-loop/

crawler

Veröffentlicht am 2018-04-14 | Visitors:

node.js实现简单爬虫

工具:cheerio

cheerio 是nodejs特别为服务端定制的,能够快速灵活的对JQuery核心进行实现。它工作于DOM模型上,且解析、操作、呈送都很高效。
更多API参看:

https://github.com/cheeriojs/cheerio

我们以慕课网页面为例,爬取每个视频课程的标题和课程对应id,期望结构如下:

1
2
3
4
titles = [{
chapterTitle: chapterTitle,
id: id
}]

第一步,我们用node写一个请求,获取想要爬虫的网站html,这里以慕课网为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var http = require('http')
var url = 'http://www.imooc.com/course/list?c=nodejs'
http.get(url, function(res){
var html = ''
res.on('data', function(data){
html += data
})
res.on('end', function(){
var result = filterHml(html)
print(result)
})
}).on('error', function(){
console.log('获取数据错误!')
})

第二步,我们根据需求来编写过滤HTML的函数,将过滤后的数据打印在控制台。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function filterChapters(html) {
var $ = cheerio.load(html)
var chapters = $('.course-card-container')//以类名获取节点元素
var titles = []
chapters.each(function (item) {
var chapter = $(this)
var chapterTitle = chapter.find('h3').text()
var id = chapter.find('a').attr('href').split('learn/')[1]
titles.push({
chapterTitle: chapterTitle,
id: id
})
})
return titles
}
function printCourseInfo(courseData){
courseData.forEach(item => {
console.log('【' + item.id + '】' + item.chapterTitle + '\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
26
27
28
29
30
31
32
33
【935】Vue+Webpack打造todo应用
【882】基于websocket的火拼俄罗斯(单��版)
【861】基于Websocket的火拼俄罗斯(基础)
【866】前端性能优化-通用的缓存SDK
【773】AC2016腾讯前端技术大会
【728】创业公司的Nodejs工程师
【725】Roundtable前端分享专场
【637】进击Node.js基础(二)
【590】阿里D2前端技术论坛——2015融合
【564】去哪儿前端沙龙分享第三期
【556】慕课网技术沙龙之前端专场
【434】去哪儿前端沙龙分享第二期
【367】Qnext前端交互沙龙
【348】进击Node.js基础(一)
【221】D2前端技术论坛——2014绽放
【197】node建站攻略(二期)——网站升级
【75】node+mongodb 建站攻略(一期)

小结:node.js使得JavaScript代码能够运行在服务端,从而进行一些操作,node.js的更多用法参看后续文章.

浅谈原型链

Veröffentlicht am 2017-12-31 | Visitors:

instanceof 简介

在 JavaScript 中,判断一个变量的类型通常会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 “object”。例如:

1
2
var arr = new Array();
console.log( typeof arr ); // object

如果想要确定原型和实例之间的关系就需要用到 instanceof 操作符, 例如:

1
2
3
4
5
6
7
8
var arr = new Array();
var Fn = function() {};
var foo = new Fn();
console.log( arr instanceof Array ); // true
console.log( arr instanceof Object ); // true
console.log( foo instanceof Fn); // true
console.log( foo instanceof Function ); // false
console.log( foo instanceof Object ); // true

Function instanceof Function ?

1
2
3
4
5
console.log( String instanceof String );
console.log( Function instanceof Function );
console.log( Function instanceof Object );
console.log( Object instanceof Function );
console.log( Object instanceof Object );

要解释这个问题就需要了解 1.JavaScript 语言规范中是如何定义 instanceof 运算符的,2.JavaScript 原型继承机制。

instanceof 运算符的定义

在 ECMAScript-262 中 instanceof 运算符的定义是这样的。

12.9.4 Runtime Semantics: InstanceofOperator(O, C)
The abstract operation InstanceofOperator(O, C) implements the generic algorithm for determining if an object O inherits from the inheritance path defined by constructor C. This abstract operation performs the following steps:

1. If Type(C) is not Object, throw a TypeError exception.
2. Let instOfHandler be GetMethod(C,@@hasInstance).
3. ReturnIfAbrupt(instOfHandler).
4. If instOfHandler is not undefined, then
  a. Return ToBoolean(Call(instOfHandler, C, «O»)).
5. If IsCallable(C) is false, throw a TypeError exception.
6. Return OrdinaryHasInstance(C, O).
NOTE Steps 5 and 6 provide compatibility with previous editions of ECMAScript that did not use a @@hasInstance method to define the instanceof operator semantics. If a function object does not define or inherit @@hasInstance it uses the default instanceof semantics.

7.3.19 OrdinaryHasInstance (C, O)
The abstract operation OrdinaryHasInstance implements the default algorithm for determining if an object O inherits from the instance object inheritance path provided by constructor C. This abstract operation performs the following steps:

1. If IsCallable(C) is false, return false.
2. If C has a [[BoundTargetFunction]] internal slot, then
    a. Let BC be the value of C’s [[BoundTargetFunction]] internal slot.
    b. Return InstanceofOperator(O,BC) (see 12.9.4).
3. If Type(O) is not Object, return false.
4. Let P be Get(C, "prototype").
5. ReturnIfAbrupt(P).
6. If Type(P) is not Object, throw a TypeError exception.
7. Repeat
  a. Let O be `O.[[GetPrototypeOf]]()`.
  b. ReturnIfAbrupt(O).
  c. If O is null, return false.
  d. If SameValue(P, O) is true, return true.

官网的定义非常晦涩,上面的翻译成代码大概就是:

1
2
3
4
5
6
7
8
9
function instanceOf( L, R ) { //L 表示左表达式,R 表示右表达式
var P = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while ( true ) {
if ( L === null ) return false;
if ( P === L ) return true;
L = L.__proto__;
}
}

再直接点的表达就是 instanceof 会一直在 obj 的原型链上查找,直到找到右边那个构造函数的 prototype 属性,或者为 null 的时候才停止。

类似:

1
obj.__proto__.__proto__ ... = Obj.prototype

instanceof 会一直沿着隐式原型链 __proto__ 向上查找直到 obj.__proto__.__proto__ ...... === Obj.prototype 为止,如果找到则返回 true,也就是 obj 为 Obj 的一个实例。否则返回 false,obj 不是 Obj 的实例。

JavaScript 原型继承机制

原型与原型链

在 JavaScript 每个函数都有一个 prototype 属性,该属性存储的就是原型对象。JavaScript 构造函数的继承都是通过 prototype 属性, 真正的原型链的实现是通过 __proto__ 实现的,__proto__其实是指向‘父类’的 prototype 属性。例如:

1
2
3
4
var Foo = function() {}
var foo = new Foo;
console.log(foo.__proto__ === Foo.prototype) // true
console.log(Foo.__proto__ === Function.prototype) // true

原型继承

JavaScript 是单继承的,Object.prototype 是原型链的顶端,所有对象从它继承了包括 valueOf、toString 等等方法和属性。

Object 本身是构造函数,继承了 Function.prototype。 Function 也是对象,继承了 Object.prototype。

Object instanceof Object

1
2
3
4
5
6
7
8
9
10
11
ObjectL = Object, ObjectR = Object;
R = ObjectR.prototype = Object.prototype
L = ObjectL.__proto__ = Function.prototype
R != L
// 循环查找 L 是否还有 __proto__
L = Function.prototype.__proto__ = Object.prototype
R == L
// 返回 true

String instanceof String

1
2
3
4
5
6
7
8
9
StringL = String, StringR = String;
R = StringR.prototype = String.prototype
L = StringL.__proto__ = Object.prototype
R != L
// 循环查找 L 是否还有 __proto__
L = Object.prototype.__proto__ = null
// 返回 false

一切皆对象?

常常说 JavaScript 中一切皆对象,那么就有这样一个问题了:

1
2
'string'.__proto__ === String.prototype // true
'string' instanceof String // false

按照上面的推导,'string' instanceof String 应该为 true,但是我们得到的却是 false。
其实问题的关键在于:

1
console.log(typeof 'string'); // string

‘string’ 并不是一个 object 对象,MDN 上对 instanceof 的定义是:

The instanceof operator tests whether an object in its prototype chain has the prototype property of a constructor.

这样又有一个问题了,既然字符串不是对象那为什么有对象才有的属性和方法呢?

1
2
var s1 = "string";
var s2 = s1.substring(2);

为了便于操作基本类型值,ECMAScript 还提供了 3 个特殊的引用类型: Boolean、Number 和 String。 实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们 能够调用一些方法来操作这些数据。

《JavaScript高级程序设计》中是这么解释的:

上面的例子其实当第二行代码访问 s1 时,访问过程处于一种读取模式,也就是要 从内存中读取这个字符串的值。而在读取模式中访问字符串时,后台都会自动完成:
(1) 创建 String 类型的一个实例;
(2) 在实例上调用指定的方法;
(3) 销毁这个实例。

可以将以上三个步骤想象成是执行了下列 ECMAScript 代码。

1
2
3
var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;

《Javascript权威指南》里说:

其实(包装对象)在实现上并不一定创建或销毁这个临时对象,然而整个过程看起来是这样的。

这样 Boolean、Number 是一样的逻辑。还剩下两种基本类型:null 和 undefined。

undefined 当我们对变量只声明没有初始化时,输出为 undefined,typeof undefined 返回的是 undefined 也不是 object 类型,所以 undefined 并不是任何对象的实例。

null 表示的是空对象,虽然 typeof null 是 object,但是 null 和 undefined 一样并没有任何属性和方法,在 instanceof 定义中也有判断,如果类型不是 object(这个类型判断并不是跟 typeof 返回值一样),就返回 false。

浅谈ES5,ES6继承

Veröffentlicht am 2017-12-31 | Visitors:

今天来看下JavaScript中是如何实现继承的?es5和es6在继承实现上有什么区别?带着这两个问题,进入下文。

我们都知道,在JavaScript中,继承是通过原型链来实现的。原型链的知识请自行参考文章从instanceof浅谈原型链。

es5

在es5中继承是这样定义的:子类的原型是父类的一个实例,以此子类可以继承父类的原型上的属性和方法,即继承是通过原型链实现。看图:
es5
看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Super() {
this.property = true;
};
Super.prototype.getSuperValue = function() {
return this.property;
};
function Sub() {
this.subproperty = false;
};
//继承了Super
Sub.prototype = new Super();
Sub.prototype.getSubValue = function() {
return this.subproperty;
};
var instance = new Sub();
console.log(instance.getSuperValue());//true

代码中,定义了两个构造函数Super和Sub,以及他们各自的属性和方法。我们注意到一句代码,Sub.prototype = new Super();Sub的原型实际上是Super类型的一个实例,这样Sub就能够使用Super的方法和属性,即实现了继承。

它们的关系可以进一步检验:

1
2
3
4
Sub.prototype.constructor == Sub;//true
Sub.prototype.__proto__ == Super.prototype;//true
instance.__proto__ == Sub.prototype;//true
instance.constructor == Sub;//true

es5中,继承存在的一个问题是,每次子构造函数从父构造函数继承,需要调用多次父构造函数:继承时,父构造函数需要new一个实例,调用一次;当子构造函数调用父的属性方法会调用多次。

es6

在es6中,继承实现和es5是一样的,都是通过原型链实现的。es6中Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Point() {
};
class ColorPoint extends Point() {
constructor(x,y,color) {
super(x,y);//调用父类的constructor(x,y)
this.color = color;
}
toString() {
return this.color + '' + super.toString();//调用父类的toString()
}
};
var instance = new ColorPoint(25,8,'red');
cp instanceof ColorPoint//ture
cp instanceof Point // true

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

如果子类没有定义constructor方法,这个方法会被默认添加,也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

看图:
es6
从图中,很清晰的看到,相比于es5的继承,子类的 proto 属性指向其父类。

  • 小结:

es5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面。ES6 的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

media query

Veröffentlicht am 2017-12-25 | Visitors:

1.媒体查询(Media query)

  • 原理
    通过对不同设备(比如屏幕宽度width)设置不同的css样式,达到响应式页面效果。
  • 使用
    在css文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @media (max-width:670px;) {
    //设置670px像素下的css样式
    }
    @media (max-width:860px;) {
    //设置在860px屏幕宽度下的css样式
    }
    @media (max-width:1200px;) {
    //设置1200px屏幕宽度下的css样式
    }

2.媒体查询引用

在html文件标签里加入以下代码:

1
2
3
4
5
6
7
8
9
<html>
<head>
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no">//媒体查询引用
<link rel="stylesheet" href="main.less">
</head>
</html>

参数解释:

  • width = device-width:宽度等于当前设备的宽度

  • initial-scale:初始的缩放比例(默认设置为1.0)

  • minimum-scale:允许用户缩放到的最小比例(默认设置为1.0)

  • maximum-scale:允许用户缩放到的最大比例(默认设置为1.0)

3.响应式布局小技能

  • 在响应式布局中,一般设置元素的宽度和高度为百分比值,不建议给元素固定的px值。

    实例:

登录界面中的表单dome.
index.html文件:

1
2
3
4
5
6
7
<div class="login">
<h6>LOGIN QUICK</h6>
<input type="text" class="form" placeholder="E-mail">
<input type="text" class="form" placeholder="Password">
<p class="forget">Forgot Password?</p>
<button class="btn">Login</button>
</div>

css样式(main.less文件):

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
.login {
width:26%;//先设置一个初始值,为百分比值
margin: 7em auto 9em;
h6 {
font-size: 1em;
margin-top:90px;
margin-bottom: 30px;
letter-spacing: 4px;
}
.form {
display: block;
width:100%;
font-size:.9em;
border-radius: 40px;
margin:15px auto;
padding:1em 3em 1em 1em;
box-sizing: border-box;
border: none;
outline: none;
letter-spacing: 1px;
font-weight: 400;
font-family: 'Open Sans', sans-serif;
background: rgba(255, 255, 255, 0.85);
}
.forget {
text-align: right;
font-size: 14px;
}
.btn {
width: 56%;
height: 40px;
background-color: #ea2858;
border-radius: 40px;
border: none;
margin:15px 0;
color:#fff;
font-size: 14px;
outline: none;
cursor: pointer;
// transition: all .1s linear;
transition: background-color .5s linear;
font-weight: 400;
font-family: 'Open Sans', sans-serif;
&:hover {
color: black;
background-color: white;
}
}
}
//利用媒体查询,在不同设备上设置.login的宽度,以达成宽度自适应
@media screen and (min-width: 900px) and (max-width:1200px) {
.login {
width:36%;
margin: 6em auto 5.7em;
}
}
@media screen and (max-width: 768px) {
.login {
width:42%;
margin: 5.4em auto 5em;
}
}
@media (max-width: 736px) {
.login {
width:56%;
margin: 5em auto 4em;
}
}
@media screen and(max-width: 375px) {
.login {
width:90%;
margin: 3.7em auto 4em;
}
}

demo效果图:

pc大屏(1366x866):

1366x866

移动端:
iPhone8:

iPhone8

iPad:

iPad

超小屏(389x866):

389x866

备注:如果在这里设置login固定宽度:

1
2
3
.login {
width:600px;
}

那么网页显示在小屏幕上就会有问题,无法呈现响应式效果。

  • 小结:通过媒体查询,可以实现网页响应式,PC端和移动端渲染是木有问题啦!

git

Veröffentlicht am 2017-12-15 | Visitors:

git工作流程

区别svn的集中式,git是分布式的,
工作流程见图:
工作流程图2
git
git工作区、版本库、暂存区关系:
工作原理图

这里有几个概念:

  • git有工作区和版本库两个概念。而暂存区index(或者stage)属于版本库的一块。
  • 执行git commit操作时,暂存区的目录树写到版本库(对象库)中,master分支也会做相应更新。即master指向暂存区提交的目录树。
  • git push操作推送到共享版本库(对象库)。

常用命令

git pull

从版本库库(远程服务器)更新代码到本地工作副本

1
git pull

git add .

添加文件到暂存区

1
git add .

git commit

提交

1
git commit -m '备注'

git push

将文件推到远程服务器的master主分支上

1
git push origin master

git stutes

查看文件状态

1
git stutes

git checkout branch

切换到分支branch

1
git checkout branch

git merge branch

将分支branch合并到master主干上

1
➜ test git:(master)git merge branch

gti branch -D branchNamed

删除分支branch1

1
gti branch -D branch1

### 总结

  • 与svn相比,git在分支上特别好用,svn 分支只是复制出另外一个文件,是多出一个目录,虽然最后也可以合并branch。而git可以在相同目录下切换分支,分支上开发互不影响,最后合并分支即可。

svn常用指令总结

Veröffentlicht am 2017-12-13 | Visitors:

常用命令总结

svn add

  • 将未版本化文件/新建文件提交到版本库.

    1
    svn add 文件名/目录名
  • 提交文件:

    1
    svn add a.js
  • 提交目录:

    1
    svn add dist
  • 或者进入到待提交目录下:

    1
    svn add .

svn add *会忽略已经在版本控制之下的目录,需要提交工作副本中的所有未版本化文件,使用svn add –force命令,可以递归的提交未版本化文件,包括忽略文件,慎用。

1
2
svn add *
svn add * --force

–force表示递归的提交目录下的所有文件。

svn commit

  • 提交命令.将工作副本中的更改提交到版本库

    1
    svn commit -m '备注'
  • 缩写

    1
    svn ci -m '备注'

svn update

  • 更新命令。将版本库的文件更新到工作副本。

    1
    svn update
  • 缩写:

    1
    svn up

svn log

  • 查看提交日志。
    1
    svn log

svn revert

  • 将文件回滚到提交前,如果有误操作,可以使用svn revert
    1
    svn revert

tag/branch

创建tag
  • 在产品已经开发完准备上线时,就可以创建tag做版本标记,发布给客户用。
    进入到工作副本的trunk目录下:
    1
    svn copy http://svn_server/xxx_repository/trunk http://svn_server/xxx_repository/tags/release-1.0 -m "1.0 released"
创建branch分支

在本期开发中,遇到紧急需求需要临时上线,这时可以创建一个branch分支,进行紧急开发上线。

  • 在工作副本中创建分支:
1
2
3
4
5
root@runoob:~/svn/runoob01# ls
branches tags trunk
root@runoob:~/svn/runoob01# svn copy trunk/ branches/br_001
回车:
A branches/br_001
  • 提交新增的分支到版本库:

    1
    2
    root@runoob:~/svn/runoob01# svn ci -m 'add branches/br_001'
    Adding branches/br_001
  • 切换到trunk,执行svn update,然后将br_001分支合并到trunk主干:

    1
    root@runoob:~/svn/runoob01/trunk# svn ../branches/br_001/
  • 查看主干:

    1
    root@runoob:~/svn/runoob01/trunk# ll

提交到版本库后,

  • 删除branches分支:

    1
    root@runoob:~/svn/runoob01# svn rm svn://191.111.111.11/xxx_repository/branches/br_001
  • 删除tag:

    1
    root@runoob:~/svn/runoob01# svn rm svn://191.111.111.11/xxx_repository/tags/release_001

svn status

查看工作副本中的文件状态,缩写svn st.

  • 我们新建文件index.html,演示下svn st的使用。

    1
    2
    root@runoob:~/svn/runoob01# svn st
    ? index.html
  • ? 问号表示该文件没有在版本控制下,需要先使用svn add命令添加到版本控制中

  • A 表示文件已经成功添加到版本控制中,然后再svn up提交到版本库
  • M 表示本地文件有修改
  • C 表示从版本库更新时与本地工作副本有冲突,必须手动解决冲突
  • !表示文件已在版本控制下,但已丢失,或文件不完整
  • D 表示删除了工作副本中的文件,提交到版本库后才算删除成功
  • I 表示文件不在版本控制下,在使用svn add,,svn ci命令时会忽略文件,不操作。

angular+ui-select+select2

Veröffentlicht am 2017-12-07 | Visitors:

今天项目需求,增加一个下拉搜索框,找来了ui-selet插件,就插件的使用做一下总结分享。

1.引入

npm:

1
npm install ui-select

在angular项目的文件引入模块:

1
2
3
impprt 'ui-select';
var module = angular.module('myapp', ['ui.select', 'ngSanitize']);

小坑:

  • 这个ui-select版本还不完善,需要自己手动引入下css文件,在node_module中找到ui-slect的主文件index.js,将css文件导出一下,如下:

    1
    2
    3
    require('./dist/select.js');
    require('./dist/select.css');
    module.exports = 'ui.select';
  • 此外需要引入angular依赖文件:

    1
    2
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-sanitize.js"></script>

2.基本使用

在html文件中写入:

1
2
3
4
5
6
7
8
<ui-select ng-disabled="disabled" ng-model="arr.selected" style="min-width: 300px;">
<ui-select-match placeholder="请选择">
<span ng-bind="$select.selected.name"></span>
</ui-select-match>
<ui-select-choices repeat="item in arr | filter: $select.search"><span ng-bind="item.name"></span>
</ui-select-choices>
</ui-select>

js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';
var app = angular.module('demo', ['ngSanitize', 'ui.select']);
app.controller('DemoCtrl', function ($scope, $http, $timeout, $interval) {
$scope.arr = [
{
name:'slecet1'
id:1
},
{
name:'slecet2'
id:2
}
]
}

3.增加event事件

在使用select2插件时,发觉事件ng-click和ng-change并不会触发执行。这个问题在ui-select插件的event事件中可以解决。on-selsect=”expression”:

1
2
3
4
5
6
7
<ui-select ng-disabled="disabled" ng-model="arr.selected" on-select="change($item, $model)" style="min-width: 300px;">
<ui-select-match placeholder="请选择">
<span ng-bind="$select.selected.name"></span>
</ui-select-match>
<ui-select-choices repeat="item in arr | filter: $select.search"><span ng-bind="item.name"></span>
</ui-select-choices>
</ui-select>

在js文件:

1
2
3
4
5
//change()函数可以取到当前选中的选项信息$item, $model
$scope.change = function($item, $model) {
//这里写入业务逻辑代码
$scope.id = $item.id;
}

4.设置ui-select默认值

1
2
3
4
$scope.arr.selected = {//ui-select 赋初值
id: '1',
name: '111'
}

因为在给ui-select组件绑定的是对象,所以在设置默认值的时候也必须保持类型一致。

5.总结

小结:更多使用方法参看官网例子:https://github.com/angular-ui/ui-select

如何在npm上发布一个插件

Veröffentlicht am 2017-12-03 | Visitors:

今天尝试在在npm上发布一个插件,做下笔记给大家分享。

1.在GitHub新建一个项目

这次用我自己写的一个gulp插件来做例子,演示下如何一步一步在npm上发布自己的插件。

首先,在GitHub上新建一个项目,我的插件取名叫gulp-px-to-rem,如图:
图3
新建成功后,将gulp-px-to-rem项目克隆到本地。这就是即将发布到npm的插件项目:
图片2

2.在npm注册一个账号

访问npm官网注册一个账号:https://www.npmjs.com。
用命令行登录:

1
npm login

3.发布插件

进入本地项目gulp-px-to-rem的根目录下:

1
npm publish

发布完成后,在npm官网下搜索我们刚发布的插件:
图1
如上,搜搜到插件gulp-px-to-rem,表示已经发布成功!

发布常见问题

1.小坑:注意每次再发布的时候,到package.json文件改下版本号再发布,否则会报错。
2.从github克隆项目下来后,记得使用npm init初始化项目,npm install 安装项目依赖。

备注:
为了在GitHub同步代码,可以把每次发布更新到GitHub。只需三步命令:

1
2
3
git add .
git ci -m '备注'
git push

123
yangjie

yangjie

29 Artikel
15 Tags
© 2020 yangjie
本站总访问量次 本站访客数人次
Erstellt mit Hexo
|
Theme — NexT.Muse