Blog for learning


  • Home

  • Archives

初识分布式系统

Posted on 2020-04-10

基本概念

分布式系统是一个古老而宽泛的话题,近年因“大数据”等概念的兴起,又焕发出了青春。

总的来说,分布式系统要做的任务就是把多台机器有机的组合,连接起来,让其协同完成同一件任务,可以是计算任务,也可以是存储任务。

分布式系统大概包括三大部分:

  1. 分布式存储系统
  2. 分布式计算系统
  3. 分布式管理系统

分布式存储系统

分布式存储系统大概可以分为四个子方向:

  1. 结构化存储
  2. 非结构化存储
  3. 半结构化存储
  4. In-memory 存储

结构化存储(structured storage systems)

典型场景就是 事务处理系统或关系型数据库。 结构化存储系统强调的是:

  1. 结构化的数据(例如关系表)。
  2. 强一致性(例如银行系统,电商系统等场景)。
  3. 随机访问(索引,增删改查,SQL语言)。

然而,正是由于这些性质和限制,结构化存储系统的可扩展性通常不是很好,这在一定程度上限制了大数据环境下的表现。

非结构化存储(no-structured storage systems)

和结构化存储不同,非结构化存储强调的是高扩展性,典型的系统就是分布式系统。

Google在2013年推出的GFS(google file system)做出了里程碑的一步,其开源实现对应为HDFS.GFS的主要思想包括:

  1. 用master来管理metadata。
  2. 文件使用64MB的chunks来存储,并且在多个server上保存多个副本。
  3. 自动容错,自动错误恢复。

虽然分布式文件系统的可扩展性,吞吐率都非常好,但几乎无法支持随机访问(random access)操作,通常只能进行文件追加(append)操作。 这使得非结构化存储系统很难面对低延时,实时性较强的应用。

半结构化存储(semi-structure storage systems)

半结构化存储便是为了解决结非构化存储系统随机访问性能差的问题。NoSQL,Key-Value Store,对象存储等都属于半结构化存储研究的领域。 NoSQL系统既有分布式文件系统所具有的可扩展性,又有结构化存储系统的随机访问能力(例如随机update, read 操作),系统在设计时通常选择简单键值(K-V)进行存储,抛弃了传统RDBMS里复杂SQL查询以及ACID事务。这样做可以换取系统最大的限度的可扩展性和灵活性。

In-memory存储

随着业务的并发越来越高,存储系统对低延迟的要求也越来越高。In-memory存储顾名思义就是将数据存储在内存中, 从而获得读写的高性能。比较有名的系统包括memcahed,以及Redis。这些基于K-V键值系统的主要目的是为基于磁盘的存储系统做cache。

NewSQL

在结构化存储中提到,单机RDBMS系统在可扩展性上面临着巨大的挑战,然而NoSQL不能很好的支持关系模型。那是不是有一种系统能兼备RDBMS的特性(例如:完整的SQL支持,ACID事务支持),又能像NoSQL系统那样具有强大的可扩展能力呢?

2012年Google在OSDI上发表的Spanner,以及2013年在SIGMOD发表的F1,让业界第一次看到了关系模型和NoSQL在超大规模数据中心上融合的可能性。 不过由于这些系统都太过于黑科技了,没有大公司支持应该是做不出来的。比如Spanner里用了原子钟这样的黑科技来解决时钟同步问题,打破光速传输的限制。在这里只能对google表示膜拜。

分布式计算系统

分布式计算最为核心的部分就是容错。由于硬件的老化,有可能会导致某台存储设备没有启动起来,某台机器的网卡坏了,甚至于计算运行过程中断电了,这些都是有可能的。然而最平凡发生的错误是计算进程被杀掉。

分布式计算系统大概分为如下几类:

  1. 传统基于msg的系统
  2. MapReduce-like系统
  3. 图计算系统
  4. 基于状态(state)的系统
  5. Streaming系统

传统基于msg的系统

这类系统里比较有代表性的就是 MPI(message passing interface)。MPI除了提供消息传递接口之外,其框架还实现了资源管理和分配,以及调度的功能。除此之外,MPI在高性能计算里也被广泛使用,通常可以和Infiniband这样的高速网络无缝结合。

除了send和recv接口之外,MPI中另一个接口也值得注意,那就是AllReduce。使用AllReduce通常只需要在单机核心源码里加入AllReduce一行代码,就能完成并行化的功能。因为其底层消息传递使用了tree aggregation,尽可能的将计算分摊到每一个节点。

因为MPI不支持容错,所以很难扩展到大规模集群之上。

MapReduce-like系统

这一类系统又叫作dataflow系统,其中以MapReduce(Hadoop)和Spark为代表。这一类系统的特点是将计算抽象成为high-level operator,例如像map,reduce,filter这样的函数式算子,然后将算子组合成DAG,然后由后端的调度引擎进行并行化调度。

MapReduce-like的编程风格和MPI截然相反。MapReduce对程序的结构有严格的约束——计算过程必须能在两个函数中描述:map和reduce;输入和输出数据都必须是一个一个的records;任务之间不能通信,整个计算过程中唯一的通信机会是map phase和reduce phase之间的shuffling phase,这是在框架控制下的,而不是应用代码控制的。因为有了严格的控制,系统框架在任何时候出错都可以从上一个状态恢复。

由于良好的扩展性,许多人都机器学习算法的并行化任务放在了这些平台之上。然而这些系统最大缺点有两点:

  1. 这些系统所能支持的机器学习模型通常都不是很大。导致这个问题的主要原因是这系统在push back机器学习模型时都是粗粒度的把整个模型进行回传,导致了网络通信的瓶颈。
  2. 严格的BSP同步计算使得集群的效率变的很低。也就是说系统很容易受到straggle的影响。

图计算系统

图计算系统是分布式计算里另一个分支,这些系统都是把计算过程抽象成图,然后在不同节点分布式执行,例如PageRank这样的任务,很适合用图计算系统来表示。

