谈一谈CSS选择器择优及BEM

很久没瞎扯了,看着今天下雨也没地方去,闲下来撸一篇吧。

关于CSS,其实只要不用奇葩表达式、书写不会太离谱,对页面性能影响是相对较小,毕竟现代浏览器的渲染能力还是可以的。但除了性能问题,CSS的书写应该注意后期维护和拓展能力。

在项目开发中,对于同样一个视图,不同开发者的代码实现及写法可能会不一样,对问题的处理方式也有可能不一致,所以在同一个项目组里面,如果没有一个大致相同的规范来限定CSS的书写方式,后期维护五花八门的CSS将变得很费劲。

今天要扯的就是CSS选择器。选择器是CSS学习中的一个重要部分,可以说掌握了选择器,CSS最难的部分算是搞定了,剩下的就都是些属性了,记一记就好了。

先介绍一下主要CSS选择器(按性能从高到低)

ID 选择器,例如#fleming

传说中性能最好的选择器,没有之一。其实在现代浏览器中,ID选择器的性能与类选择器的性能相差无几。ID选择器在选择器名称前带#,示例:

HTML:

<div id="fleming">弗斯基</div>

CSS:

#fleming{
    font-weight:bold;
}

类选择器,例如.fleming

类选择器是CSS中最常用的选择器,可以选中class相同的所有元素。与ID选择器有所不同,ID选择器是选中某个ID的元素,而类选择器是选中class相同的所有元素。例如:

HTML

<div class="fleming">弗斯基</div>
<p class="fleming">厨子</p>
<span class="fleming">有点胖</span>

CSS

.fleming{
    color: red;
}

元素选择器,例如div

元素选择器通常将是某个 HTML 元素,比如 p、h1、em、a,甚至可以是 html 本身当做选择器名称。

HTML

<div>弗斯基</div>
<p>厨子</p>
<span>有点胖</span>

CSS

div, p, span{
    color: red;
}

兄弟选择器,例如 h2 + p

如果需要选择紧接在另一个元素后的元素,而且二者有相同的父元素,可以使用相邻兄弟选择器(Adjacent sibling selector)。例如,如果要增加紧接在 h1 元素后出现的段落的上边距,可以这样写:

HTML

<h1>弗斯基</h1>
<p>老司机</p>

CSS

h1 + p{
    margin: 10px;
}

子选择器,例如li > ul

如果想只选择某个元素的子元素,请使用子元素选择器(Child selector)。 例如,如果希望选择只作为 h1 元素子元素的 strong 元素,可以这样写:

HTML

<h1>This is <strong>very</strong> <strong>very</strong> important.</h1>

CSS

h1 > strong {
    color:red;
}

后代选择器,例如div span

与子选择器有些区别,后代选择器可以选中包括子元素及子元素的子元素。 比子元素选择器范围更广。例如希望对 div 元素中的 <span>元素应用样式,可以这样写:

HTML

<div>
    This is <span>apple</span>
    <p>This is <span>orange</p></p>
</div>

CSS

div p{
    color: red;
}

通用选择器,例如 *

通用选择器可能是所有选择器中最强大的,却使用最少的。通用选择器的作用就像是通配符,它匹配所有可用元素。通用选择器由一个星号表示。通用选择器一般用来对象页面上所有元素应用样式。

HTML

<div>Fleming</div>
<p>Test</p>
<ul>
    <li>厨子</li>
</ul>

CSS

*{
    color: red;
}

属性选择器,例如 type = “text”

可以选中所有包含某种属性的元素。例如:

HTML

<input type="text">

CSS

[type="text"]{
    color: red;
}

伪类/伪元素选择器,例如 a:hover

可为一些元素添加不同状态的样式,可以使用伪类选择器。例如给a元素添加鼠标hover样式。

HTML

<a href="https://chenfengming.cn">弗斯基</a>

CSS

a:hover{
    color: red;
}

除此之外,选择器可以组合使用,例如: .info p > a,但需要注意的一点是,浏览器解析选择器是从右到左的,复杂的选择器组合会耗费较多的时间去解析,对页面整体性能会有影响。例如选择器 .info p > a,浏览器查找到所有a元素之后还需要继续匹配父级元素是p 的a元素,查找完成之后再继续匹配属于class是.info的元素子级元素。

总的来说,CSS选择器的使用不仅仅要考虑到选择器的性能,需要结合当前项目特点,选择合适的选择器,兼顾页面性能和后期的维护和拓展。例如,不能因为ID选择器的高性能就觉得整页都是用ID选择器,那样CSS维护会变得非常困难,不利于组件化开发。适当的组合使用选择器是允许的,但不要过度嵌套选择器。

关于CSS BEM

上面有提到选择器的书写对页面CSS渲染性能有一定的影响,那么如何更加规范、合理的书写选择器能既能方便理解用意又能便于后期维护和拓展呢?

BEM is a highly useful, powerful, and simple naming convention that makes your front-end code easier to read and understand, easier to work with, easier to scale, more robust and explicit, and a lot more strict.

先介绍一下BEM, Block(块) Element(元素) Modifier(修饰器)的简称。使用BEM规范来命名CSS,组织HTML中选择器的结构,利于CSS代码的维护,使得代码结构更清晰,缺点主要是选择器名会比较丑陋。此外,BEM规范的遵循在一定程度上能帮助优化CSS选择器结构,从而提升页面性能。

在BEM规范中,以双下划线 __来作为块和元素的间隔,以单下划线_来作为块和修饰器或元素和修饰器的间隔,以中划线 – 来作为块|元素|修饰器名称中多个单词的间隔。保证各个部分只有一级 B__E_M ,修饰器需要和对应的块或元素一起使用,避免单独使用。

举个例子

HTML

<form class="form form--theme-xmas form--simple">
    <input class="form__input" type="text" />
    <input class="form__submit form__submit--disabled" type="submit" />
</form>

CSS

.form { }
.form--theme-xmas { } //某种状态的form
.form--simple { } //某种状态的form
.form__input { } // Form子元素input
.form__submit { } // Form子元素summit
.form__submit--disabled { } // Form子元素submit带disabled状态

LESS(CSS预处理器)

.form {
    &--theme-xmas { } //某种状态的form
    &--simple { } //某种状态的form
    &__input { } // Form子元素input
    &__submit {
       &--disabled { } // Form子元素submit带disabled状态
    } // Form子元素summit
}

这样书写的好处是编译出来的CSS只有一层,不会有多个选择器嵌套的情况,浏览器执行起来效率会高些,另外,因为嵌套层数少,样式优先级比较低,后期做响应式或其它样式覆盖会比较容易。

