林深深

林深深 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

魔法师

个人动态

林深深 关注了用户 · 9月23日

joking_zhang @joking_zhang

"Life's simple , you make choices and you don't look back."

关注 3316

林深深 关注了专栏 · 9月23日

前端爱翻译

我会在这里定期翻译一些前端领域优秀的,且包含示例的文章, 至少,我觉得这些文章对我自己有用~ グッ!(๑•̀ㅂ•́)و✧

关注 4967

林深深 关注了专栏 · 9月23日

javascript魔法师

涉及我的学习心得与经验

关注 1312

林深深 关注了用户 · 9月23日

疯狂的技术宅 @evilboy

资深技术宅,爱好广泛,兴趣多变。博览群书,喜欢扯淡。十八种语言样样稀松。想要了解更多,请关注微信公众号:充实的脑洞

关注 5832

林深深 关注了专栏 · 9月23日

疯狂的技术宅

本专栏文章首发于公众号:前端先锋 。

关注 24309

林深深 关注了专栏 · 9月2日

前端工匠公众号

我是浪里行舟,每周分享至少两篇前端文章,致力于打造一系列能够帮助初中级工程师提高的优质文章

关注 7015

林深深 关注了专栏 · 7月29日

前端小码农

分享有趣的代码,让编程更有趣;总结自己学习过程中遇到的坑

关注 1498

林深深 收藏了文章 · 7月26日

那些不常见,但却非常实用的js知识(整理不易)

一、window

window 对象表示一个包含 DOM 文档的窗口,其 document 属性指向窗口中载入的 DOM 文档 。

1、window 属性和方法

在有标签页功能的浏览器中,每个标签都拥有自己的 window 对象;也就是说,同一个窗口的标签页之间不会共享一个 window 对象。

1.1、几个浏览器的高度

window.screen.height==window.screen.availHeight
表示手机的屏幕高度,在一部手机中是固定的。不同浏览器打开,都不会变。

window.innerHeight==document.documentElement.clientHeight
表示浏览器中,除去顶部地址栏,下部工具栏之外,暴露给用户,可以看的见的中间区域。也就是实际的网页浏览高度,不同浏览器不同。

document.body.clientHeight
body 元素的高度。
如果设置 body 的 height:30px; 那么这个属性就是 30。

下面是示意图,在 pc 端同理
image

1.2、window.performance

Web Performance API 允许网页访问某些函数来测量网页和 Web 应用程序的性能,包括 Navigation Timing API 和高分辨率时间数据。

performance.now()

该方法返回一个 DOMHighResTimeStamp 对象,该对象表示从某一时刻(译者注:某一时刻通常是 navigationStart 事件发生时刻)到调用该方法时刻的毫秒数。

image

1.3、devicePixelRatio

返回当前显示设备的物理像素分辨率与 CSS 像素分辨率之比。

物理像素分辨率表示,你的电脑的硬件部分,在出场时,已经确定。

而 css 像素分辨率,则可以动态调整。
当电脑显示设置为 100%时,物理像素分辨率和 css 像素分辨率是相等的。即 devicePixelRatio 为 1

window.devicePixelRatio   //1

image

而如果你将电脑设置为 150%;或者在电脑设置 100%时,同时将浏览器分辨率调成 150%,那么 devicePixelRatio 都为 1.5
image

image

window.devicePixelRatio   //1.5

你可以将 devicePixelRatio 为 1.5 理解为,1px 的 css 样式,占用了电脑硬件屏幕分辨路的 1.5 个 dpi,所以肉眼看起来电脑上的文字更大些。

这种放大的效果在某些情况下,会导致 html 中的元素失真,典型的便是 canvas 变得模糊。

下面是矫正代码

//size为原本的大小
let scale = window.devicePixelRatio;
canvas.width = Math.floor(size * scale);
canvas.height = Math.floor(size * scale);

image

1.4、base64 编码/解码
window.btoa() 编码为 base64

window.atob() 解码

let encodedData = window.btoa("Hello, world"); // 编码
let decodedData = window.atob(encodedData);    // 解码

image

1.5、requestAnimationFrame / cancelAnimationFrame()

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

回调函数执行次数通常是每秒 60 次,但在大多数遵循 W3C 建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。但是却和 setInterval(),不同,因为 setInterval 设置的秒,并不一定准确在对应秒后执行,而是需要看是否有其他资源在执行,如果有,则等待。实际可能大于等于设置的秒数,而 requestAnimationFrame 会准确的按照浏览器渲染的频率想匹配

//兼容性处理
var requestAnimationFrame =
  window.requestAnimationFrame ||
  window.mozRequestAnimationFrame ||
  window.webkitRequestAnimationFrame ||
  window.msRequestAnimationFrame;

var cancelAnimationFrame =
  window.cancelAnimationFrame || window.mozCancelAnimationFrame;

var start = window.mozAnimationStartTime; // 只有Firefox支持mozAnimationStartTime属性,其他浏览器可以使用Date.now()来替代.

var myReq;
function step(timestamp) {
  var progress = timestamp - start;
  d.style.left = Math.min(progress / 10, 200) + "px";
  if (progress < 2000) {
    myReq = requestAnimationFrame(step); //必须再调用一次,这样就形成了递归
  }
}
myReq = requestAnimationFrame(step);

//当不需要是取消动画,比如 在vue destoryed中
window.cancelAnimationFrame(myReq);

image

image

1.6、getSelection()

此方法不必传入具体的 dom,选中哪个区域或者光标在哪个输入框, 此方法会获取到那个 dom。 返回一个selection对象

window.getSelection();

image

1.7、scroll() / scrollBy() / scrollTo()

scroll()滚动至文档中的绝对位置(类似于 css 中的 display:absolute), scrollBy()滚动指定的距离(类似于 css 中的 display:relative)

scrollTo 和 scroll 一致。

window.scroll({
  top: 100,
  left: 100,
  behavior: 'smooth'  //表示是否平滑的过渡还是即可调到指定位置,同css属性scroll-behavior
});
window.scrollBy({
  top: 100,
  left: 100,
  behavior: "smooth"
});

scroll() scrollBy() scrollTo()合称为 scrollOptions API
image

2、window 事件

通常在定义 window 中的事件时,有两种方式:

  • window.onXXX = function(){};
  • window.addEventListener("XXX", ()=>{}, {capture:true, once:true, passive:true});

那么这两种方式有区别吗? 有区别,但也没区别。

因为虽然定义的方式不同,可最终都会走到事件中。

但是又有区别,尤其在多个组件中,同时定义相同的事件,那么 window.onXXX 会后面定义的把前面定义的相同事件覆盖掉。最终只会执行最后定义的那个。而 window.addEventListener 不同,定义几个,执行几次,而且还可以再 options 中设置 once,capture,passive 等选项。