最早成名的图计算系统当属Google的pregel,该系统采用BSP模型,计算以vectex为中心。 除了同步(BSP)图计算系统之外,异步图计算系统里的佼佼者当属GraphLab,该系统提出了GAS的编程模型。

基于状态(state)的系统

这一类系统主要包括2010年OSDI上推出的Piccolo,以及后来2012年nips上Google推出的dist belief再到后来被机器系学习领域广泛应用的Parameter Server架构。

ParameterServer这种state-centric模型则把机器学习的模型存储参数上升为主要组件,并且采用异步机制提升处理能力。它通过采用分布式的 memcached 作为存放参数的存储,这样就提供了有效的机制作用于不同worker节点同步模型参数。

Streaming 系统

Streaming系统是为流式数据提供服务的。其中比较有名的系统包括Storm,SparkStreaming,Flink等等。

分布式管理系统

暂无笔记。

学习总结

鉴于新冠肺炎疫情封城措施,在磊哥的建议下初识了分布式的广阔世界。希望以后有机会能根据公司的业务场景,去继续深挖学习其中一个方向,并在实战中做出成果。

web性能优化的常见方法

Posted on 2019-04-08

web性能优化思路

首先理解页面加载过程,然后从中考虑可能的解决方向。

  1. 缓存
  2. DNS查询 — 减少DNS查询
  3. 建立TCP连接 — 连接复用(keep-alive)
  4. 发送HTTP请求 — 减小cookie — cache-control
  5. 接受响应 — Etag 304 — Gzip
  6. 接受完成
  7. Doctype(html/xml)
  8. 逐行解析
  9. 解析到标签
    • 在页面中渲染标签 (IE)
    • 等CSS解析完了再渲染标签 (chrome)
  10. 解析到CSS
    • 下载CSS
    • 继续解析看还有没有CSS
    • 并行下载,串行解析(IE同时下载4个,chrome同时下载8个)

使用 CDN

使用 Cache-Control

使用 Etag

使用 Gzip

合并文件(CSS、JS、图片)

调整 CSS 和 JS 的位置

CSS放到head里,js放在body最底部。

压缩图片的工具

增加域名以并行下载资源

懒加载和预加载

最开始只加载首屏内容,然后看情况预加载后面的内容。通过js监听onscroll来决定记载哪些内容。

JavaScript中使用prototype和class实现继承的对比

Posted on 2019-04-08

继承的两种写法

ES5写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Human(name){
this.name = name
}
Human.prototype.run = function(){
console.log("我叫"+this.name+",我在跑")
return undefined
}
function Man(name){
Human.call(this, name)
this.gender = '男'
}

var f = function(){}
f.prototype = Human.prototype
Man.prototype = new f()
//上面三行等价于Man.prototype.__proto__ = Human.prototype
//但直接修改__proto__开销很高

Man.prototype.fight = function(){
console.log('糊你熊脸')
}

ES6写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Human{
constructor(name){
this.name = name
}
run(){
console.log("我叫"+this.name+",我在跑")
return undefined
}
}
class Man extends Human{
constructor(name){
super(name)
this.gender = '男'
}
fight(){
console.log('糊你熊脸')
}
}

两种方法的对比

ES5更为直观,便于理解。 ES6语法糖写起来容易,但继承属性不方便,需要写成函数然后return属性值。

《认同》读后感---说服他人认同你的变革计划

Posted on 2019-04-04

《认同》内容精华

主要内容

  • 本书介绍了赢得认同应当遵循的若干原则
  • 归纳了反对派经常使用的抨击手法
  • 详细分析了回应抨击手法的话术

核心观点

  1. 好计划不会自然而然的得到认同,你需要认清变革中的三方关系和博弈过程。
  2. 反对派不是你要说服的对象,甚至也不是阻挡变革的人,而是你的搭档、你的共谋。
  3. 你需要看穿反对派五花八门的说辞——————它们惯用的无非是四种抨击手法。
  4. 在你做出回应时,简洁有力的风格比长篇大论更有效。

1554308359644.png

赢得认同的四个原则

  1. 做好思想准备

    误区:人为好计划自然会赢得人们的认同,而拿大把的时间去构思计划,而疏于考虑如何获得支持。

  2. 认清权力关系

    误区:公布计划反派浮出水面后,全力说服反对的人。事实上,由于利益、性格等方面他们不可能被说服技巧所动摇。实际工作中,大领导和广大同事处在第三方位置,认清变革中推动者、反对派和第三方的权力关系。注意到第三方的认同的重要性。

  3. 借力吸引关注

    以开放的心态欢迎反对意见,以公开讨论的方式吸引关注。

  4. 回应应简洁有力

    长篇大论不适合回应反对派抨击,不是针对某一个质疑做出深度辩护。争取缺少足够耐心深入复杂思考的,第三方认同,需要简短,符合常理。

如何简洁有力的回应抨击

反派观点五花八门,实则万变不离其宗,实则可归纳为四种抨击手法。

制造恐慌

大多数人面对变革,不确定性抱有谨慎的态度,反派利用这一点通常夸大其词,引爆他人的焦虑。

典型说辞:“你的提议会留下许多无法解决的问题,这个问题怎么办,那个又怎么办”,比如“如果美国限枪,那么接下来,明天限制买酒,后天限制买车,然后限制恋爱自由,直到最后完全控制我们的生活。”

回应方式:

  • 总结对方的思维方式,在逻辑上因果依据不充足。
  • 借力打力,列举更为荒谬的例子。

无限拖延

  1. 他们会说你的问题不重要。

这时需要证明问题的紧迫性,拖下去只会让局面更糟。

  1. 批评你的方案不够深入。
  • 你的方案本来很完善,却被对方故意简化,忽略重点,片面概括。
  • 拿”条件不成熟”作为理由:”没有足够预算,缺乏相关经验,其他工作紧迫”。背后逻辑只有一条:”条件不完美,所以不行动”