具体使用需要结合项目特点。对于BEM中CSS选择器的命名,不宜过长,适当、能表达意思即可。关于BEM想了解更多的童鞋参见:http://getbem.com/

另外,关于静态资源与页面性能。在现代浏览器中,资源是并发加载的(同域名下约6个左右),当资源过多时,资源总数大于并发数,后续的资源加载会受限于浏览器并发数。许多网站为静态资源提供多个域名调用方式从而提升浏览器静态资源加载效率。但即便如此,浏览器仍然有可能出现资源阻塞的情况。例如,CSS资源就会阻塞浏览器渲染进程,也就是说在这之前,浏览器不会渲染任何已处理资源,直到CSSOM(CSS Object Model)构建完毕。当CSS资源阻塞时,DOM节点的渲染以及JavaScript处理会延时。可见低效率的CSS会影响到整个页面的性能。

当浏览器遇到 script 标签时,DOM 构建将暂停,直至脚本完成执行。 所以,一般在页面引用CSS会放置在页面header部分,JavaScript会被放置带页面末尾,这样做的目的是在确保页面样式不会受到JavaScript的影响下优先渲染。开始扯远了,今天就先说到这了。

种子站那么优秀?论客户端用户唯一性识别

曾经某大师给我展示了这么一个使用场景。

某片子网站有一套免登陆识别客户端用户的机制,用户不需要注册、登录,直接访问链接即可在网站上实现一些会员服务。按正常思维,需要实现会员制,必须要求用户在网站上登录留下凭证,这样服务器才能把对应会员正确的服务分发,黄金会员白金会员钻石会员,各种会员,当然,如果用户充值,这些帐会自动记录在当前客户端名下。看着似乎有点玄乎,当时就被唬住了。

按照常规的思路,想要获取一个能当做用户唯一标识的东西,从浏览器端来看,要么MAC地址之类的?类似于这些相对固定而唯一的号,然鹅,浏览器除了提供UA(User agent)之外,其它信息一概无法获取,而UA根本就无法用来识别浏览器端的唯一性,因为相同设备相同浏览器的UA是一致的,美梦似乎要破灭了。然而我那锲而不舍的精神一直支撑着我,让我不断向前~探索。

小片子网站能做,应该还是有对应的技术可以实现的,后面回来后翻了下资料,发现还真有这种操作,不用注册不用登陆就可以区分浏览器端用户!感觉这个需求瞬间就靠谱了,PM不能随便打,还好这么冷静。翻开了我的小本子,在里面狠狠地记了一笔。

这么看来是要能造一个类UUID(Universally Unique IDentifier)的东西出来标识一下用户才行,具体怎么造,这里就不详细说了,GitHub上也有很多基友写好的码子可以下,有时间了解原理的小伙伴也可以科研一下。至于造UUID这种事情,肯定是要交给我们服务端来干了。那么问题来了,服务端辛苦造出来的UUID该怎么分发给浏览器端并在客户端本地存起来用来标识唯一用户呢?服务器通过页面?cookie?URL?各种本地存储?似乎都不行,清理一下浏览器、关下机又是空白一片,充值的会员还没来得及看片就没掉了,这是不允许的。

现有的浏览器没有现成接口标识当前浏览器用户的唯一性,认真搜刮了一番,似乎有好几种聊的比较多的方法,这里就选个看上去相对靠谱一点的方法聊一聊:

HTML5 Canvas指纹、AudioContext指纹

先说说第一种Canvas指纹方法,基于Canvas标签绘制特定内容的图片,使用canvas.toDataURL()方法获得图片内容的base64编码(对于PNG格式的图片,以块(chunk)划分,最后一块是32位CRC校验)作为唯一性标识,JavaScript示例:

function getUUID() {
    var canvas = document.getElementById('myCanvas');
    var ctx = canvas.getContext('2d');
    ctx.fillStyle = '#FF0000';
    ctx.fillRect(0,0,8,10);

    var b64 = canvas.toDataURL().replace('data:image/png;base64,', '');
    var bin = window.atob(b64);
    var crc = bin2hex(bin.slice(-16,-12));
    
    return src;
}

alert(getUUID());

这种方法是基于相同的HTML5 Canvas元素的绘制操作在不同操作系统、不同浏览器上,产生图片内容不完全相同原理实现的。在图片处理上,不同浏览器使用了不同的图形处理引擎、不同的默认压缩级别等。从像素级别来看,操作系统各自使用了不同的设置和算法来进行像素渲染。即使相同的绘图操作,不同的浏览器端产生的图片数据的CRC检验也不相同。这个特性可以用来区分大部分的浏览器端,但仍然没办法做到完全区分。碰撞的可能性比较大。

而从Canvas被浏览器兼容的情况来看,几乎已被所有主流浏览器都支持Canvas,覆盖了大部分的PC、平板、智能手机端用户。早期有不少人选择这种方式实现浏览器端唯一性标识。

另一种是AudioContext指纹,原理与canvas类似。本地计算机或浏览器硬件或软件的细微差别,导致音频信号的处理上的差异,相同器上的同款浏览器产生相同的音频输出,不同机器或不同浏览器产生的音频输出会存在差异。

AudioContext指纹有两种方式实现:

方法一: 生成音频信息流(三角波),对其进行FFT变换,计算SHA值作为指纹,音频输出到音频设备之前进行清除,用户毫无察觉。

第二种方法是:生成音频信息流(正弦波),进行动态压缩处理,计算MD5值。

 

这种方法出来的指纹仍然不是可靠的,与Canvas指纹类似,有很高的碰撞概率。似乎离实际运用还有一段距离。

为了提高这两种指纹的可靠性,我们需要额外附加一些其它信息,降低碰撞概率。例如增加一些硬件指纹作为补充,有兴趣的小伙伴可以详细阅读一下这个文档: Hardware fingerprint using HTML5。主要的硬件模块包括:GPU’s clock frequency、Camera、Speakers/Microphone、Motion sensors、GPS、Battery等等。

即便如此,虽然碰撞的概率降低了,但仍然无法达到一个比较理想的状态。那需要怎样再做优化呢?没错,将上述所有的指纹综合利用,进行分析、计算哈希值作为综合指纹,可以大大降低碰撞率,极大提高客户端唯一性识别的准确性。

似乎事情得到解决了,然而细想会发现,这种唯一性验证在跨浏览器的时候就无法使用了,同一个用户在同一台机器上使用不同的浏览器所产生的指纹是不一样的。在Chrome充值的会员在Firefox上不能用了,好像也不太科学。又是一顿好找,还真有大佬在研究这块跨浏览器指纹,感兴趣的童鞋可以研读膜拜一下这篇: ( cross-) Browser fingerprint via OS and Hardware level features