以 passive 为例,它可以忽略事件得默认行为,不会阻止它。当你在 js 操作时,html 中会立马响应到。最典型得可以使得触摸事件和 scroll 事件不再卡顿,非常流畅。

通常有 XXX 方式,那么对应也会有 onXXX

2.1、onbeforeunload

当窗口即将被卸载(关闭)时,会触发该事件.此时页面文档依然可见,且该事件的默认动作可以被取消.

需要注意的是,点击浏览器标签页的 x 号前,如果没有对页面进行过任何“操作”,则不会弹框直接关闭,如果进行过操作,则会弹出确认框。

这里所指的“操作”包括,但不限于,input 输入,打开 console,复制页面的一段文字等等。

window.onbeforeunload = () => {
    let isIE = xxxxxxxx; //忽略通过user-agent判断ie浏览器的方法
    if (isIE) {
      return "The system may be not save your changes.";
    } else {
      return false; //其他浏览器会弹出自己的文字,如上图。是中文
    }
};

image

2.2、onhashchange

当 一个窗口的 hash (URL 中 # 后面的部分)改变时就会触发 hashchange 事件(参见 location.hash)。

window.onhashchange = (newURL, oldURL)=>{
    //newURL 当前页面新的URL
    //oldURL 当前页面旧的URL
};

image

2.3、onload / DOMContentLoaded

DOMContentLoaded 指 html 页面加载完毕就会触发,至于一些异步资源的比如 img,video 等等,它不关心。

而 onload 表示除了 html 加载完,img,video 等也加载完,才执行

window.addEventListener("load", function () {

});

window.addEventListener("DOMContentLoaded", function () {

});

image

image

看到没: JavaScript event that fires when the DOM is loaded, but before all page assets are loaded (CSS, images, etc.). 说的再清楚不过了

2.4、onmouseover、onmouseout / onmouseenter、onmouseleave

onmouseover、onmouseout:鼠标移动到自身时候会触发事件,同时移动到其子元素身上也会触发事件 onmouseenter、onmouseleave:鼠标移动到自身是会触发事件,但是移动到其子元素身上不会触发事件

onmouseover 和 onmouseout 支持性一致
image

onmouseenter 和 onmouseleave 支持性一致
image

2.5、onscroll / onwheel

onwheel 事件在鼠标滚轮在元素上下滚动时触发,必须滚轮要转,至于页面滚不滚动,它不关心。

onscroll 事件在元素滚动条在滚动时触发,必须滚动条存在且上下运动。
触发方式有:滚轮滚动,鼠标按住滚动条拖动,键盘上下键滚动,js 脚本去滚动如 scrollTo,scrollBy,scrollByLines, scrollByPages 等。

在这里有个特殊,在手机端,虽然没有鼠标,但是手指触摸上下滚动,也是触发 onscroll 的。

image

image
什么? 支持率这么一点点? 大大出乎我的意料

2.6、copy 事件/cut 事件/ paste 事件

window 的复制/剪切, 粘贴事件。

window.addEventListener("copy", function (e) {
    //将复制的数据,存入到剪切板中
    e.clipboardData.setData("abc", "Hello, world!");
    e.preventDefault();
});

window.addEventListener("paste", function (e) {
    //再从剪切板中取出数据
    let data = e.clipboardData.getData("abc");
    e.preventDefault();
});

这三个事件支持性一致

image

2.7、error 事件

当资源加载失败或无法使用时,会在 Window 对象触发 error 事件。例如:script 执行时报错。

window.addEventListener('error', (event) => {

});

image

2.8、orientationchange 事件

orientationchange 事件在设备的纵横方向改变时触发。

window.addEventListener("orientationchange", function() {

});

image
pc 上几乎都不支持,这也能理解,毕竟 pc 上是使用 resize 的,只有手机才会使用 横竖屏。

2.9、rejectionhandled 事件 / unhandledrejection 事

当 Promise 被 rejected 且有 rejection 处理器时会在全局触发 rejectionhandled。

当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;

window.addEventListener("rejectionhandled", event => {

}, false);

window.addEventListener("unhandledrejection", event => {

});


function getName() {
    return Promise.reject("error");
}

let p = getName();   //触发unhandledrejection
setTimeout(() => {
    p.catch((err) => {});  //触发rejectionhandled
}, 1000);

image

image

2.10、storage 事件

当 localStorage 被修改时,将触发 storage 事件。

注意:

  • 只有在同域名的不同文件之间,才能够监听到,
  • 同一个文件中,是监听不到的(合理,同一个文件直接拿就行了,用 storage 监听多此一举)
  • 通过 iframe 引入的另一个站点,也可以监听到

image

2.11、online 事件 / offline 事件

online 当网络连接时,触发,
offline 当网络断开时触发

online 和 offline 支持性一致
image

二、element(DOM 对象)

1、element 属性和方法

1.1、clientHeight / clientWidth 属性
  • clientHeight:元素内部的高度,包含内边距,但不包括水平滚动条、边框和外边距。
  • clientWidth:元素内部的宽度,包含内边距,但不包括垂直滚动条、边框和外边距。

此属性会将获取的值四舍五入取整数。

document.querySelector("span").clientWidth

clientHeight 和 clientWidth 支持性一致
image

1.2、scrollHeight / scrollWidth 属性

类似于上方的 clientHeight / clientWidth,不同在于 clientHeight / clientWidth 在元素设置 overflow 后,不包含隐藏不可见的高度部分。而 scrollHeight / scrollWidth 却包含隐藏的那部分高度。

我觉得利用这个特性来判断是否超长,如果 scrollWidth 大于 clientWidth,则表示超长,此时可以对于那些超长后显示...的元素在浮上去后展示一个自定义的 toolip.

scrollHeight 和 scrollWidth 支持性一致
image

1.3、getBoundingClientRect()方法

返回元素的大小及其相对于视口的位置(眼睛看的见的文档区域)。

这个方法超级好用。不管你的元素在什么位置,它都会计算出来当前元素 相当于 视口的边缘的位置,和滚动条无关。

image

返回值为

{
    bottom: xx,  //元素底部离视口顶部的距离
    height: xx,   //元素高度,和元素的clientHeight属性一致,但比它精确,会保留小数
    left: xx,  //元素左边离视口左侧的距离
    right: xx,  //元素右边离视口左侧的距离
    top: xx,   //元素上部离视口顶部的距离
    width: xx,   //元素宽度,和元素的clientWidth属性一致,但比它精确,会保留小数
}

image
这个方法的支持性为 100%, 良心啊,为什么我才知道这个方法?

1.4、scrollIntoView()方法

如果父元素定义了 overflow,并产生了滚动条,里面有个子元素,在滚动条滑动后,看不到了。那么可以让这个子元素执行这个方法,让元素滚动到父元素可视区域内。

可以定义滚动条可视区域的顶部,底部,还是左边,右边。
也可以定义平滑的滚动过来,还是一瞬间滚动过来。

这个方法的好处是自动计算里面子元素距离可见区域多少,不需要给数值进行人为干涉,而 scroll()或 scrollTo()方法,必须人为计算滚动条的距离,然后给个数值,才能滚动到某个位置。

首先看个例子,
image

div1 超过屏幕的区域有个 div2,超过 div2 的区域有个 span1,此时我们可以通过调用 span1 元素的 scrollIntoView 方法,让 span1,滚动到 div2 的可见区域,也就是向左边滚动,但是有个坏处,就是 div1 也会向上滚动,直到 div2 能被用户看到。
而 scroll()或 scrollTo()方法在 span1 滚动到 div2 的可见区域时,div1 不动。不会产生其他附加作用,直到你慢慢滑屏,才会看到 span1,已经停留在 div2 的可视范围了。

image

1.5、scroll() / scrollTo() / scrollBy() 方法

scrollTo 方法可以使界面滚动到给定元素的指定坐标位置。

以产生滚动条的那个父元素为基准,

scroll() scrollBy() scrollTo()合称为 scrollOptions API
image

2、element 事件

2.1、contextmenu 事件

contextmenu 事件会在用户尝试打开上下文菜单时被触发。该事件通常在鼠标点击右键或者按下键盘上的菜单键时被触

<p id="noContextMenu">这个段落右键菜单已被禁用。</p>



noContext = document.getElementById('noContextMenu');

noContext.addEventListener('contextmenu', e => {
  e.preventDefault();
});

单击右键,不会出现标准的右键菜单项目

image

2.2、copy 事件/cut 事件/ paste 事件

dom 的复制/剪切, 粘贴事件。

<div class="source">
  Try copying text from this box...
</div>
<div class="target" contenteditable="true">
  ...and pasting it into this one
</div>
let sourceDom = document.querySelector(".source");
sourceDom.addEventListener("copy", function (e) {
  //将数据存入剪切板
  e.clipboardData.setData("abc", "Hello, world!");
  e.preventDefault();
});

let targetDom = document.querySelector(".target");
targetDom.addEventListener("paste", function (e) {
  //从剪切板取出来
  let data = e.clipboardData.getData("abc");

  //自定义转化数据
  data = data.toUpperCase();

  //获取光标位置
  const selection = window.getSelection();
  if (!selection.rangeCount) return false;
  selection.deleteFromDocument();
  //将转换后的数据插入到光标位置
  selection.getRangeAt(0).insertNode(document.createTextNode(data));

  e.preventDefault();
});

这三个事件支持性一致

image

2.3、focusin 事件 / focusout 事件

当元素获得焦点时,focusin 事件被触发。
focusin 事件和 focus 事件之间的主要区别在于 focus 不会冒泡。

当元素即将失去焦点时,focusout 事件被触发。
focusout 事件和 blur 事件之间的主要区别在于 blur 不会冒泡。

image

2.4、onscroll / onwheel

类似于 window 的 onscroll 和 onwheel。只不过绑定对象为 element

let sourceDom = document.querySelector(".source");
sourceDom.addEventListener("scroll", function (e) {
    console.log("111");
});

sourceDom.addEventListener("wheel", function (e) {
    console.log("111");
});

三、document(文档对象)

1、document 属性和方法

1.1、characterSet

返回当前文档的字符编码,但有相当一部分浏览器未实现,可使用原始的 charset 代替

document.characterSet || document.charset
1.2、compatMode

表明当前文档的渲染模式是怪异模式/混杂模式还是标准模式。

document.compatMode  //  BackCompat怪异模式, CSS1Compat标准模式

image

1.3、defaultView

返回当前 document 对象所关联的 window 对象,如果没有,会返回 null。

document.defaultView   //返回还是window,我为什么不直接 使用window呢?

上面的代码很平淡,不足为奇,可是下面的就不一定了

<div class="source" style="height: 200px; overflow: auto;">
    <object type="text/html" style="width: 100%; height: 100%;"> </object>
</div>
let doc = document.querySelector("object").contentDocument; //得到一个document对象

doc.defaultView; //接着得到一个window

得到这个 window 后,有什么用呢?

我们可以想象一个场景,当我们缩放 window 窗口时,会触发 window 的 onresize 事件,但是缩放 div,却不会触发 onresize 事件,因为 dom 没有 onresize,那么如何监听一个 div 的 resize 呢?

就是上述的方式,在 div 中套一个 object。我不监听 div,我监听 div 中的 object,一旦 div 变了,object 不也就变了吗?

然后通过 object 返回的一个 window 对象,就自然可以绑定 onresize 事件了

document.querySelector("object").contentDocument.defaultView .addEventListener("resize", () => {
    //只要div尺寸变化,object尺寸就变化,resize就能监听到。不管何种原因导致的尺寸变化。都会监听到。
});

image

除此之外,我有一遍专门介绍如何彻底解决 div 尺寸变化问题的文章,有兴趣的伙伴可以阅读

1.4、designMode

控制整个文档是否可编辑。有效值为 "on" 和 "off" 。

默认值为 "off" 。如果设置为"on",则好比给 html 所有的元素都添加了 contenteditable 属性。

image

1.5、documentElement

会返回文档对象(document)的根元素。

可以通过 document.documentElement.clientHeight 来获取浏览器的可用高度,这个高度和 html 或者 body 的 style 上的 height 无关,只与浏览器的上方地址栏,下方工具栏等有关,和 window.innerHeight 相等。

image

2、document 事件

2.1、onscroll / onwheel

类似于 window 的 onscroll 和 onwheel。只不过绑定对象为 document

document.addEventListener("scroll", function (e) {
    console.log("111");
});

document.addEventListener("wheel", function (e) {
    console.log("111");
});
查看原文

林深深 收藏了文章 · 7月26日

JS开发必须知道的41个技巧【持续更新】

js-tricks.jpg

前言

JS是前端的核心,但有些使用技巧你还不一定知道;
本文梳理了JS的41个技巧,帮助大家提高JS的使用技巧;
文章有点长,可以clone下源码,直接撸,源码地址请戳全部源码,原创不易,欢迎star;
序列文章:
Vue 开发必须知道的 36 个技巧
React 开发必须知道的 34 个技巧

Array

1.数组交集

普通数组

const arr1 = [1, 2, 3, 4, 5 , 8 ,9],arr2 = [5, 6, 7, 8, 9];

const intersection = arr1.filter(function (val) { return arr2.indexOf(val) > -1 })
console.log(intersection) //[5, 8, 9]

数组对象
数组对象目前仅针对value值为简单的Number,String,Boolan数据类型

const arr1 = [{ name: 'name1', id: 1 }, { name: 'name2', id: 2 }, { name: 'name3', id: 3 }, { name: 'name5', id: 5 }];
const arr2 = [{ name: 'name1', id: 1 }, { name: 'name2', id: 2 }, { name: 'name3', id: 3 }, { name: 'name4', id: 4 }, { name: 'name5', id: 5 }];
const result = arr2.filter(function (v) {
  return arr1.some(n => JSON.stringify(n) === JSON.stringify(v))
})
console.log(result); // [{ name: 'name1', id: 1 },{ name: 'name2', id: 2 },{ name: 'name3', id: 3 },{ name: 'name5', id: 5 }]

2.数组并集

普通数组

const arr1 = [1, 2, 3, 4, 5, 8, 9]
const arr2 = [5, 6, 7, 8, 9];
const result = arr1.concat(arr2.filter(v => !arr1.includes(v)))
console.log(result) //[1, 2, 3, 4,5, 8, 9]

数组对象

const arr1 = [{ name: 'name1', id: 1 }, { name: 'name2', id: 2 }, { name: 'name3', id: 3 }];
const arr2 = [{ name: 'name1', id: 1 }, { name: 'name4', id: 4 }, { name: 'name5', id: 5 }];
let arr3 = arr1.concat(arr2);
let result = [];
let obj = [];
result = arr3.reduce(function (prev, cur, index, arr) {
  obj[cur.id] ? '' : obj[cur.id] = true && prev.push(cur);
  return prev;
}, []);
console.log(result); //[{ name: 'name1', id: 1 },{ name: 'name2', id: 2 },{ name: 'name3', id: 3 },{ name: 'name4', id: 4 },{ name: 'name5', id: 5 }]

3.数组差集

数组arr1相对于arr2所没有的
普通数组

const arr1 = [1, 2, 3, 4, 5, 8, 9]
const arr2 = [5, 6, 7, 8, 9];
const diff = arr1.filter(item => !new Set(arr2).has(item))
console.log(diff) //[ 1, 2, 3, 4 ]

数组对象

// 对象数组
let arr1 = [{ name: 'name1', id: 1 }, { name: 'name2', id: 2 }, { name: 'name3', id: 3 }];
let arr2 = [{ name: 'name1', id: 1 }, { name: 'name4', id: 4 }, { name: 'name5', id: 5 }];
let result = arr1.filter(function (v) {
  return arr2.every(n => JSON.stringify(n) !== JSON.stringify(v))
})
console.log(result); // [ { name: 'name2', id: 2 }, { name: 'name3', id: 3 } ]

4.数组补集

两个数组各自没有的集合
普通数组

const arr1 = [1, 2, 3, 4, 5, 8, 9]
const arr2 = [5, 6, 7, 8, 9];
const difference = Array.from(new Set(arr1.concat(arr2).filter(v => !new Set(arr1).has(v) || !new Set(arr2).has(v)))) 
console.log(difference) //[ 1, 2, 3, 4, 6, 7 ]

数组对象

let arr1 = [{ name: 'name1', id: 1 }, { name: 'name2', id: 2 }, { name: 'name3', id: 3 }];
let arr2 = [{ name: 'name1', id: 1 }, { name: 'name4', id: 4 }, { name: 'name5', id: 5 }];
let arr3 = arr1.concat(arr2);
let result = arr3.filter(function (v) {
  return arr1.every(n => JSON.stringify(n) !== JSON.stringify(v)) || arr2.every(n => JSON.stringify(n) !== JSON.stringify(v))
})
console.log(result); // [{ name: 'name2', id: 2 },{ name: 'name3', id: 3 },{ name: 'name4', id: 4 },{ name: 'name5', id: 5 }]

总结一下,差集就是数组arr1相对于arr2所没有的集合,补集是两个数组各自没有的集合

5.数组去重

普通数组

console.log(Array.from(new Set([1, 2, 3, 3, 4, 4]))) //[1,2,3,4]
console.log([...new Set([1, 2, 3, 3, 4, 4])]) //[1,2,3,4]

数组对象

const arr = [{ name: 'name1', id: 1 }, { name: 'name2', id: 2 }, { name: 'name3', id: 3 }, { name: 'name1', id: 1 }, { name: 'name4', id: 4 }, { name: 'name5', id: 5 }];
 const result = [];
 arr.forEach(item=>{
    !result.some(v => JSON.stringify(v) === JSON.stringify(item)) && result.push(item)
 })
 console.log(result) //[{ name: 'name1', id: 1 },{ name: 'name2', id: 2 },{ name: 'name3', id: 3 },{ name: 'name4', id: 4 },{ name: 'name5', id: 5 }]

6.数组排序

普通数组

console.log([1, 2, 3, 4].sort((a, b) => a - b)); // [1, 2,3,4] 升序
console.log([1, 2, 3, 4].sort((a, b) => b - a)); // [4,3,2,1] 降序

数组对象

const arr1 = [{ name: "Rom", age: 12 }, { name: "Bob", age: 22 }].sort((a, b) => { return a.age - b.age })//升序
const arr2 = [{ name: "Rom", age: 12 }, { name: "Bob", age: 22 }].sort((a, b) => { return -a.age + b.age })//降序
console.log(arr2) // [{ name: 'Bob', age:22 }, { name: 'Rom', age: 12 }]
console.log(arr1) // [ { name: 'Rom', age: 12 }, { name: 'Bob', age: 22 } ]

两个种类型数组都可以使用sort排序,sort是浏览器内置方法;
默认是升序排序,默认返回一个函数,有两个参数:
(a, b) => a - b 是升序;
(a, b) => b - a 是降序。

7.最大值

普通数组

Math.max(...[1, 2, 3, 4]) //4
Math.max.apply(this, [1, 2, 3, 4]) //4
[1, 2, 3, 4].reduce((prev, cur, curIndex, arr) => {
   return Math.max(prev, cur);
}, 0) //4

取数组对象中id的最大值

const arr = [{ id: 1, name: 'jack' },{ id: 2, name: 'may' },{ id: 3, name: 'shawn' },{ id: 4, name: 'tony' }]
const arr1 = Math.max.apply(Math, arr.map(item => { return item.id }))
const arr2 = arr.sort((a, b) => { return b.id - a.id })[0].id
console.log(arr1) // 4
console.log(arr2) // 4

8.数组求和

普通数组

[1, 2, 3, 4].reduce(function (prev, cur) {
  return prev + cur;
}, 0) //10 

数组对象

const sum = [{age:1},{age:2}].reduce(function (prev, cur) {
  return prev + cur.age;
}, 0) //3
console.log(sum)

9.数组合并

普通数组

const arr1 =[1, 2, 3, 4].concat([5, 6]) //[1,2,3,4,5,6]
const arr2 =[...[1, 2, 3, 4],...[4, 5]] //[1,2,3,4,5,6]
const arrA = [1, 2], arrB = [3, 4]
const arr3 =[].concat.apply(arrA, arrB)//arrA值为[1,2,3,4]

数组对象

const arr4 = [{ age: 1 }].concat([{ age: 2 }])
const arr5 = [...[{ age: 1 }],...[{ age: 2 }]]
console.log(arr4) //[ { age: 1 }, { age: 2 } ]
console.log(arr5) // [ { age: 1 }, { age: 2 } ]

10.数组是否包含值

普通数组

console.log([1, 2, 3].includes(4)) //false
console.log([1, 2, 3].indexOf(4)) //-1 如果存在换回索引
console.log([1, 2, 3].find((item) => item === 3)) //3 如果数组中无值返回undefined
console.log([1, 2, 3].findIndex((item) => item === 3)) //2 如果数组中无值返回-1

数组对象

const flag = [{age:1},{age:2}].some(v=>JSON.stringify(v)===JSON.stringify({age:2}))
console.log(flag)

11.数组每一项都满足

普通数组

[1, 2, 3].every(item => { return item > 2 })

数组对象

const arr = [{ age: 3 }, { age: 4 }, { age: 5 }]
arr.every(item => { return item.age > 2 }) // true

12.数组有一项满足

普通数组

[1, 2, 3].some(item => { return item > 2 })

数组对象

const arr = [{ age: 3 }, { age: 4 }, { age: 5 }]
arr.some(item => { return item.age < 4 }) // true

13.版本号排序

方法一

function sortNumber(a, b) {
  return a - b
}
const b = [1,2,3,7,5,6]
const a = ["1.5", "1.5", "1.40", "1.25", "1.1000", "1.1"];

console.log(a.sort(sortNumber)); // [ 1, 2, 3, 5, 6, 7 ]
console.log(b.sort(sortNumber)); //[ '1.1000', '1.1', '1.25', '1.40', '1.5', '1.5' ]

可见sort排序对整数可以,类似版本号这个格式就不适用了,因为sort函数在比较字符串的时候,是比较字符串的Unicode进行排序的。

方法二

//假定字符串的每节数都在5位以下
//去除数组空值||空格
if (!Array.prototype.trim) {
  Array.prototype.trim = function () {
    let arr = []; this.forEach(function (e) {
      if (e.match(/\S+/)) arr.push(e);
    })
    return arr;
  }
}

//提取数字部分
function toNum(a) {
  let d = a.toString();
  let c = d.split(/\D/).trim();
  let num_place = ["", "0", "00", "000", "0000"], r = num_place.reverse();
  for (let i = 0; i < c.length; i++) {
    let len = c[i].length;
    c[i] = r[len] + c[i];
  }
  let res = c.join('');
  return res;
}

//提取字符
function toChar(a) {
  let d = a.toString();
  let c = d.split(/\.|\d/).join('');
  return c;
}

function sortVersions(a, b) {

  let _a1 = toNum(a), _b1 = toNum(b);
  if (_a1 !== _b1) return _a1 - _b1;
  else {
    _a2 = toChar(a).charCodeAt(0).toString(16);
    _b2 = toChar(b).charCodeAt(0).toString(16);
    return _a2 - _b2;
  }
}

let arr1 = ["10", "5", "40", "25", "1000", "1"];
let arr2 = ["1.10", "1.5", "1.40", "1.25", "1.1000", "1.1"];
let arr3 = ["1.10c", "1.10b", "1.10C", "1.25", "1.1000", "1.10A"];
console.log(arr1.sort(sortVersions)) //[ '1', '5', '10', '25', '40', '1000' ]
console.log(arr2.sort(sortVersions)) //[ '1.1', '1.5', '1.10', '1.25', '1.40', '1.1000' ]
console.log(arr3.sort(sortVersions)) // [ '1.10A', '1.10C', '1.10b', '1.10c', '1.25', '1.1000' ]

可以看出这个函数均兼容整数,非整数,字母;
字母排序是根据Unicode排序的,所以1.10b在1.10C的后面

14. 对象转数组

将数组的key和value转化成数组

Object.keys({ name: '张三', age: 14 }) //['name','age']
Object.values({ name: '张三', age: 14 }) //['张三',14]
Object.entries({ name: '张三', age: 14 }) //[[name,'张三'],[age,14]]
Object.fromEntries([name, '张三'], [age, 14]) //ES10的api,Chrome不支持 , firebox输出{name:'张三',age:14}

15.数组转对象

将数组的值转化为对象的value

const arrName = ['张三', '李四', '王五']
const arrAge=['20','30','40']
const arrDec = ['描述1', '描述2', '描述3']
const obj = arrName.map((item,index)=>{
  return { name: item, age: arrAge[index],dec:arrDec[index]}
})

console.log(obj) // [{ name: '张三', age: '20', dec: '描述1' },{ name: '李四', age: '30', dec: '描述2' },{ name: '王五', age: '40', dec: '描述3' }]

16.数组解构

const arr=[1,2]; //后面一定要加分号,因为不加解释器会认为在读数组
[arr[1], arr[0]] = [arr[0], arr[1]]; // [2,1]

Object

17.对象变量属性

const flag = true;
const obj = {
    a: 0,
    [flag ? "c" : "d"]: 2
};
// obj => { a: 0, c: 2 }

18.对象多余属性删除

const { name, age, ...obj } = { name: '张三', age: 13, dec: '描述1', info: '信息' }
console.log(name)  // 张三
console.log(age)  // 13
console.log(obj)  // {dec: '描述1', info: '信息' }

19.对象嵌套属性解构

const { info:{ dec} } = { name: '张三', age: 13, info:{dec: '描述1', info: '信息' }}
console.log(dec) // 描述1

20.解构对象属性别名

const { name:newName } = { name: '张三', age: 13 }
console.log(newName)  // 张三

21.解构对象属性默认值

const { dec='这是默认dec值' } = { name: '张三', age: 13 }
console.log(dec) //这是默认dec值

22.拦截对象

利用Object.defineProperty拦截对象
无法拦截数组的值

let obj = { name: '', age: '', sex: '' },
  defaultName = ["这是姓名默认值1", "这是年龄默认值1", "这是性别默认值1"];
Object.keys(obj).forEach(key => {
  Object.defineProperty(obj, key, { // 拦截整个object 对象,并通过get获取值,set设置值,vue 2.x的核心就是这个来监听
    get() {
      return defaultName;
    },
    set(value) {
      defaultName = value;
    }
  });
});

console.log(obj.name); // [ '这是姓名默认值1', '这是年龄默认值1', '这是性别默认值1' ]
console.log(obj.age); // [ '这是姓名默认值1', '这是年龄默认值1', '这是性别默认值1' ]
console.log(obj.sex); // [ '这是姓名默认值1', '这是年龄默认值1', '这是性别默认值1' ]
obj.name = "这是改变值1";
console.log(obj.name); // 这是改变值1
console.log(obj.age);  // 这是改变值1
console.log(obj.sex); // 这是改变值1

let objOne = {}, defaultNameOne = "这是默认值2";
Object.defineProperty(obj, 'name', {
  get() {
    return defaultNameOne;
  },
  set(value) {
    defaultNameOne = value;
  }
});
console.log(objOne.name); // undefined
objOne.name = "这是改变值2";
console.log(objOne.name); // 这是改变值2

利用proxy拦截对象

let obj = { name: '', age: '', sex: '' }
let handler = {
  get(target, key, receiver) {
    console.log("get", key); 
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log("set", key, value); // set name 李四  // set age 24
    return Reflect.set(target, key, value, receiver);
  }
};
let proxy = new Proxy(obj, handler);
proxy.name = "李四";
proxy.age = 24;

defineProterty和proxy的对比:
1.defineProterty是es5的标准,proxy是es6的标准;
2.proxy可以监听到数组索引赋值,改变数组长度的变化;
3.proxy是监听对象,不用深层遍历,defineProterty是监听属性;
4.利用defineProterty实现双向数据绑定(vue2.x采用的核心)

23.对象深度拷贝

JSON.stringify深度克隆对象;
1.无法对函数 、RegExp等特殊对象的克隆;
2.会抛弃对象的constructor,所有的构造函数会指向Object;
3.对象有循环引用,会报错

const objDeepClone = obj => {
  return clone(obj)
}

const isType = (obj, type) => {
  if (typeof obj !== 'object') return false;
  // 判断数据类型的经典方法:
  const typeString = Object.prototype.toString.call(obj);
  let flag;
  switch (type) {
    case 'Array':
      flag = typeString === '[object Array]';
      break;
    case 'Date':
      flag = typeString === '[object Date]';
      break;
    case 'RegExp':
      flag = typeString === '[object RegExp]';
      break;
    default:
      flag = false;
  }
  return flag;
};

/**
* deep clone
* @param  {[type]} parent object 需要进行克隆的对象
* @return {[type]}        深克隆后的对象
*/
const clone = parent => {
  // 维护两个储存循环引用的数组
  const parents = []
  const children = []

  const _clone = parent => {
    if (parent === null) return null
    if (typeof parent !== 'object') return parent

    let child, proto

    if (isType(parent, 'Array')) {
      // 对数组做特殊处理
      child = []
    } else if (isType(parent, 'RegExp')) {
      // 对正则对象做特殊处理
      child = new RegExp(parent.source, getRegExp(parent))
      if (parent.lastIndex) child.lastIndex = parent.lastIndex
    } else if (isType(parent, 'Date')) {
      // 对Date对象做特殊处理
      child = new Date(parent.getTime())
    } else {
      // 处理对象原型
      proto = Object.getPrototypeOf(parent)
      // 利用Object.create切断原型链
      child = Object.create(proto)
    }

    // 处理循环引用
    const index = parents.indexOf(parent)

    if (index !== -1) {
      // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
      return children[index]
    }
    parents.push(parent)
    children.push(child)

    for (const i in parent) {
      // 递归
      child[i] = _clone(parent[i])
    }

    return child
  }
  return _clone(parent)
}

console.log(objDeepClone({ 
  name: '张三', age: 23, 
  obj: { name: '李四', age: 46},
  arr:[1,2,3]
})) // { name: '张三', age: 23, obj: { name: '李四', age: 46 }, arr: [ 1, 2, 3 ] }

对象深度克隆实际上就是要兼容Array,RegExp,Date,Function类型;
克隆函数可以用正则取出函数体和参数,再定义一个函数将取出来的值赋值进去
详细请戳对象深度拷贝

24.对象是否相等

如果用JSON.stringify转化属性顺序不同,也不相等;
而且不支持无法对函数 、RegExp等特殊对象的克隆


function deepCompare(x, y) {
  var i, l, leftChain, rightChain;

  function compare2Objects(x, y) {
    var p;

    // remember that NaN === NaN returns false
    // and isNaN(undefined) returns true
    if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
      return true;
    }

    // Compare primitives and functions.     
    // Check if both arguments link to the same object.
    // Especially useful on the step where we compare prototypes
    if (x === y) {
      return true;
    }

    // Works in case when functions are created in constructor.
    // Comparing dates is a common scenario. Another built-ins?
    // We can even handle functions passed across iframes
    if ((typeof x === 'function' && typeof y === 'function') ||
      (x instanceof Date && y instanceof Date) ||
      (x instanceof RegExp && y instanceof RegExp) ||
      (x instanceof String && y instanceof String) ||
      (x instanceof Number && y instanceof Number)) {
      return x.toString() === y.toString();
    }

    // At last checking prototypes as good as we can
    if (!(x instanceof Object && y instanceof Object)) {
      return false;
    }

    if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
      return false;
    }

    if (x.constructor !== y.constructor) {
      return false;
    }

    if (x.prototype !== y.prototype) {
      return false;
    }

    // Check for infinitive linking loops
    if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
      return false;
    }

    // Quick checking of one object being a subset of another.
    // todo: cache the structure of arguments[0] for performance
    for (p in y) {
      if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
        return false;
      } else if (typeof y[p] !== typeof x[p]) {
        return false;
      }
    }

    for (p in x) {
      if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
        return false;
      } else if (typeof y[p] !== typeof x[p]) {
        return false;
      }

      switch (typeof (x[p])) {
        case 'object':
        case 'function':

          leftChain.push(x);
          rightChain.push(y);

          if (!compare2Objects(x[p], y[p])) {
            return false;
          }

          leftChain.pop();
          rightChain.pop();
          break;

        default:
          if (x[p] !== y[p]) {
            return false;
          }
          break;
      }
    }

    return true;
  }

  if (arguments.length < 1) {
    return true; 
  }

  for (i = 1, l = arguments.length; i < l; i++) {

    leftChain = []; //Todo: this can be cached
    rightChain = [];

    if (!compare2Objects(arguments[0], arguments[i])) {
      return false;
    }
  }

  return true;
}