说清被简化的观点并非本意,承认问题的难度,并强调解决之后产生的价值。 说明确实有不足的地方,同时列举已经具备的条件,筹备计划中的条件,或者针对不足的措施等。引导人们看到积极的方面。

混淆视听

反对者讲一些貌似很深奥、很复杂的道理,让人搞不清你的计划到底是咋回事。 你需要用通俗易懂的方式赶紧澄清。

  1. 反派将你的计划总结为”先有鸡还是先有蛋”的问题,理由是计划包含了A、B两个部分,只要B不做,A就没法做,反过来也一样。 问题举例:

    教授想在学校开一门新课,而只有足够的学生选修这门课,这门课学校才允许开。但不开课,你就不在选修目录上,又怎么知道有多少人报名呢?

实际上,你可以先开小型研讨会,根据研讨会上学生的反馈积累经验,把研讨会的质量做的更扎实,多开几次,吸引更多学生参与。研讨会规模达到一定程度后,就有足够的理由申请把研讨会升级为正式课程,这就是把AB两个部分同时推进的方式。

  1. 反派使用极端的,夸张的语言,让他人觉得你的计划不合理。

你计划提到了,要买的那种设备非常便宜,后来又说这种设备要十分耐用。但是耐用的东西一定会花更多钱。那到底是非常便宜还是十分耐用呢?不可能两样都占吧。

实际上,并没有要求设备既要非常便宜又要十分耐用,而只是价格在我们接受的范围内,同时设备质量可以达到行业平均水平,这两种要求可以同时满足。

人身攻击

反对者歪曲你做事的动机,诱发人们的不满和怀疑。

  • 你的计划违背了企业长期以来的价值观。
  • 你的计划是在职责其他员工没有早点解决这个问题吗?

你是为了成事,为了创造价值,而他们是为了坏事,捣乱、搞破坏。如果你把自己降到跟他们同样的层次,以暴制暴,以牙还牙的时候,人们会觉得你不具备做成事的能力和胸怀。甚至反过来同情抨击你的人。 所以面对人身攻击要保持风度,这样才能在别人心中占领道德制高点。 然后,要表达你对第三方的重视和诚意,表示计划恰恰是坚持企业价值观,并真诚感激其他员工的辛勤劳动打下的基石,才会出现创造更大价值的机会。

节流与防抖

Posted on 2019-03-27

节流与防抖的概念

函数节流(throttle)

预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期

函数防抖(debounce)

当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间

函数节流(throttle)与 函数防抖(debounce)都是为了限制函数的执行频次,以优化函数触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象。

节流与防抖的应用

常见于如下状况:

  • window对象的resize、scroll事件
  • 拖拽时的mousemove事件
  • 文字输入、自动完成的keyup事件

节流

scroll事件在文档或文档元素滚动时触发,主要出现在用户拖动滚动条。

1
window.addEventListener('scroll', callback);

该事件会连续地大量触发,所以它的监听函数之中不应该有非常耗费计算的操作。推荐的做法是使用requestAnimationFrame或setTimeout控制该事件的触发频率,然后可以结合customEvent抛出一个新事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function () {
var throttle = function (type, name, obj) {
var obj = obj || window;
var running = false;
var func = function () {
if (running) { return; }
running = true;
requestAnimationFrame(function() {
obj.dispatchEvent(new CustomEvent(name));
running = false;
});
};
obj.addEventListener(type, func);
};

// 将 scroll 事件重定义为 optimizedScroll 事件
throttle('scroll', 'optimizedScroll');
})();

window.addEventListener('optimizedScroll', function() {
console.log('Resource conscious scroll callback!');
});

上面代码中,throttle函数用于控制事件触发频率,requestAnimationFrame方法保证每次页面重绘(每秒60次),只会触发一次scroll事件的监听函数。也就是说,上面方法将scroll事件的触发频率,限制在每秒60次。具体来说,就是scroll事件只要频率低于每秒60次,就会触发optimizedScroll事件,从而执行optimizedScroll事件的监听函数。

改用setTimeout方法,可以放置更大的时间间隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function() {
window.addEventListener('scroll', scrollThrottler, false);

var scrollTimeout;
function scrollThrottler() {
if (!scrollTimeout) {
scrollTimeout = setTimeout(function () {
scrollTimeout = null;
actualScrollHandler();
}, 66);
}
}

function actualScrollHandler() {
// ...
}
}());

上面代码中,每次scroll事件都会执行scrollThrottler函数。该函数里面有一个定时器setTimeout,每66毫秒触发一次(每秒15次)真正执行的任务actualScrollHandler。

下面是一个更一般的throttle函数的写法,scroll事件的触发频率,限制在一秒一次。

1
2
3
4
5
6
7
8
9
10
11
function throttle(fn, wait) {
var time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
fn();
time = Date.now();
}
}
}

window.addEventListener('scroll', throttle(callback, 1000));

防抖

有时,我们不希望回调函数被频繁调用。比如,用户填入网页输入框的内容,希望通过 Ajax 方法传回服务器,jQuery 的写法如下。

1
$('textarea').on('keydown', ajaxAction);

这样写有一个很大的缺点,就是如果用户连续击键,就会连续触发keydown事件,造成大量的 Ajax 通信。这是不必要的,而且很可能产生性能问题。正确的做法应该是,设置一个门槛值,表示两次 Ajax 通信的最小间隔时间。如果在间隔时间内,发生新的keydown事件,则不触发 Ajax 通信,并且重新开始计时。如果过了指定时间,没有发生新的keydown事件,再将数据发送出去。

这种做法叫做debounce(防抖动)。假定两次 Ajax 通信的间隔不得小于2500毫秒,上面的代码可以改写成下面这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
$('textarea').on('keydown', debounce(ajaxAction, 2500));