其原理是根据浏览器、操作系统与底层硬件之间的交互进而分析出来的指纹,同一机器上不同浏览器产生的这种指纹是一致的。

总结一下

如果你上面的内容似乎看不太懂,可以直接跳到这里来。

客户端用户唯一性识别可靠性不够高,在利用综合指纹识别时虽然碰撞概率降低,但仍然需要解决一些例如跨浏览器身份识别这种问题,需要深挖。运用场景比较广,可用于免登录、客户端追踪(例如根据用户搜索内容,广告精确定推等等)一些精度要求不太高的地方。对于免登录场景运用,可结合后期用户的一些信息做进一步区分处理,但仍然达不到注册的身份验证方法的精度。

对于客户端用户唯一性识别你怎么看呢?

关于WEB前端开发

那么,到底什么是WEB前端开发呢?

从字面上理解,WEB前端开发的工作与网站的某些“前端”息息相关,可以认为这个“前端”是整个网站的最前端,是最接近用户的地方。那所谓的网站“前端”又包括哪些部分呢?

大家都知道,打开网站后首先展现在用户面前的是整个网站的视图部分,包括布局、样式、动画、多媒体元素等,而当用户开始浏览页面点击鼠标或者输入内容时,页面会针对用户的行为做出对应的反馈(例如显示/隐藏、内容变化等页面交互),这些综合起来就是网站的“前端”部分了。

上面我们提到的网站那些“前端”部分的东西,大多都在用户使用的浏览器端执行,所以WEB前端开发也称之为客户端/浏览器端开发。WEB前端主要开发语言为HTML,CSS和JavaScript。随着技术变革,衍生出了一系列新的框架和解决方案,使得WEB前端开发逐渐丰富起来。

早些年,WEB前端开发的工作几乎被“美工”承包了,在很多人的印象中,WEB前端开发仍然停留在“切图仔”时代,认为调调样式切一切图就可以放学回家了。然而并不是,从2007年到现在,经过大概11年的发展,前端开发变得越来越规范,很多后端开发的模式及思想已经渗透到了WEB前端开发,PWA模式(Progressive Web App)也让前端的疆域也从页面扩展到各种APP,包括跨平台桌面运用。WEB前端开发的工作已不仅仅是简单写写样式切切图了,加之WEB前端与用户体验息息相关,前端开发变得越来越重要。

随着时代变迁,越来越多的移动设备拥有了互联网访问能力,各种各样的平台、屏幕和分辨率,这就需要我们在开发之前进行更好的规划,网站的前端能适时自动适应这些平台设备,在开发成本与用户体验之间寻求一个合适的契合点。响应式布局应运而生,与传统多平台开发相比,在获得较好的用户体验同时节省了大量的人力物力,所有设备一套代码,后期维护也变得容易。

HTML, CSS, & JavaScript:

WEB前端开发者使用Web技术(即HTML,CSS,DOM和JavaScript)构建和开发网站或WEB应用程序前端,通常需要在各种Web平台环境(包括浏览器)上运行或一些非Web平台环境使用(例如React Native),不仅仅是WEB浏览器端那么单一。

我们通过学习开发HTML,CSS和JavaScript进入WEB前端开发领域,而这些HTML,CSS和JavaScript可以在Web浏览器中运行,也可以在其它环境下运行,例如无头浏览器WebView或一些Native环境中运行。 下面介绍一下这四个运行场景:

Web 浏览器(WEB BROWSERS) – 通常情况

Web浏览器是用于检索、呈现互联网上的信息的软件。 一般在台式机或笔记本电脑,平板电脑或手机上可以看到它的身影,但近年来,越来越多的智能设备也包含了WEB浏览器,例如智能电视、汽车甚至是冰箱。WEB浏览器是一种比较常见的访问网站的工具。

常见的Web浏览器(按照最常用的顺序显示):

无头浏览器(HEADLESS BROWSERS)

无头浏览器是指没有用户图形界面的浏览器,可以通过编程方式从命令行界面控制的网页浏览器,通常用于网页自动化(例如,功能测试,抓取,单元测试等)。可以理解为,无头浏览器就是一种通过命令行运行的可以检索和遍历网页的浏览器。

最常见的无头浏览器是:

Webviews

Webviews通常被包含在原生的系统或应用程序中,用来运行web页面。可以理解为,Webviews就像iframe或者是Web浏览器里面的一个选项卡,被嵌入在原生系统或应用程序的设备中运行(例如,iOS,android,windows)。

Webview最常见的几种解决方案:

  • Cordova (typically for native phone/tablet apps)
  • NW.js (typically used for desktop apps)
  • Electron (typically used for desktop apps)

注:htmlpage.cn项目中的网页构建器桌面版就是用Electron包的。

Native from Web Tech

最后,WEB前端开发者可以使用从前端开发中学到的知识来为其它跨平台及非浏览器设备开发应用程序。 例如,创建Native应用程序(native applications)。

示例:

介绍完这些,你应该对WEB前端开发有大概的了解了吧?

 

CSS响应式布局之Grid实战

在这篇文章中,我将教你如何使用 CSS Grid(网格) 布局来创建一个超酷的图像网格,它会根据屏幕的宽度改变列的数量,以实现响应式布局。

而这篇文章中最漂亮的部分是:添加一行 CSS 代码即可实现响应式布局。

这意味着我们不必通过丑陋的类名(即 col-sm-4col-md-8)来混淆 HTML ,或者为每一个屏幕尺寸创建媒体查询。

现在就让让我们开始吧!

设置

以下是我们的初始网格的外观:

HTML part

<div class="container">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
</div>

CSS part

.container {
    display: grid;
    grid-template-columns: 100px 100px 100px;
    grid-template-rows: 50px 50px;
}

注意:这个例子也有一些基本的样式,比如容器宽度,网格间隔,背景颜色什么的,我不会在这里介绍,因为它与 CSS Grid 没有任何关系。

如果这段代码让你感到困惑,我建议你阅读 理解CSS Grid布局 这篇文章,在那里我解释了 Grid 布局模块的基础知识。

让我们开始将 列 实现响应式布局。

使用等分(fr)单位实现基本的响应式

CSS Grid 带来了一个全新的值,称为等分单位,即 fr 。它允许你将容器可用空间分成你想要的多个等分空间。

让我们将每个列更改为一个等分单位宽度。

CSS示例

.container {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: 50px 50px;
}

这里发生的事情是,将整个网格的宽度分成三等分,每一列都占据一个 fr 单位。结果是:

如果我们将 grid-template-columns 的值更改为 1fr 2fr 1fr,那么第 2 列现在将是另外 2 列的 2 倍。总宽度现在是 4 等分,第 2 列占据了 2 等分,而其他 2 列则各占 1 等分。看起来类似这样:

换句话说,等分单位值使你可以非常容易地改变列的宽度。

更加高级的响应式

但是,上面的例子并没有给我们想要的响应式,因为这个网格总是包含 3 列。我们希望我们的网格根据容器的宽度来改变列的数量。要做到这一点,你必须学习三个新的概念。

repeat()

我们将从 repeat() 函数开始。 这是指定列和行更强大的方法。 让我们把原来的网格改成使用 repeat() :

.container {
    display: grid;
    grid-template-columns: repeat(3, 100px);
    grid-template-rows: repeat(2, 50px);
}

换句话说,repeat(3, 100px) 与 100px 100px 100px 相同。 第一个参数指定了你想要的列数或行数,第二个参数指定了它们的宽度,所以上面的代码将为我们创建和第一个一样的布局。

auto-fit (自适应)

然后是自适应。让我们跳过固定数量的列,而是用 auto-fit 取代 3 。

.container {
    display: grid;
    grid-gap: 5px;
    grid-template-columns: repeat(auto-fit, 100px);
    grid-template-rows: repeat(2, 100px);
}

效果如下图:

你会看到,现在这个网格已经可以通过容器的宽度来改变列的数量。

它只是试图尽可能多地将 100px 宽的列排列在容器中。

但是,如果我们将所有列硬编码为 100px ,我们永远得不到我们想要的灵活性,因为它们很少会加起来正好等于容器的宽度。正如你在上面的 gif 图中所看到的,网格通常会在右侧留下空白区域。

minmax()

为了解决这个问题,我们需要的最后一方法是 minmax()。我们只需用 minmax(100px, 1fr) 替换 100px 即可。这是最终的CSS。

.container {
    display: grid;
    grid-gap: 5px;
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
    grid-template-rows: repeat(2, 100px);
}

注意,所有的响应都发生在一行 CSS 中。

这会达到以下效果:

正如你们所见,这样很完美。minmax() 函数定义大于或等于 min 且小于或等于 max 的大小范围。

所以现在列的宽度至少 100px 。但是,如果有更多的可用空间,网格将简单地分配给每个列,因为列的值变成了一个等分单位 1fr ,而不是 100px 。

添加图片

现在最后一步是添加图片。 这与 CSS Grid 没有任何关系,但是我们还是要来看看代码。

我们将在每个网格项内添加一个 img 标签。

<div><img src="img/forest.jpg"/></div>

为了使图像适合该网格项,我们将它设置为与网格项一样宽和高,然后使用 object-fit: cover;。这将使图片覆盖整个容器,如果需要的话,浏览器会裁剪该图片。

.container > div > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

效果如下:

就是这么简单!你已经了解了 CSS Grid 中最复杂的概念之一,所以后面请自己动手吧。

浏览器支持

在我们结束之前,我还需要提及浏览器的支持。在写这篇文章的时候,占全球 77% 的网站流量的浏览器支持CSS Grid,而且正在攀升。

CSS Grid 使用频率会越来越高。很快会得到突破,并将成为前端开发人员的必备技能。就像过去几年在 CSS Flexbox 所发生的一样。

推荐阅读:原文 How to make your HTML responsive by adding a single line of CSS

 

JavaScript函数的Throttle与Debounce

你可能知道,JavaScript遵循事件驱动的编程范例。这意味着一些行为可以激活一些响应,并且这些响应仅在发生特定的行为时才被激活。我们称这些行为events(事件),和响应callbacks(回调)。连续的事件流被称为event stream(事件流)。

这些行为发生的速度不是我们能手动控制的。但是我们可以控制何时和如何激活正确的响应。有一些技术为我们提供精确的控制。

  • Throttle
  • Debounce
  • Immediate

Throttle

在现代浏览器中,帧速率为60fps是流畅性能的目标,给定我们16.7ms的时间预算用于响应一些事件所有需要的更新。这样可以推断,如果每秒发生n个事件并且回调执行,需要t秒的时间,为了流畅运行,

1 / n >= t

如果t以毫秒为单位,

1000 / n >= t

如果你曾经使用mousemove事件,你会知道产生mousemove事件的数量每秒可以超过60次。如果我们的回调需要超过16.7ms,那就开始凌乱了。

var then = 0;
 
function log() {
  var now = Date.now();
  if (1000 / (now - then) > 60) {
    console.log('It\'s over 9000!!!');
  }
  then = now;
}
 
window.onmousemove = log;

实现

Throttle 允许我们限制我们激活响应的数量。我们可以限制每秒回调的数量。反过来,也就是说在激活下一个回调之前要等待多少时间;

var delta = 1000;
var then = 0;
 
function log() {
  console.log('foo');
}
 
function throttledLog() {
  var now = Date.now();
  if (now - then >= delta) {
    log();
 
    then = now;
  }
};
 
window.onmousemove = throttledLog;

我们可以用 fps替换delta,并推断出不同的代码。

var fps = 60;
...
function throttledLog() {
  var now = Date.now();
  if (1000 / (now - then) < = fps) {
    log();
 
    then = now;
  }
};
 
window.onmousemove = throttledLog;

我们也可以通过使用setTimeout来实现相同的结果。 但是,不是检查时间差,而是检查状态变化。

第一次,我们可以安全地激活回调。一旦完成,只有在等待 delta 时间之后才能再次激活回调。

var delta = 1000;
var safe = true;
 
function log() {
  console.log('foo');
}
 
function throttledLog() {
  if (safe) {
    log();
 
    safe = false;
    setTimeout(function() {
      safe = true;
    }, delta);
  }
};
 
window.onmousemove = throttledLog;

Debounce

这个术语-去抖动 来自电子学的领域,手动开关输入的信号被发送到数字电路中。在电子学中,当你按一个物理按钮一次,数字电路可能读到多个按压,因为按钮的物理属性(金属触点,弹簧,磨损件等)。

去抖动意味着采集到的所有这些波动的信号,并把它们当作一个。

例子

一个简单的例子已经存在于JS中:keydown vs keyup。假设您正在处理一个项目,并且需要输入内容。但是你想要每次敲击键盘得到一个字符。输入时,如果长按一个键,keydown事件将连续被触发,但是 keyup 事件只有在按键被释放时才会触发。

window.onkeyup = function() {
  console.log('onkeyup');
}
 
window.onkeydown = function() {
  console.log('onkeydown');
}

这种行为上的差异对于确定输入是否已完成是有用的。在示例场景中,它是你将使用的keyup事件。在某种程度上,我们可以说keydown 是原始输入,keyup 是去抖动输入。