const obj1 = { 
  name: '张三', age: 23, 
  obj: { name: '李四', age: 46 }, 
  arr: [1, 2, 3],
  date:new Date(23),
  reg: new RegExp('abc'),
  fun: ()=>{}
 }
const obj2 = { 
  name: '张三', age: 23, 
  obj: { name: '李四', age: 46 }, 
  arr: [1, 2, 3],
  date: new Date(23),
  reg: new RegExp('abc'),
  fun: ()=>{}
 }

console.log(deepCompare(obj1,obj2)) // true

判断对象是否相等,实际上就是要处理Array,Date,RegExp,Object,Function的特殊类型是否相等

25.对象转化为字符串

通过字符串+Object 的方式来转化对象为字符串(实际上是调用 .toString() 方法)

'the Math object:' + Math.ceil(3.4)                // "the Math object:4"
'the JSON object:' + {name:'曹操'}              // "the JSON object:[object Object]"

覆盖对象的toString和valueOf方法来自定义对象的类型转换

2  * { valueOf: ()=>'4' }                // 8
'J' + { toString: ()=>'ava' }                // "Java"

当+用在连接字符串时,当一个对象既有toString方法又有valueOf方法时候,JS通过盲目使用valueOf方法来解决这种含糊;
对象通过valueOf方法强制转换为数字,通过toString方法强制转换为字符串