function debounce(fn, delay){
var timer = null; // 声明计时器
return function() {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}

上面代码中,只要在2500毫秒之内,用户再次击键,就会取消上一次的定时器,然后再新建一个定时器。这样就保证了回调函数之间的调用间隔,至少是2500毫秒。

通过lodash使用节流函数和防抖函数

_.throttle(func, [wait=0], [options={}])

创建一个节流函数,在 wait 秒内最多执行 func 一次的函数。 该函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。 可以提供一个 options 对象决定如何调用 func 方法, options.leading 与|或 options.trailing 决定 wait 前后如何触发。 func 会传入最后一次传入的参数给这个函数。 随后调用的函数返回是最后一次 func 调用的结果。

参数

  • func (Function): 要节流的函数。
  • [wait=0] (number): 需要节流的毫秒。
  • [options={}] (Object): 选项对象。
  • [options.leading=true] (boolean): 指定调用在节流开始前。
  • [options.trailing=true] (boolean): 指定调用在节流结束后。

返回

  • (Function): 返回节流的函数。

例子

1
2
3
4
5
6
7
8
9
// 避免在滚动时过分的更新定位
jQuery(window).on('scroll', _.throttle(updatePosition, 100));

// 点击后就调用 `renewToken`,但5分钟内超过1次。
var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
jQuery(element).on('click', throttled);

// 取消一个 trailing 的节流调用。
jQuery(window).on('popstate', throttled.cancel);

_.debounce(func, [wait=0], [options={}])

创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。 debounced(防抖动)函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。 可以提供一个 options(选项) 对象决定如何调用 func 方法,options.leading 与|或 options.trailing 决定延迟前后如何触发(愚人码头注:是 先调用后等待 还是 先等待后调用)。 func 调用时会传入最后一次提供给 debounced(防抖动)函数 的参数。 后续调用的 debounced(防抖动)函数返回是最后一次 func 调用的结果。

参数

  • func (Function): 要防抖动的函数。
  • [wait=0] (number): 需要延迟的毫秒数。
  • [options={}] (Object): 选项对象。
  • [options.leading=false] (boolean): 指定在延迟开始前调用。
  • [options.maxWait] (number): 设置 func 允许被延迟的最大值。
  • [options.trailing=true] (boolean): 指定在延迟结束后调用。

返回

  • (Function): 返回新的 debounced(防抖动)函数

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 避免窗口在变动时出现昂贵的计算开销。
jQuery(window).on('resize', _.debounce(calculateLayout, 150));

// 当点击时 `sendMail` 随后就被调用。
jQuery(element).on('click', _.debounce(sendMail, 300, {
'leading': true,
'trailing': false
}));

// 确保 `batchLog` 调用1次之后,1秒内会被触发。
var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
var source = new EventSource('/stream');
jQuery(source).on('message', debounced);

// 取消一个 trailing 的防抖动调用
jQuery(window).on('popstate', debounced.cancel);

Reflow和Repaint对前端性能的影响

Posted on 2019-03-04

Reflow和Repaint是什么?

Repaint 就是“重绘”,它会在你改变 DOM 元素的视觉效果时进行,改变布局时不会触发。比如,opacity,background-color,visibility和outline等都会触发,“重绘”的开销还是比较昂贵的,因为浏览器会在某一个DOM元素的视觉效果改变后去check这个DOM元素内的所有节点。

Reflow 就是“回流”,它的影响更大。它会在某一个DOM元素的位置发生改变后触发,而且它会重新计算所有元素的位置和在页面中的占有的面积,这样的话将会引起页面某一个部分甚至整个页面的重新渲染。改变某一个元素会影响它所有的子节点 (children)、祖先节点 (ancestors) 及兄弟节点(siblings)。

浏览器的对待页面是先布局后渲染,在PC端Reflow和Repaint对于性能的影响是微乎其微,但是在移动端这俩货简直就是性能杀手。

repaint和reflow是DOM操作影响性能的主要原因。一个节点触发了repaint,浏览器会检查DOM Tree中其他所有节点的显示方式;一个节点触发了reflow会导致它的祖先节点,后代节点以及在它之后的节点全部reflow。reflow对性能的影响大于repaint。

什么情况下会触发Reflow?

添加、删除或者改变DOM元素的可见性时:使用JS去改变DOM元素时会触发Reflow。 添加、删除或者改变CSS样式:直接改变CSS Style或者元素的class可能会影响布局,还有改变一个元素的宽度能够影响它所在的DOM节点中的所有元素,以及它周围的那些元素。 CSS3 动画(animation)和过渡(transition): 动画的每一frame都会触发Reflow。 使用offsetWidth和offsetHeight:这一点很特别,你读一个DOM的offsetWidth和offsetHeight属性同样会触发一下Reflow,因为这两个属性需要依赖一些元素去计算。 ** 用户交互:用户可以通过:hover一下<a>链接,在input里面输入文字,拖动浏览器的大小,改变字体大小,更换样式表或者字体等都会触发reflow。

如何提高性能?

一些常用的提高性能的原则

方面 方法
布局 不要用 inline style 或 table 布局,flexbox 布局也会给性能带来一些小困扰。inline style 会在 html下载完后进行一次额外的 Reflow,table布局的开销远比其他 DOM 元素的布局开销要大。flexbox 的 item 会在HTML 下载完成后改变尺寸。
简写CSS 尽量简写CSS,避免使用复杂的CSS选择器,使用 Unused CSS https://unused-css.com/), uCSS(https:/github.com/oyvindeh/ucss), gulp-uncss(https://github.com/ben-eb/gulp-uncss)可以有效的减少样式定义和文件的大小。
优化DOM 减少DOM的层级,减少DOM的数量,如果不需适配老浏览器,删掉一些无用的wrapper性质的DOM元素,总之越少好。
慎改class 在一个DOM树中,尽可能改那些没有特别多子元素DOM的class,子元素少的可以改,多的不推荐。
避免复杂动画 删掉复杂的动画,运用动画的元素尽量是position:absolute或position:fixed的,这样会让他们脱离文档流不去影响其他的元素。
善用display:none display:none的元素不会引发Reflow和Repaint,可以在让这些元素在display之前进行一些诸如颜色、尺寸么的改变。
批量更新元素 将状态写入一个类中,用JQ直接给元素添加或删除这个类,而不是直接通过方法来修改元素样式状态。
避免大量DOM互相影响 比如Tabs这种场景,如果你点击一个Tab会显示它控制的区块,显示的那个区块会影响其他的区块,这样可能会起Reflow,因为它们的高度不一样,可以通过定个高度来优化这种场景。
性能永远比酷炫重要 记住一个原则,你网页的动画再牛逼,性能还是第一位的,如果每一帧移动1个像素会造成你的页面卡顿,那宁愿一帧移动10像素让动画的帧变得迟钝一些,也不要让页面的性能降下来。

参考链接

Cookie与登录注册

Posted on 2019-02-07

Cookie 的特点

  1. 服务器通过 Set-Cookie 响应头设置 Cookie
  2. 浏览器得到 Cookie 之后,每次请求都要带上 Cookie
  3. 服务器读取 Cookie 就知道登录用户的信息(email)

问题

  1. 我在 Chrome 登录了得到 Cookie,用 Safari 访问,Safari 会带上 Cookie 吗 no

  2. Cookie 存在哪 Windows 存在 C 盘的一个文件里

  3. Cookie会被用户篡改吗? Session 可以解决这个问题,防止用户篡改

  4. Cookie 有效期吗? 默认有效期20分钟左右,不同浏览器策略不同 后端可以强制设置有效期

    1
    2
    3
    4
    5
    设置过期时间:
    Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date> Expires //用于定义在哪个时间过期
    Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit> max-age //用于定义在多久之后会过期
    删除cookie:
    docCookies.removeItem(name[, path],domain)
  5. Cookie 遵守同源策略吗? 也有,不过跟 AJAX 的同源策略稍微有些不同。 当请求 qq.com 下的资源时,浏览器会默认带上 qq.com 对应的 Cookie,不会带上 baidu.com 对应的 Cookie 当请求 v.qq.com 下的资源时,浏览器不仅会带上 v.qq.com 的Cookie,还会带上 qq.com 的 Cookie

Session 与 Cookie 的关系

一般来说,Session 基于 Cookie 来实现。

Cookie

  1. 服务器通过 Set-Cookie 头给客户端一串字符串
  2. 客户端每次访问相同域名的网页时,必须带上这段字符串
  3. 客户端要在一段时间内保存这个Cookie
  4. Cookie 默认在用户关闭页面后就失效,后台代码可以任意设置 Cookie 的过期时间
  5. 大小大概在 4kb 以内

Session

  1. 将 SessionID(随机数)通过 Cookie 发给客户端
  2. 客户端访问服务器时,服务器读取 SessionID
  3. 服务器有一块内存(哈希表)保存了所有 session
  4. 通过 SessionID 我们可以得到对应用户的隐私信息,如 id、email
  5. 这块内存(哈希表)就是服务器上的所有 session

LocalStorage

  1. LocalStorage 跟 HTTP 无关
  2. HTTP 不会带上 LocalStorage 的值
  3. 只有相同域名的页面才能互相读取 LocalStorage(没有同源那么严格)
  4. 每个域名 localStorage 最大存储量为 5Mb 左右(每个浏览器不一样)
  5. 常用场景:记录有没有提示过用户(没有用的信息,不能记录密码)
  6. LocalStorage 永久有效,除非用户清理缓存

SessionStorage(会话存储)

1、2、3、4 同上

  1. SessionStorage 在用户关闭页面(会话结束)后就失效。

Cookie 和 Session 的区别

Cookie 保存在客户端,每次都随请求发送给 Server Session 保存在 Server 的内存里,其 Session ID 是通过 Cookie 发送给客户端的

Cookie 和 LocalStorage 的区别

LocalStorage 不会随 HTTP 发给 Server LocalStorage 的大小限制比 Cookie 大多了

LocalStorage 和 SessionStorage 的区别

一个不会自动过期,一个会自动过期。

Cache-Control: max-age=1000 缓存 与 ETag 的「缓存」有什么区别?

Cache-Control 直接不发请求。 而 ETag 要发请求才行。


登录注册

服务端代码server.js

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]