实现

当事件发生时,我们不会立即激活回调。相反,我们等待一定的时间并检查相同的事件是否再次触发。如果是,我们重置定时器,并再次等待。如果在等待期间没有发生相同的事件,我们就立即激活回调。

var delta = 1000;
var timeoutID = null;
 
function log() {
  console.log('foo');
}
 
function debouncedLog() {
  clearTimeout(timeoutID);  // reset timer
  timeoutID = setTimeout(function() {
    // wait for some time
    // and check if event happens again
    log();
  }, delta);
};
 
window.onkeydown = debouncedLog;

Immediate

Immediate是Debounce的精确版本。比起 Debounce 的 等待后续事件触发,然后再激活回调,Immediate 是 立即激活回调,然后等待后续事件在一定时间内触发。

实现

就像Throttle的情况一样,我们需要一个状态变量来检查是否应该激活我们的回调。我们在Debounce不需要一个,因为timeoutID隐式管理这部分。

var delta = 1000;
var timeoutID = null;
var safe = true;
 
function log() {
  console.log('foo');
}
 
function immediatedLog() {
  if (safe) {
    log();
    safe = false;
  }
 
  clearTimeout(timeoutID);
  timeoutID = setTimeout(function() {
    safe = true;
  }, delta);
};
 
window.onkeydown = immediatedLog;

理解CSS Grid布局

Grid 布局是网站设计的基础,CSS Grid 是创建网格布局最强大和最简单的工具。

CSS Grid 今年也获得了主流浏览器(Safari,Chrome,Firefox,Edge)的原生支持,所以我相信所有的前端开发人员都必须在不久的将来学习这项技术。

在本文中,我将尽可能快速地介绍CSS网格的基本知识。我会把你不应该关心的一切都忽略掉了,只是为了让你了解最基础的知识。

你的第一个 Grid 布局

CSS Grid 布局由两个核心组成部分是 wrapper(父元素)和 items(子元素)。 wrapper 是实际的 grid(网格),items 是 grid(网格) 内的内容。

下面是一个 wrapper 元素,内部包含6个 items :

<div class="wrapper">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
</div>

要把 wrapper 元素变成一个 grid(网格),只要简单地把其 display 属性设置为 grid 即可:

.wrapper {
    display: grid;
}

但是,这还没有做任何事情,因为我们没有定义我们希望的 grid(网格) 是怎样的。它会简单地将6个 div 堆叠在一起。

我已经添加了一些样式,但是这与 CSS Grid 没有任何关系。

Columns(列) 和 rows(行)

为了使其成为二维的网格容器,我们需要定义列和行。让我们创建3列和2行。我们将使用grid-template-rowgrid-template-column属性。

.wrapper {
    display: grid;
    grid-template-columns: 100px 100px 100px;
    grid-template-rows: 50px 50px;
}

正如你所看到的,我们为 grid-template-columns 写入了 3 个值,这样我们就会得到 3 列。 我们想要得到 2 行,因此我们为 grid-template-rows 指定了2个值。

这些值决定了我们希望我们的列有多宽( 100px ),以及我们希望行数是多高( 50px )。 结果如下:

为了确保你能正确理解这些值与网格外观之间的关系,请看一下这个例子。

.wrapper {
    display: grid;
    grid-template-columns: 200px 50px 100px;
    grid-template-rows: 100px 30px;
}

请尝试理解上面的代码,思考一下以上代码会产生怎样的布局。

这是上面代码的布局的结果:

非常好理解,使用起来也非常简单是不是?下面我们来加大一点难度。

放置 items(子元素)

接下来你需要学习的是如何在 grid(网格) 上放置 items(子元素) 。特别注意,这里才是体现 Grid 布局超能力的地方,因为它使得创建布局变得非常简单。

我们使用与之前相同的 HTML 标记,为了帮助我们更好的理解,我们在每个 items(子元素) 加上了单独的 class :

HTML part

<div class="wrapper">
  <div class="item1">1</div>
  <div class="item2">2</div>
  <div class="item3">3</div>
  <div class="item4">4</div>
  <div class="item5">5</div>
  <div class="item6">6</div>
</div>

现在,我们来创建一个 3×3 的 grid(网格):

.wrapper {
    display: grid;
    grid-template-columns: 100px 100px 100px;
    grid-template-rows: 100px 100px 100px;
}

将得到以下布局:

不知道你发现没有,我们只在页面上看到 3×2 的 grid(网格),而我们定义的是 3×3 的 grid(网格)。这是因为我们只有 6 个 items(子元素) 来填满这个网格。如果我们再加3个 items(子元素),那么最后一行也会被填满。

要定位和调整 items(子元素) 大小,我们将使用 grid-column 和 grid-row 属性来设置:

.item1 {
    grid-column-start: 1;
    grid-column-end: 4;
}

我们在这里要做的是,我们希望 item1 占据从第一条网格线开始,到第四条网格线结束。换句话说,它将独立占据整行。 以下是在屏幕上显示的内容:

如果你不明白我们设置的只有 3 列,为什么有4条网格线呢?看看下面这个图像,我画了黑色的列网格线:

请注意,我们现在正在使用网格中的所有行。当我们把第一个 items(子元素) 占据整个第一行时,它把剩下的 items(子元素) 都推到了下一行。

最后,给你一个更简单的缩写方法来编写上面的语法:

.item1 {
    grid-column: 1 / 4;
}

为了确保你已经正确理解了这个概念,我们重新排列其他的 items(子元素) 。

.item1 {
    grid-column-start: 1;
    grid-column-end: 3;
}
.item3 {
    grid-row-start: 2;
    grid-row-end: 4;
}
.item4 {
    grid-column-start: 2;
    grid-column-end: 4;
}

你可以尝试在你的脑子里过一边上面代码的布局效果,应该不会很难。

以下是页面上的布局效果:

Grid 布局就是这么简单,当然这里展示的是最简单的 Grid 布局概念,但是 Grid 布局系统中还有更多强大灵活的特性。

推荐阅读:CSS Grid layout 进阶篇 CSS响应式布局之Grid实战

HTML5新增&废除的元素

新增块级元素

section元素

section元素定义文档或应用程序中的一个区段,比如章节、页眉、页脚或文档中的其他部分。它可以与h1,h2,h3,h4,h5,h6元素结合起来使用,标示文档结构。

HTML5中代码示例: <section>…</section>

HTML4中代码示例:<div>…</div>

article元素

article元素表示文档中的一块独立的内容,譬如博客中的一篇文章或报纸中的一篇文章。

HTML5中代码示例 :<article>…</article>