'' + {toString:()=>'S',valueOf:()=>'J'}  //J

Function

26.函数隐式返回值

(()=>3)()  //3
(()=>(
   3
))()

函数省略大括号,或者将大括号改成小括号可以确保代码以单个语句的形式进行求值

27.函数自执行

const Func = function() {}(); // 常用

(function() {})(); // 常用
(function() {}()); // 常用
[function() {}()];

new function() {};
new function() {}();
void function() {}();
typeof function() {}();
delete function() {}();

+ function() {}();
- function() {}();
~ function() {}();
! function() {}();

28.函数异步执行

Promise

Promise.reject('这是第二个 reject 值').then((data)=>{
  console.log(data)
}).catch(data=>{
  console.log(data) //这是第二个 reject 值
})

Generator

function* gen(x) {
  const y = yield x + 6;
  return y;
}

// yield 如果用在另外一个表达式中,要放在()里面
// 像上面如果是在=右边就不用加()
function* genOne(x) {
  const y = `这是第一个 yield 执行:${yield x + 1}`;
  return y;
}

const g = gen(1);
//执行 Generator 会返回一个Object,而不是像普通函数返回return 后面的值
g.next() // { value: 7, done: false }
//调用指针的 next 方法,会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停,也就是执行yield 这一行
// 执行完成会返回一个 Object,
// value 就是执行 yield 后面的值,done 表示函数是否执行完毕
g.next() // { value: undefined, done: true }
// 因为最后一行 return y 被执行完成,所以done 为 true