if(!port){
console.log('请指定端口号\nnode server.js 8888 这样')
process.exit(1)
}

var server = http.createServer(function(request, response){
var parsedUrl = url.parse(request.url, true)
var pathWithQuery = request.url
var queryString = ''
if(pathWithQuery.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) }
var path = parsedUrl.pathname
var query = parsedUrl.query
var method = request.method

/******** 从这里开始看,上面不要看 ************/

console.log('含查询字符串的路径\n' + pathWithQuery)

if(path === '/'){
let string = fs.readFileSync('./index.html', 'utf8')
let cookies = request.headers.cookie.split('; ') // ['email=1@', 'a=1', 'b=2']
let hash = {} //将cookie存放到一个hash表中
for(let i =0;i<cookies.length; i++){
let parts = cookies[i].split('=')
let key = parts[0]
let value = parts[1]
hash[key] = value
}
let email = hash.sign_in_email
let users = fs.readFileSync('./db/users', 'utf8')
users = JSON.parse(users)
let foundUser //确认服务器是否有此用户信息
for(let i=0; i< users.length; i++){
if(users[i].email === email){
foundUser = users[i]
break
}
}
console.log(foundUser)
if(foundUser){
string = string.replace('__password__', foundUser.password)
}else{
string = string.replace('__password__', '不知道')
}
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(string)
response.end()
}else if(path === '/sign_up' && method === 'GET'){ //路由sign_up的逻辑
let string = fs.readFileSync('./sign_up.html', 'utf8')
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(string)
response.end()
}else if(path === '/sign_up' && method === 'POST'){
readBody(request).then((body)=>{
let strings = body.split('&') // ['email=1', 'password=2', 'password_confirmation=3']
let hash = {} //将用户提交的信息存到一个hash表里
strings.forEach((string)=>{
// string == 'email=1'
let parts = string.split('=') // ['email', '1']
let key = parts[0]
let value = parts[1]
hash[key] = decodeURIComponent(value) // hash['email'] = '1'
})
let {email, password, password_confirmation} = hash
if(email.indexOf('@') === -1){ //如果用户填写的邮箱地址不含有@
response.statusCode = 400
response.setHeader('Content-Type', 'application/json;charset=utf-8')
response.write(`{
"errors": {
"email": "invalid"
}
}`)
}else if(password !== password_confirmation){
response.statusCode = 400
response.write('password not match')
}else{
var users = fs.readFileSync('./db/users', 'utf8')
try{
users = JSON.parse(users) // []
}catch(exception){
users = [] //如果解析本地db文件失败则设置为空数组
}
let inUse = false //确定本地是否有相同账户邮箱名
for(let i=0; i<users.length; i++){
let user = users[i]
if(user.email === email){
inUse = true
break;
}
}
if(inUse){
response.statusCode = 400
response.write('email in use')
}else{
users.push({email: email, password: password})
var usersString = JSON.stringify(users)
fs.writeFileSync('./db/users', usersString)
response.statusCode = 200
}
}
response.end()
})
}else if(path==='/sign_in' && method === 'GET'){ //sign_in逻辑与sign_up类似
let string = fs.readFileSync('./sign_in.html', 'utf8')
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(string)
response.end()
}else if(path==='/sign_in' && method === 'POST'){
readBody(request).then((body)=>{
let strings = body.split('&') // ['email=1', 'password=2', 'password_confirmation=3']
let hash = {}
strings.forEach((string)=>{
// string == 'email=1'
let parts = string.split('=') // ['email', '1']
let key = parts[0]
let value = parts[1]
hash[key] = decodeURIComponent(value) // hash['email'] = '1'
})
let {email, password} = hash
var users = fs.readFileSync('./db/users', 'utf8')
try{
users = JSON.parse(users) // []
}catch(exception){
users = []
}
let found
for(let i=0;i<users.length; i++){
if(users[i].email === email && users[i].password === password){
found = true
break
}
}
if(found){
response.setHeader('Set-Cookie', `sign_in_email=${email}`)
response.statusCode = 200
}else{
response.statusCode = 401
}
response.end()
})
}else if(path==='/main.js'){
let string = fs.readFileSync('./main.js', 'utf8')
response.statusCode = 200
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
response.write(string)
response.end()
}else{
response.statusCode = 404
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(`
{
"error": "not found"
}
`)
response.end()
}

/******** 代码结束,下面不要看 ************/
})