HTML4中代码示例 :<div class=”article”>…</div>

header元素

header元素表示页面中一个内容区块或整个页面的标题。

HTML5中代码: <header>…</header>

HTML4中代码示例 :<div>…</div>

nav元素

nav元素表示导航链接的部分。

HTML5中代码示例 : <nav>…</nav>

HTML4中代码示例 :<ul>…</ul>

footer元素

footer元素表示整个页面或页面中一个内容区块的脚注。一般来说,它会包含创作者的姓名、文档的创作日期以及创建者联系信息。

HTML5中代码示例 :<footer>…</footer>

HTML4中代码示例 :<div>…</div>

新增块级语义元素

aside元素

aside元素表示article元素的内容之外的与article元素的内容相关的有关内容。

HTML5中代码示例 :<aside>…</aside>

HTML4中代码示例 :<div>…</div>

figure元素

figure元素表示一段独立的流内容,一般表示文档主体流内容中的一个独立单元。使用 <figcaption> 元素为figure元素组添加标题。

HTML5中代码示例:

<figure>
<figcaption>PRC</figcaption>
<p>The People's Republic of China was born in 1949...</p>
</figure>

HTML4中代码示例:

<dl>
<h1>PRC</h1>
<p>The People's Republic of China was born in 1949...</p>
</dl>

dialog元素

dialog标签定义对话,比如交谈。 注意: 对话中的每个句子都必须属于 <dt> 标签所定义的部分。

HTML5中代码示例:

<dialog>
<dt>老师</dt>
<dd>2+2 等于?</dd>
<dt>学生</dt>
<dd>4</dd>
<dt>老师</dt>
<dd>答对了!</dd>
</dialog>

新增行内语义元素

mark元素

mark元素主要用来在视觉上向用户呈现那些需要突出显示或高亮显示的文字。mark元素的一个比较典型的应用就是在搜索结果中向用户高亮显示搜索关键词。

HTML5中代码示例 :<mark>…</mark>

HTML4中代码示例 :<span>…</span>

time元素

time元素表示日期或时间,也可以同时表示两者。

HTML5中代码示例 : <time>…</time>

HTML4中代码示例 :<span>…</span>

meter元素

meter元素表示度量衡。仅用于已知最大和最小值的度量。必须定义度量的范围,既可以在元素的文本中,也可以在 min/max 属性中定义。

HTML5中代码示例 : <meter>…</meter>

progress元素

progress元素表示运行中的进程。可以使用 progress元素来显示 JavaScript 中耗费时间的函数的进程。

HTML5中代码示例 :<progress>…</progress>

新增多媒体与交互性元素

video元素 & audio元素

video用来插入视频,audio用来插入声音,当然,用下面这个:

<video src="XX.wmv">您的浏览器不支持video标签</video>

如果浏览器不支持,则显示标签内的文字

details元素

details元素表示用户要求得到并且可以得到的细节信息。它可以与summary元素配合使用。summary元素提供标题或图例。标题是可见的,用户点击标题时,会显示出details。summary元素应该是details元素的第一个子元素。目前只有 Chrome 支持 <details> 标签。

HTML5中代码示例:

<details>
<summary>HTML5</summary>
<p>All pages and graphics on this web site are the property of chenfengming.cn.<p>
</details>
<!--目前只有 Chrome 支持 <details> 标签—>

datagrid元素

datagrid元素表示可选数据的列表,与input元素配合使用,可以制作出输入值的下拉列表。

HTML5中代码示例 :<datagrid>…</datagrid>

menu元素

menu元素表示菜单列表。当希望列出表单控件时使用该标签。

HTML5中代码示例:

<menu>
<li><input type="checkbox" />Red</li>
<li><input type="checkbox"/>blue</li>
</menu>

注意:HTML4中 menu元素不被推荐使用。

command元素

command元素表示命令按钮,比如单选按钮、复选框或按钮。

HTML5中代码示例:

<menu>
<command onclick="alert('Hello World')">Click Me!</command>
</menu>

 

新增input类型

email——email类型用于应该包含 e-mail 地址的输入域。

url——url 类型用于应该包含 URL 地址的输入域。

number——number 类型用于应该包含数值的输入域。

range——range 类型用于应该包含一定范围内数字值的输入域。

Date Pickers(日期选择器)

search——search 类型用于搜索域,比如站点搜索或 Google 搜索。search 域显示为常规的文本域。

多个可供选取日期和时间的新输入类型

date – 选取日、月、年

month – 选取月、年

week – 选取周和年

time – 选取时间(小时和分钟)

datetime – 选取时间、日、月、年(UTC 时间)

datetime-local – 选取时间、日、月、年(本地时间)

废除的元素

•能使用css代替的元素
对于basefont、big、center、font、s、strike、tt、u这些元素,由于他们的功能都是纯粹为画面展示服务的,而在HTML5中提倡把画面展示性功能放在css样式表中统一编辑,所以将这些元素废除,并使用编辑css样式表的方式进行替代。

•不再使用frame框架
对于frameset元素、frame元素与nofranes元素,由于frame框架对页面可存在负面影响,在html5中已不再支持frame框架,只支持iframe框架,或者用服务器方创建的由多个页面组成的复合页面的形式,同时将以上三个元素废除。

•只有部分浏览器支持的元素
对于applet、bgsound、blink、marguee等元素,由于只有部分浏览器支持这些元素,所以在HTML5中被废除。其中applet元素可由embed元素替代,bgsound元素可由audio元素替代,marquee可以由JavaScript编程的方式所替代。

 

 

CSS布局和块级格式化上下文

CSS布局中有一些概念是你一旦理解它那么就会极大的提升你的 CSS 技能的。这篇文章是关于块级格式化上下文的 BFC 。也许你从未听说过这个术语,但是如果你曾经用 CSS 做过布局,那么你也许知道它是什么。理解什么是 BFC ,它为什么会起作用以及如何创建一个有用的 BFC 可以帮助你理解 CSS 布局是怎样工作的。

在这篇文章中,我将通过一些你可能熟悉的例子解释什么是 BFC 。我将向你展示一个新的概念,当你明白什么是 BFC 以及你为什么需要它的时候,它才真正有意义。

什么是 BFC?

最容易明白一个 BFC 表现的是一个浮动的例子。在下面的例子中有一个盒模型,其中包含一张左浮动的图和一些文字。如果我们有大量的文字,它环绕在浮动的图像上,则边框会围绕着整个区域。

HTML part:

<div class="outer">
  <div class="float">I am a floated element.</div>
  I am text inside the outer box.
</div>

CSS part:

.outer {
  border: 5px dotted rgb(214,129,137);
  border-radius: 5px;
  width: 450px;
  padding: 10px;
  margin-bottom: 40px;
}

.float {
  padding: 10px;
  border: 5px solid rgba(214,129,137,.4);
  border-radius: 5px;
  background-color: rgba(233,78,119,.4);
  color: #fff;
  float: left;  
  width: 200px;
  margin: 0 20px 0 0;
}

图1:文本环绕着浮动元素

如果删除了一些文本,那么文本就不足以环绕图像,并且因为图片浮动脱离了文档流,边框就会在图片下面并且上升到文本的高度。

图2:没有足够的文本,边框就不能到达浮动元素所期望的高度

这是因为当我们在浮动一个元素时,文本所在的盒模型仍然是固定的高度,而因浮动元素而缩短的空间是文本的行框。这就是为什么背景和边框会出现在浮动元素的后面。

这里有两种我们通常修复这种问题的方式。一种是使用清除浮动 clearfix hack 1,它是通过在文本和图片下面插入一个元素并且设置清除两侧浮动来起作用的。另一种方式是使用 overflow属性,使用其他的值来代替默认的 visible 。

.outer {
  overflow: auto;
}

图3:使用 overflow:auto 使盒模型中包含浮动

overflow 属性起作用的原因是使用任何一个其他值来代替初始值 visible ,从而创建一个 BFC 。即 BFC 的一个特点就是它包含浮动。

BFC 布局是一个迷你布局

你可以认为 BFC 在网页中是一个迷你布局。一旦一个元素创建的 BFC ,所有东西都包含在里面了。正如我们所看到的,它包含浮动元素使其不再超出盒子底部。同时 BFC 也产生了一些其他有用的行为。

BFC 防止外边距塌陷

理解外边距塌陷是另一个被低估的 CSS 技能。在下一个例子中,有一个灰色背景的 div 。这个 div 中有两个段落。外层 div 有 40px 的下边距;每一个段落也分别有 20px 的上下边距。

.outer {
  background-color: #ccc;
  margin: 0 0 40px 0;
}

p {
  padding: 0;
  margin: 20px 0 20px 0;
  background-color: rgb(233,78,119);
  color: #fff;
}

由于 p 元素的外边距和外层 div 的外边距之间没有任何东西而导致它们折叠,使 p 段落最后会与盒子的顶部和底部平齐。所以在 p 段落的上面和下面我们没有看到任何灰色。

图4:外边距塌陷导致在盒子的顶部和底部看不到任何灰色

如果我们对盒模型应用 BFC ,那么它将包括段落和边距并使之不会塌陷,所以我们将在边距的后面看到灰色的背景。

.outer {
  background-color: #ccc;
  margin: 0 0 40px 0;
  overflow: auto;
}

图5:使用BFC外边距将不会塌陷

BFC 再一次使元素包含在其中,阻止其外边距塌陷或超出盒模型。

BFC 阻止内容环绕浮动元素。

你也会熟悉 BFC 这种行为,就是它如何在使用浮动的多列布局中工作的。如果一个项目创建了 BFC ,那么它将不会环绕任何浮动元素,比如在下面的示例中有这样的标记:

<div class="outer">
  <div class="float">I am a floated element.</div>
  <div class="text">I am text</div>
</div>

带有 float 类的元素开始浮动,然后 div 中的文本会环绕在浮动元素周围。

图6:文本环绕浮动元素

那么可以使用通过对文本使用 BFC 来阻止其环绕行为。

.text {
  overflow: auto;
}

图7:div 包含的文本使用了 BFC 使之停止环绕

这是我们创建多列浮动布局常用的方式。浮动一个元素同时也为另一个元素创建了 BFC ,所以当右边的元素比左边高时,创建的列也不再尝试环绕对方。

还有什么方式可以创建 BFC?

除了使用 overflow 属性以外,其他一些 CSS 属性也可以创建 BFC 。正如我们看到的,浮动一个元素也创建了 BFC ,所以浮动项目将包含里面的任何元素。

其他方式还有使用 position: absolute , position: fixed ,使用 display: inline-block, display: table-cell 及 display: table-caption ,其中 table-cell 以及 table-captions是 HTML 元素的默认属性,所以如果有一个 table 数据,那么它的每个格子都将创建 BFC 。 column-span: all 多被使用在多列布局中。 Flex 和 Grid 项目也会创建类似的 BFC ,它们分别被描述为 Flex 格式化上下文和 Grid 格式化上下文,这分别反映了不同的布局类型。 BFC 表示块级布局, FFC 代表 Flex 布局。在实际项目中结果是一样的,都是包含浮动并且外边距不会发生塌陷。

创建 BFC 新方式

使用 overflow 属性或其他方式创建 BFC 有两个问题。第一,这些方法对于它们真正的用途会产生副作用。使用 overflow 属性创建一个 BFC 并且包含浮动,但是在某些情况下你可能会发现得到一个了不必要的滚动条,或者阴影被剪掉了。这是由于 overflow 属性本质上是告诉浏览器在溢出的情况下应该怎样做—产生滚动条或者剪掉元素。浏览器实际上做了你让它做的工作!

即使在没有任何副作用的情况下,使用 overflow 属性也可能会让另一个开发人员感到困惑。为什么 overflow 属性设置为自动或滚动?开发者最初的目的是什么?他们希望在这个组件上使用滚动条吗?怎样创建一个 BFC 是行之有效的?应该是没有造成其他行为而创造出迷你的布局, 或者保证是在安全范围内的,它将不会引发任何意想不到的问题,并且开发人员的意图也很清晰。 CSS 工作组认为有一个很方便的新的 display 属性: flow-root 。

你可以在任何情况下使用 display: flow-root ,它将会创建一个新的有用的 BFC ,它包含浮动,阻止外边距塌陷,并且阻止元素环绕浮动。

你可以在下面的 CodePen 中看到上述所有的这些, 如果你的浏览器支持 display: flow-root的话,如目前流行的火狐或谷歌浏览器。

图8:支持 display: flow-root 属性的浏览器

支持这个属性的浏览器是有限的,但如果你认为这将是方便的,你可以去支持它。然而,即使目前你不能够在你的代码很流利的使用 flow-root 功能,但你现在明白了 BFC 是什么,以及当你使用 overflow 属性或其它方法包含浮动的时候你明白了你在做什么。了解这样一个事实:比如 BFC 将阻止元素环绕浮动,这在不支持的浏览器中想创建 Flex 或 Grid 布局的时候都是非常有用的。

Object.assign()

Object.assign() 方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

语法EDIT

Object.assign(target, ...sources)

参数

target
目标对象。
sources
(多个)源对象。