Async/Await

function getSomething() {
    return "something";
}
async function testAsync() {
    return Promise.resolve("hello async");
}
async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2); //something 和 hello async
}
test();

String

29.字符串翻转

function reverseStr(str = "") {
  return str.split("").reduceRight((t, v) => t + v);
}

const str = "reduce123";
console.log(reverseStr(str)); // "321recuder"

30.url参数序列化

将对象序列化成url参数传递

function stringifyUrl(search = {}) {
  return Object.entries(search).reduce(
    (t, v) => `${t}${v[0]}=${encodeURIComponent(v[1])}&`,
    Object.keys(search).length ? "?" : ""
  ).replace(/&$/, "");
}

console.log(stringifyUrl({ age: 27, name: "YZW" })); // "?age=27&name=YZW"

31.url参数反序列化

一般会通过location.search拿到路由传递的参数,并进行反序列化得到对象

function parseUrlSearch() {
  const search = '?age=25&name=TYJ'
  return search.replace(/(^\?)|(&$)/g, "").split("&").reduce((t, v) => {
    const [key, val] = v.split("=");
    t[key] = decodeURIComponent(val);
    return t;
  }, {});
}

console.log(parseUrlSearch()); // { age: "25", name: "TYJ" }

32.转化为字符串

const val = 1 + ""; // 通过+ ''空字符串转化
console.log(val); // "1"
console.log(typeof val); // "string"