function readBody(request){
return new Promise((resolve, reject)=>{
let body = []
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
resolve(body)
})
})
}

server.listen(port)
console.log('监听 ' + port + ' 成功\n请打开 http://localhost:' + port)

sign_up

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
<body>
<div class="form-wrapper">
<h1>注册</h1>
<form id="signUpForm">
<div class="row">
<label>邮箱</label>
<input type="text" name="email">
<span class="error"></span>
</div>
<div class="row">
<label>密码</label>
<input type="password" name="password">
<span class="error"></span>
</div>
<div class="row">
<label>确认密码</label>
<input type="password" name="password_confirmation">
<span class="error"></span>
</div>
<div class="row">
<input type="submit" value="注册">
</div>
</form>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
let $form = $('#signUpForm')
$form.on('submit', (e)=>{
e.preventDefault()
let hash = {}
let need = ['email', 'password', 'password_confirmation']
need.forEach((name)=>{
let value = $form.find(`[name=${name}]`).val()
hash[name] = value
})
$form.find('.error').each((index, span)=>{
$(span).text('')
})
if(hash['email'] === ''){
$form.find('[name="email"]').siblings('.error')
.text('填邮箱呀同学')
return
}
if(hash['password'] === ''){
$form.find('[name="password"]').siblings('.error')
.text('填密码呀同学')
return
}
if(hash['password_confirmation'] === ''){
$form.find('[name="password_confirmation"]').siblings('.error')
.text('确认密码呀同学')
return
}
if(hash['password'] !== hash['password_confirmation']){
$form.find('[name="password_confirmation"]').siblings('.error')
.text('密码不匹配')
return
}
$.post('/sign_up', hash)
.then((response)=>{
console.log(response)
}, (request)=>{
let {errors} = request.responseJSON
if(errors.email && errors.email === 'invalid'){
$form.find('[name="email"]').siblings('.error')
.text('邮箱格式错误')
}
})
})
</script>
</body>

sign_in

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
<body>
<div class="form-wrapper">
<h1>登录</h1>
<form id="signInForm">
<div class="row">
<label>邮箱</label>
<input type="text" name="email">
<span class="error"></span>
</div>
<div class="row">
<label>密码</label>
<input type="password" name="password">
<span class="error"></span>
</div>
<div class="row">
<input type="submit" value="登录">
</div>
</form>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
let $form = $('#signInForm')
$form.on('submit', (e)=>{
e.preventDefault()
let hash = {}
let need = ['email', 'password']
need.forEach((name)=>{
let value = $form.find(`[name=${name}]`).val()
hash[name] = value
})
$form.find('.error').each((index, span)=>{
$(span).text('')
})
if(hash['email'] === ''){
$form.find('[name="email"]').siblings('.error')
.text('填邮箱呀同学')
return
}
if(hash['password'] === ''){
$form.find('[name="password"]').siblings('.error')
.text('填密码呀同学')
return
}
$.post('/sign_in', hash)
.then((response)=>{
window.location.href = '/'
}, (request)=>{
alert('邮箱与密码不匹配')
})
})
</script>
</body>

index.html

1
2
3
<body>
<h1>你的密码是:__password__</h1>
</body>

CSS深入浅出总结

Posted on 2019-01-18

CSS 的学习思路