返回值

目标对象。

描述EDIT

如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。

Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象身上。该方法使用源对象的 [ [ Get ] ] 和目标对象的 [ [ Set ] ],所以它会调用相关 getter 和 setter。因此,它分配属性而不是复制或定义新的属性。如果合并源包含了 getter,那么该方法就不适合将新属性合并到原型里。假如是拷贝属性定义到原型里,包括它们的可枚举性,那么应该使用 Object.getOwnPropertyDescriptor()和 Object.defineProperty() 。

String类型和 Symbol 类型的属性都会被拷贝。

注意,在属性拷贝过程中可能会产生异常,比如目标对象的某个只读属性和源对象的某个属性同名,这时该方法会抛出一个 TypeError 异常,拷贝过程中断,已经拷贝成功的属性不会受到影响,还未拷贝的属性将不会再被拷贝。

注意, Object.assign 会跳过那些值为 null 或 undefined 的源对象。

示例EDIT

复制一个 object

var obj = { a: 1 }; var copy = Object.assign({}, obj); console.log(copy); // { a: 1 }

深度拷贝问题

针对深度拷贝,需要使用其他方法,因为 Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。

function test() {   let a = { b: {c:4} , d: { e: {f:1}} }   let g = Object.assign({},a)   let h = JSON.parse(JSON.stringify(a));   console.log(g.d) // { e: { f: 1 } }   g.d.e = 32   console.log('g.d.e set to 32.') // g.d.e set to 32.   console.log(g) // { b: { c: 4 }, d: { e: 32 } }   console.log(a) // { b: { c: 4 }, d: { e: 32 } }   console.log(h) // { b: { c: 4 }, d: { e: { f: 1 } } }   h.d.e = 54   console.log('h.d.e set to 54.') // h.d.e set to 54.   console.log(g) // { b: { c: 4 }, d: { e: 32 } }   console.log(a) // { b: { c: 4 }, d: { e: 32 } }   console.log(h) // { b: { c: 4 }, d: { e: 54 } } } test();

合并 objects

var o1 = { a: 1 }; var o2 = { b: 2 }; var o3 = { c: 3 };  var obj = Object.assign(o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } console.log(o1);  // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。

拷贝 symbol 类型的属性

var o1 = { a: 1 }; var o2 = { [Symbol("foo")]: 2 };  var obj = Object.assign({}, o1, o2); console.log(obj); // { a: 1, [Symbol("foo")]: 2 }

继承属性和不可枚举属性是不能拷贝的

var obj = Object.create({foo: 1}, { // foo 是个继承属性。     bar: {         value: 2  // bar 是个不可枚举属性。     },     baz: {         value: 3,         enumerable: true  // baz 是个自身可枚举属性。     } });  var copy = Object.assign({}, obj); console.log(copy); // { baz: 3 }

原始类型会被包装为 object

var v1 = "abc"; var v2 = true; var v3 = 10; var v4 = Symbol("foo")  var obj = Object.assign({}, v1, null, v2, undefined, v3, v4);  // 原始类型会被包装,null 和 undefined 会被忽略。 // 注意,只有字符串的包装对象才可能有自身可枚举属性。 console.log(obj); // { "0": "a", "1": "b", "2": "c" }

异常会打断接下来的拷贝任务

var target = Object.defineProperty({}, "foo", {     value: 1,     writable: false }); // target 的 foo 属性是个只读属性。  Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4}); // TypeError: "foo" is read-only // 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。  console.log(target.bar);  // 2,说明第一个源对象拷贝成功了。 console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。 console.log(target.foo);  // 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。 console.log(target.foo3); // undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。 console.log(target.baz);  // undefined,第三个源对象更是不会被拷贝到的。

拷贝访问器(accessor)

var obj = {   foo: 1,   get bar() {     return 2;   } };  var copy = Object.assign({}, obj);  // { foo: 1, bar: 2 } // copy.bar的值来自obj.bar的getter函数的返回值  console.log(copy);   // 下面这个函数会拷贝所有自有属性的属性描述符 function completeAssign(target, ...sources) {   sources.forEach(source => {     let descriptors = Object.keys(source).reduce((descriptors, key) => {       descriptors[key] = Object.getOwnPropertyDescriptor(source, key);       return descriptors;     }, {});      // Object.assign 默认也会拷贝可枚举的Symbols     Object.getOwnPropertySymbols(source).forEach(sym => {       let descriptor = Object.getOwnPropertyDescriptor(source, sym);       if (descriptor.enumerable) {         descriptors[sym] = descriptor;       }     });     Object.defineProperties(target, descriptors);   });   return target; }  var copy = completeAssign({}, obj); // { foo:1, get bar() { return 2 } } console.log(copy);

PolyfillEDIT

由于 ES5 里压根就没有 symbol 这种数据类型,所以这个 polyfill 也没必要去支持 symbol 属性(意思就是说,有 symbol 的环境一定有原生的 Object.assign):

if (typeof Object.assign != 'function') {   Object.assign = function(target) {     'use strict';     if (target == null) {       throw new TypeError('Cannot convert undefined or null to object');     }      target = Object(target);     for (var index = 1; index < arguments.length; index++) {       var source = arguments[index];       if (source != null) {         for (var key in source) {           if (Object.prototype.hasOwnProperty.call(source, key)) {             target[key] = source[key];           }         }       }     }     return target;   }; }

css实现强制不换行/自动换行/强制换行

强制不换行

div{white-space:nowrap;}

自动换行

div{word-wrap: break-word; word-break: normal;}

强制英文单词断行

div{word-break:break-all;}

word-wrap:

css的 word-wrap 属性用来标明是否允许浏览器在单词内进行断句,这是为了防止当一个字符串太长而找不到它的自然断句点时产生溢出现象。

word-break:

css的 word-break 属性用来标明怎么样进行单词内的断句。

定义和用法

word-break 属性规定自动换行的处理方法。

提示:通过使用 word-break 属性,可以让浏览器实现在任意位置的换行。

默认值: normal
继承性: yes
版本: CSS3
JavaScript 语法: object .style.wordBreak=”keep-all”
语法
word-break: normal|break-all|keep-all;
描述
normal 使用浏览器默认的换行规则。
break-all 允许在单词内换行。
keep-all 只能在半角空格或连字符处换行。

定义和用法

word-wrap 属性允许长单词或 URL 地址换行到下一行。
默认值: normal
继承性: yes
版本: CSS3
JavaScript 语法: object .style.wordWrap=”break-word”

语法

word-wrap: normal|break-word;
描述
normal 只在允许的断字点换行(浏览器保持默认处理)。
break-word 在长单词或 URL 地址内部进行换行。