const val1 = String(1);
console.log(val1); // "1"
console.log(typeof val1); // "string"

Number

33.数字千分位

方法一:

function thousandNum(num = 0) {
  const str = (+num).toString().split(".");
  const int = nums => nums.split("").reverse().reduceRight((t, v, i) => t + (i % 3 ? v : `${v},`), "").replace(/^,|,$/g, "");
  const dec = nums => nums.split("").reduce((t, v, i) => t + ((i + 1) % 3 ? v : `${v},`), "").replace(/^,|,$/g, "");
  return str.length > 1 ? `${int(str[0])}.${dec(str[1])}` : int(str[0]);
}

thousandNum(1234); // "1,234"
thousandNum(1234.00); // "1,234"
thousandNum(0.1234); // "0.123,4"
console.log(thousandNum(1234.5678)); // "1,234.567,8"

方法二

console.log('1234567890'.replace(/\B(?=(\d{3})+(?!\d))/g, ","))
console.log((1234567890).toLocaleString())

34.字符串转数字

方法一
用*1来转化为数字,实际上是调用.valueOf方法

'32' * 1            // 32
'ds' * 1            // NaN
null * 1            // 0
undefined * 1    // NaN
1  * { valueOf: ()=>'3' }        // 3

方法二

+ '123'            // 123
+ 'ds'               // NaN
+ ''                    // 0
+ null              // 0
+ undefined    // NaN
+ { valueOf: ()=>'3' }    // 3