理解常用原理,熟练常用套路。

  • 控制文档流:利用好默认的宽度和高度规则
  • 控制 z-index:堆叠上下文和堆叠顺序
  • 理解 BFC
  • 理解 IFC
  • 理解 Float 布局和 Flex 布局
  • 理解响应式
  • 掌握动态 REM 方案
  • 掌握 icon 的使用
  • 掌握 Bootstrap 的使用
  • 其他套路收集

别人研究出来我直接用即可。

animate.css CSS tricks Codepen Codrops itmeo

控制文档流:利用好默认的宽度和高度规则

  • 文档流中内联元素默认从左到右排列,宽度不够则自动换行
  • 文档流中块级元素从上到下排列,每个元素占一行
  • float:left、position:absolute、position: fixed 可以使元素脱离文档流
  • 块级元素的高度由其文档流中元素高度的总和确定
  • 给 inline 元素设置宽高是没有任何效果的
  • height 可以用 line-height 和 padding 来撑,这样写更灵活美观

两行文字「姓名」与「联系方式」两端对齐套路

1
2
3
4
5
6
7
8
9
10
11
.span{
display:inline-block;
width:4em;
text-align:justify;
overflow:hidden;
}
.span::after{
content:"";
display:inline-block;
width:100%;
}

多行(3)文本超出部分变成省略号的套路

1
2
3
4
5
6
7
${
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}

控制 z-index:堆叠上下文和堆叠顺序

堆叠上下文

堆叠顺序

  • z-index: -
  • background
  • border
  • 块级
  • 浮动
  • 内联
  • z-index: 0
  • z-index: +
  • 如果是兄弟元素重叠,那么后面的盖在前面的身上。

堆叠作用域,跟 BFC 一样,如下属性会触发堆叠上下文

  • 根元素 (HTML),
  • z-index 值不为 “auto”的 绝对/相对定位,
  • 一个 z-index 值不为 “auto”的 flex 项目 (flex item),即:父元素 display: flex|inline-flex,
  • opacity 属性值小于 1 的元素(参考 the specification for opacity),
  • transform 属性值不为 “none”的元素,
  • mix-blend-mode 属性值不为 “normal”的元素,
  • filter值不为“none”的元素,
  • perspective值不为“none”的元素,
  • isolation 属性被设置为 “isolate”的元素,
  • position: fixed
  • 在 will-change 中指定了任意 CSS 属性,即便你没有直接指定这些属性的值
  • -webkit-overflow-scrolling 属性被设置 “touch”的元素

理解 BFC

一个块格式化上下文(block formatting context) 是Web页面的可视化CSS渲染出的一部分。它是块级盒布局出现的区域,也是浮动层元素进行交互的区域。

一个块格式化上下文由以下之一创建:

  • 根元素或其它包含它的元素
  • 浮动元素 (元素的 float 不是 none)
  • 绝对定位元素 (元素具有 position 为 absolute 或 fixed)
  • 内联块 (元素具有 display: inline-block)
  • 表格单元格 (元素具有 display: table-cell,HTML表格单元格默认属性)
  • 表格标题 (元素具有 display: table-caption, HTML表格标题默认属性)
  • 具有overflow 且值不是 visible 的块元素,
  • display: flow-root
  • column-span: all 应当总是会创建一个新的格式化上下文,即便具有 column-span: all 的元素并不被包裹在一个多列容器中。 一个块格式化上下文包括创建它的元素内部所有内容,除了被包含于创建新的块级格式化上下文的后代元素内的元素。

BFC的作用:

  1. 父元素管子元素:用 BFC 包住浮动元素。
  2. 兄弟元素划清界限:用 float + div 做左右自适应布局。

避免其他bug的触发BFC套路:display: flow-root; /*触发BFC*/

理解 IFC

IFC(inline formatting context)规则:在行内格式化上下文中,框(boxes)一个接一个地水平排列,起点是包含块的顶部。水平方向上的 margin,border 和 padding在框之间得到保留。框在垂直方向上可以以不同的方式对齐:它们的顶部或底部对齐,或根据其中文字的基线对齐。包含那些框的长方形区域,会形成一行,叫做行框。

《深入理解 CSS:字体度量、line-height 和 vertical-align》

IFC应用:

  • 图片下面有空隙 vertical-align: top img{display: block;} font-size: 0 不建议,bug多
  • inline-block 元素对不齐 : 用 flex 或 float
  • inline-block 有空隙 : flex 或 float

理解 Float 布局和 Flex 布局

  • 不到万不得已,不要写死 width 和 height
  • 尽量用高级语法,如 calc、flex
  • 如果是 IE,就全部写死

Flex 布局教程 如果宽度不够,可以用 margin: 0 -4px;

float布局父元素加clearfix

1
2
3
4
5
6
7
8
.clearfix:after{
content: '';
display: block;
clear: both;
}
.clearfix{
zoom: 1;
}

理解响应式

手机端页面的做法:

  • media query
  • 先确保设计图,后写代码
  • 隐藏元素
  • 手机端要加一个 meta<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

手机端的交互方式不一样

  • 没有 hover
  • 有 touch 事件
  • 没有 resize
  • 没有滚动条

掌握动态 REM 方案

  1. 手机端方案的特点

    • 所有手机显示的界面都是一样的,只是大小不同
    • 1 rem == html font-size == viewport width
  2. 使用 JS 动态调整 REM

    1
    2
    3
    4
    <script>
    var pageWidth = window.innerWidth
    document.write('<style>html{font-size:'+pageWidth+'px;}</style>')
    </script>
  3. REM 可以与其他单位同时存在

  4. 在 SCSS 里使用 PX2REM

    1
    2
    3
    4
    5
    6
    mkdir ~/Desktop/scss-demo
    cd ~/Desktop/scss-demo
    mkdir scss css
    touch scss/style.scss
    start scss/style.scss
    node-sass -wr scss -o css

    编辑 scss 文件就会自动得到 css 文件 在 scss 文件里添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @function px( $px ){
    @return $px/$designWidth*10 + rem;
    }
    $designWidth : 640; // 640 是设计稿的宽度,你要根据设计稿的宽度填写。
    .child{
    width: px(320);
    height: px(160);
    margin: px(40) px(40);
    border: 1px solid red;
    float: left;
    font-size: 1.2em;
    }

    即可实现 px 自动变 rem