35.判断小数是否相等

肯定有人会说这还不简单,直接用'==='比较;
实际上0.1+0.2 !==0.3,因为计算机不能精确表示0.1, 0.2这样的浮点数,所以相加就不是0.3了

Number.EPSILON=(function(){   //解决兼容性问题
    return Number.EPSILON?Number.EPSILON:Math.pow(2,-52);
})();
//上面是一个自调用函数,当JS文件刚加载到内存中,就会去判断并返回一个结果
function numbersequal(a,b){ 
    return Math.abs(a-b)<Number.EPSILON;
  }
//接下来再判断   
const a=0.1+0.2, b=0.3;
console.log(numbersequal(a,b)); //这里就为true了

36.双位运算符

双位运算符比Math.floor(),Math.ceil()速度快

~~7.5                // 7
Math.ceil(7.5)       // 8
Math.floor(7.5)      // 7


~~-7.5                // -7
Math.floor(-7.5)     // -8
Math.ceil(-7.5)      // -7

所以负数时,双位运算符和Math.ceil结果一致,正数时和Math.floor结果一致

37.取整和奇偶性判断

取整

3.3 | 0         // 3
-3.9 | 0        // -3

parseInt(3.3)  // 3
parseInt(-3.3) // -3

// 四舍五入取整
Math.round(3.3) // 3
Math.round(-3.3) // -3

// 向上取整
Math.ceil(3.3) // 4
Math.ceil(-3.3) // -3

// 向下取整
Math.floor(3.3) // 3
Math.floor(-3.3) // -4

判断奇偶数

const num=5;
!!(num & 1) // true
!!(num % 2) // true

Boolean

38.判断数据类型

function dataTypeJudge(val, type) {
  const dataType = Object.prototype.toString.call(val).replace(/\[object (\w+)\]/, "$1").toLowerCase();
  return type ? dataType === type : dataType;
}
console.log(dataTypeJudge("young")); // "string"
console.log(dataTypeJudge(20190214)); // "number"
console.log(dataTypeJudge(true)); // "boolean"
console.log(dataTypeJudge([], "array")); // true
console.log(dataTypeJudge({}, "array")); // false

可判断类型:undefined、null、string、number、boolean、array、object、symbol、date、regexp、function、asyncfunction、arguments、set、map、weakset、weakmap

39.使用Boolean过滤数组假值

const compact = arr => arr.filter(Boolean)
compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34])  //[ 1, 2, 3, 'a', 's', 34 ]

40.短路运算

||(或)

const flag = false || true //true
// 某个值为假时可以给默认值
const arr = false || []

&&(与)

const flag1 = false && true //false
const flag2 = true && true //true

41.switch 简写

可以用对象替代switch,提高代码可读性

switch(a) {
  case '张三':
    return 'age是12'
  case '李四':
    return 'age是120'
}

// 使用对象替换后
const obj ={
  '张三': 'age12',
  '李四': 'age120',
}
console.log(obj['张三'])

结语

源码地址请戳全部源码
原创码字不易,欢迎start!

查看原文

林深深 收藏了文章 · 7月6日

前端工程师成长之多读好书

1 引言

乱七八糟的书看了很多,有一本讲JavaScript的印象特别深开篇说的是"JavaScript是Java的脚本语言",但还是看完了,最后忘了书名。

下面列的这些都是看过后至少记得起书名的,也有部分是经常看的书,一起列出来,推荐给爱学习的同学。

2 前端技术

2.1 综合

  • 《现代前端技术解析》
  • 《Web前端开发最佳实践》
  • 《Web前端工程师修炼之道》
  • 《编写高质量代码-Web前端开发修炼之道》
  • 《响应式Web设计 HTML5和CSS3实战》 第二版
  • 《响应式设计、改造与优化》

2.2 基础

2.2.1 HTML && HTML5

  • 《HTML与CSS基础教程》第八版
  • 《HTML与XHTML权威指南》第六版
  • 《HTML5与CSS3实战指南》
  • 《HTML5和CSS3权威指南》
  • 《HTML5与CSS3设计模式》

2.2.2 CSS && CSS3

  • 《CSS世界》
  • 《CSS核心技术详解》
  • 《CSS权威指南》 第三版
  • 《CSS设计指南》第三版
  • 《精通CSS-高级Web标准解决方案》第二版
  • 《图解CSS3-核心技术与案例实战》

2.2.3 JavaScript && ES6+

  • 《看透JavaScript:原理、方法与实践》
  • 《实战ES2015:深入现代JavaScript 应用开发》
  • 《学习JavaScript数据结构与算法》 第二版
  • 《ES6标准入门》第三版
  • 《JavaScript忍者秘籍》第二版
  • 《JavaScript学习指南》第三版
  • 《You Don't Know JS》《你不知道的JS》
  • 《JavaScript权威指南》第六版
  • 《JavaScript高级程序设计》 第三版
  • 《JavaScript核心概念及实践》
  • 《JavaScript面向对象编程指南》第二版
  • 《JavaScript DOM编程艺术》第二版
  • 《JavaScript语言精粹》
  • 《动态函数式编程语言精髓与编程实践》

2.3 性能优化

  • 《Web性能权威指南》
  • 《高性能JavaScript》
  • 《JavaScript性能优化:度量、监控与可视化》
  • 《高性能网站建设指南》
  • 《高性能网站建设进阶指南》
  • 《大型网站性能监测、分析与优化》
  • 《网站性能监测与优化》
  • 《高效前端-Web高效编程与优化实践》
  • 《速度与激情-以网站性能提升用户体验》

2.4 安全

  • 《Web前端黑客技术揭秘》
  • 《白帽子讲Web安全》
  • 《黑客攻防技术宝典 Web实战篇》第二版
  • 《Web应用安全威胁与防治 基于OWASP Top 10与ESAPI》
  • 《Web之困-现代Web应用安全指南》
  • 《Web安全开发指南》
  • 《Web应用安全权威指南》
  • 《黑客攻防技术宝典 浏览器实战篇》
  • 《XSS跨站脚本攻击剖析与防御》

2.5 工程化 && 自动化

  • 《深入浅出Webpack》
  • 《深入PostCSS Web设计》
  • 《前端工程化体系设计与实践》
  • 《Web前端测试与集成- Jasmine/Selenium/Protractor/Jenkins的最佳实践》
  • 《Web前端自动化构建-Gulp、Bower和Yeoman开发指南》

2.6 协议

  • 《Web性能权威指南》
  • 《图解HTTP》
  • 《HTTP权威指南》
  • 《HTTPS权威指南》
  • 《图解TCP-IP》

2.7 浏览器

  • 《浏览器工作原理》 文章
  • 《Webkit技术内幕》

2.8 架构

  • 《JavaScript框架设计》第二版
  • 《前端架构设计》
  • 《JavaScript开发框架权威指南》
  • 《大型JavaScript应用实践最佳指南》
  • 《JavaScript框架高级编程》
  • 《JavaScript设计模式与开发实践》
  • 《JavaScript设计模式》
  • 《JavaScript模式》

3 学点其他的

3.1 所谓的全栈

  • Web开发者技能路线图
  • 教你成为全栈工程师
  • 《全栈增长工程师指南》 《全栈应用开发-精益实践》
  • 《Web全栈工程师的自我修养》
  • 《Web开发权威指南》
  • 《JavaScript快速全栈开发》
  • 《单页Web应用-JavaScript从前端到后端》
  • 《全栈开发之道-MongoDB+Express+AngularJS+Node.js》
  • 《全端Web开发-使用JavaScript和Java》

3.2 程序设计

  • 《代码大全》第二版
  • 《修改代码的艺术》
  • 《重构-改善既有代码的设计》
  • 《代码整洁之道》

3.3 计算机基础

  • 《深入理解计算机系统》第三版
  • 《计算机是怎样跑起来的》
  • 《程序是怎样跑起来的》
  • 《网络是怎样连接的》
查看原文

认证与成就

  • 获得 0 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 3月25日
个人主页被 95 人浏览