掌握 icon 的使用

使用SVG 第一步:拷贝项目下面生成的symbol代码:

1
//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js

第二步:加入通用css代码(引入一次就行):

1
2
3
4
5
6
7
8
<style type="text/css">
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

第三步:挑选相应图标并获取类名,应用于页面:

1
2
3
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xxx"></use>
</svg>

掌握 Bootstrap 的使用

Bootstrap 的使用方法就是复制粘贴

其他套路收集

磨砂效果思路:底部色彩用background-color,白噪声采用png图片作为background-image。优化一下,采用base64方式设background-image。

1
2
3
4
body {
background-color: #000000;
background-image: url(data:image/png;base64,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);
}

参考资料

磨砂玻璃效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
body, main::before { 
background: url("tiger.jpg") 0 / cover fixed;
}
main {
position: relative;
background: hsla(0,0%,100%,.3);
overflow: hidden;
}
main::before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
filter: blur(20px);
margin: -30px;
}

参考资料

实现一个jQuery的API

Posted on 2019-01-07
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
window.jQuery = function (nodeOrSelector) { //接受一个节点或者CSS选择器来找到目标
let nodes = {} //初始化一个空的nodes对象,来存放获取到的目标对象
if (typeof nodeOrSelector === 'string') { //如果接受到的参数是CSS选择器
let temp = document.querySelectorAll(nodeOrSelector) //通过DOM的querySelectorAll方法来找到目标对象存放到temp中
for (let i = 0; i < temp.length; i++) { //遍历这个CSS选择器找到的所有目标
nodes[i] = temp[i] // 将获取的伪数组的每个值一一存放到nodes对象中
}
nodes.length = temp.length //将获取到的伪数组的长度值赋给nodes对象的长度
} else if (nodeOrSelector instanceof Node) { //如果接受到的参数是一个节点
nodes = { //直接将改节点存放到nodes对象中
0: nodeOrSelector, //有且仅有一个的值即节点本身
length: 1 //对象只存了一个节点故长度为1
}
}
nodes.addClass = function (classes) { //设置一个添加类名的函数addClass,可接受多个类名
classes.forEach(value => { //多个类名通过数组存放,通过数组的forEach方法遍历类名数组的每一个值。
for (let i = 0; i < nodes.length; i++) { //遍历获取到的每个节点
nodes[i].classList.add(value) //将类名数组的每一个值都添加到目标节点的classList中
}
})
}
nodes.Text = function (text) { //设置一个获取文本或添加文本的函数Text
if (text === undefined) { //如果用户没有提供参数,默认为获取目标节点中的文本
var texts = [] //初始化一个空的数组对象,来存放文本
for (let i = 0; i < node.length; i++) { //遍历获取到的每个节点
texts.push(node[i].textContent) //通过DOM的textContent方法来获取节点中的文本,并通过数组的push方法将文本存到texts数组中
}
return texts //将存放完文本的texts数组返回给用户
} else { //如果用户提供了参数,默认为将参数设置为目标节点中的文本
for (let i = 0; i < nodes.length; i++) { //遍历获取到的每个节点
nodes[i].textContent = text //通过DOM的textContent方法将用户提供的参数设置为节点中的文本。
}
}
}
return nodes //返回获取到的节点,提供给客户使用。
}

原型与原型链

Posted on 2018-12-31

全局对象 window

ECMAScript 规定全局对象叫做 global,但是浏览器把 window 作为全局对象(浏览器先存在的) window 就是一个哈希表,有很多属性。 window 的属性就是全局变量。 这些全局变量分为两种:

  1. 一种是 ECMAScript 规定的
  • global.parseInt
  • global.parseFloat
  • global.Number
  • global.String
  • global.Boolean
  • global.Object
  1. 一种是浏览器自己加的属性
  • window.alert
  • window.prompt
  • window.comfirm
  • window.console.log
  • window.console.dir
  • window.document
  • window.document.createElement
  • window.document.getElementById

全局函数

  1. Number var n = new Number(1) 创建一个 Number 对象 1 与 new Number(1) 的区别是什么?基本对象在stack里,new出来的对象在heap里。
  2. String var s = new String(‘hello’) 创建一个 String 对象 ‘hello’ 与 new String(‘hello’) 的区别是什么?基本对象在stack里,new出来的对象在heap里。
  3. Boolean var b = new Boolean(true) 创建一个 Boolean 对象 true 与 new Boolean(true) 的区别是什么?基本对象在stack里,new出来的对象在heap里。
  4. Object var o1 = {} var o2 = new Object() o1 和 o2 没区别

公用的属性藏在哪

所有对象都有 toString 和 valueOf 属性,那么我们是否有必要给每个对象一个 toString 和 valueOf 呢?

明显不需要。

JS 的做法是把 toString 和 valueOf 放在一个对象里(暂且叫做公用属性组成的对象)

然后让每一个对象的 __proto__ 存储这个「公用属性组成的对象」的地址。

重要公式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var 对象 = new 函数()
对象.__proto__ === 对象的构造函数.prototype

// 推论
var number = new Number()
number.__proto__ = Number.prototype
Number.__proto__ = Function.prototype // 因为 Number 是 Function 的实例

var object = new Object()
object.__proto__ = Object.prototype
Object.__proto__ = Function.prototype // 因为 Object 是 Function 的实例

var function = new Function()
function.__proto__ = Function.prototype
Function.__proto__ == Function.prototye // 因为 Function 是 Function 的实例!

proto.jpg

其他参考

js深入理解构造函数和原型对象 prototype.png

12

eririgasuki

17 posts
© 2020 eririgasuki
Powered by Hexo
|
Theme — NexT.Muse v5.1.4