W3Fun

W3Fun 查看完整档案

成都编辑  |  填写毕业院校  |  填写所在公司/组织 github.com/swpuleo 编辑
编辑

Keep original aspiration..

个人动态

W3Fun 赞了文章 · 2019-08-03

学习方法分享:为何一年半就能拿到大厂 offer

毕竟是聊聊曾经,放一张大学课堂上灵光一现,手写的一个我曾经一直使用的网名

前言

原文地址:Nealyang/personalBlog

讲真,的确是运气,才有机会进大厂。也没想到,那篇一年半工作经验试水杭州大厂的面经如此受欢迎。后面也有很多朋友在群里问我,你是如何学习的?

此篇为xxx 经验进阿里的终结篇,希望从此以后就翻过了,不再提了。不然总有种炫耀的感觉,倍感压力,汗颜汗颜~

此篇也并非技术软文。大概介绍下我在进阿里之前、工作中都经历和做过了些什么,最后我会分享一下敲开面试之门的那封简历。

关键节点经历交代

经历阶段,我尽量简短。

大学期间

从农村走出来的孩子,从只开开机关机到各种参加比赛,鬼知道我如何了解编程的。

最终我拿过Oracle java 全国青年设计大赛东北赛区一等奖、蓝桥杯编程省一、国三等等四五个编程方面的奖项吧。

大四实习期间

我使用 java 编写的坦克大战自定义 hack 版推开的实习公司汉得的门。在移动部,我原以为是用 java,结果是 hybrid App 开发。所以,实习期间,是我开始学习前端的开始。

刚开始的学习过程大家都一样,w3c搞起,最终,我成为了团队里面第一个带新人的,我还带人开发了现在汇联易App 的第一版。90%代码是我一个人写的。也理所应当的拿到了年度最佳新人奖项。这里再次感谢当初给我机会的我的老大,顺哥。

但是说实话,当时的技术,前端基础都掌握的不行、都是在用 ionic、cordova、angular。甚至连 jQuery 都不会。。。

第一份工作

毕业后在北京,第一份工作在环球网,事实证明当初的选择是正确的。

我正式接触前端,从编写页面开始。从刚开始的添加一个 click 事件监听都要百度,到最后半天能产出一张活动页。

后面一周学习 RN ,扛下了独自环球网 App (Android 版)的大旗。再后面调到平台组,开始接触了 react、node。

遇到过一些很多难的项目,也是当时我说的,怎么我一直在坑中。而这最后,都成为了我简历中比较出彩的地方。

阿里

后面决定跳槽,就想择一城。来到杭州,也就是大家看到的一年半前端工作经验试水杭州:我是如何拿下网易、阿里和滴滴 offer 的

关于前端

以下所有言论都是个人观点。如有不妥欢迎指出,一起交流

就前端而言,我个人认为有三个阶段。认知阶段、钻研阶段、掌握阶段

认知阶段

所谓认知阶段,就是开始接触前端,开始学习前端。

学习方法

这个阶段应该算是我在实习的阶段吧。一个从来接触过前端的大学生。简单总结就是各种看书、学习。

  • 从最基础的 HTML、css、JavaScript 开始学习。我个人是从 w3c 开始学习的,然后还顺带做了在线的知识掌握测试。
  • 每一次的工作都是挑战,每一次挑战都是成长。也是从这个时候,我开始养成了写博客的习惯。
  • 遇到任何新的技术,都从官网开始学习。因为这个阶段,官网能帮你解决 99%的问题
  • 遇到问题,尽量靠自己,别动不动就在群里提问。甚至,你要主动找问题。偷偷告诉你,我的 qq 群、微信群,都是我在刚学习这类知识的时候创建的,初期我是尽可能的回答群里每一个问题。虽然我是菜鸟,但是我会百度、Google 呀!

截止到 16 年初。这是我在实习阶段整理总结的自己项目中遇到的问题

开源中国 Nealyang 邪气小生

钻研阶段

所谓钻研阶段,就是你基本已经入门前端了,需要找一个方向,去学习,去钻研。比如三大框架是否可以挑选一门入坑。注意是钻研,而不是浅尝辄止。

学习方法

在这个阶段,我依旧会浏览各个官网的信息,同时就我个人而言,当初选择的是 react 技术栈+node ,这也是我最开始创建的两个技术交流群。

当然,工作中,恰巧我也用了一周时间学习 React Native,完成了官方 App 的代码编写。这让我提前对 react 有了一些了解。掌握 react 技术栈对于一个初学者来说挺艰难的。我花了一周,看完了所有教程。然后开始学习 react-router、redux、react-redux、然后也接触到了 webpack,在此之前,我刚学习 gulp(开源中国博客列表可见相关总结)。

我的学习方法比较剑走偏锋。既然看完了知识点,直接开干。

  • 大概花了四天时间看了 nodejs 的基础知识,我写了一个 demo:ejs-express-mysql
  • webpack 学习完阮一峰的 demo 后我也开始百度、Google,完成一个自己项目的配置:neal-teach-website
  • redux 我是通宵学习了一个周末,并且在周一写了一些 demo、写了相关感悟study-redux
  • 然后开始将react 技术栈串联起来的时候,发现了 redux-saga 要学习,并且整体项目结构非常的乱。于是乎,我又开源了一个 demo:React-Fullstack-Dianping-Demo ,这是一个朋友分享给我的慕课网教学视频,但是说实话,通篇看完,觉得老师讲解的不是很对口,遂自己写了一个开源出来。
  • 通篇学习完后,又写了一个总结性的Demo,也就是 github 上目前个人仓库下最高 star :React-Express-Blog-Demo

以上这些只是我个人学习 react 的时候,并且所有的学习都有相关产出、所有的 demo 都在 github 可见。同时在工作中,也有在使用和学习。

回头看看,我一直在冒充着大神,其实开源出来的时候,自己也在学习,自己也没有完全掌握。因为我感觉如果都是写一些自己会的,那简直是太浪费时间了。

除了 react 以外。在这个阶段,我 啃完了所有 读了很多 JavaScript 经典书籍、红宝书、犀牛书(看了 60%)、ES6、高性能 js、你不知道的 js 系列、忍者秘籍等等,并且感悟深的都有在各个平台上留下相关笔记。

在这个阶段,你有太多需要学习的了,任何你不知道的,你都应该知道!不要等工作、业务上来给你知识盲区扫描。自己主动找自己的技术方向。有目的、有结果性的学习~

掌握阶段

其实就我个人感觉,我应该属于第二阶段往第三阶段过渡的一个阶段,所以这里不能给出我个人的总结了。说下这个阶段,我自己的个人规划供大家参考吧。

这个阶段的我,已经进入到了自己心仪的公司。并且身边的大牛几乎是每天都能给到自己压力。所以学习。。。依旧是我最为核心的目标。但是同时!业务的理解和掌握,也是我这个阶段要去提升和重视的一点。

这个阶段,我需要做的很多。说一下对自己的期望

工作上

  • 带有业务思考的去编写每一行代码。对于代码规范、组件的封装、整体架构的搭建需要进一步的去思考、学习。
  • 明白Bu的核心利益是什么,你对Bu 的贡献点、以及如何利用好自己的技术来反哺业务。
  • 多从业务上去寻找技术的突破点。从技术的突破点去寻找自己的方向。
  • 从前端团队的角度去思考如何解放前端脑动力。时刻保持敏锐的嗅觉去思考团队的开发流程、技术痛点等,并努力寻求解决办法。

学习上

  • 学习基于业务。但是依旧要明确自己的未来领域。
  • 多做技术分享,多和大牛接触、以提高自己的技术视野和未来前端方向的嗅觉
  • 再重温一次前端,多些总结性文章。
  • 对于前端领域现有知识,不要求能够面面俱到、但是能够做到提纲挈领
  • 保持一颗有空就学习的心
  • 提高自己非技术以外的软实力(作图、架构思考、做 PPT 等)
  • 个人品牌影响力的打造(不得不说,github 帮我敲开的阿里大门)

总结

总结如上所说,其实我没有走任何捷径。只不过

  • 学习东西果断、坚持。并且一定会有产出(博客、github)
  • 不怕遇到问题,甚至主动找别人遇到的的问题,然后自己帮忙解答(技术交流群)
  • 学习新东西只是浏览一遍官网介绍和 api,然后直接上手写 demo、不会再去查!
  • 多浏览技术论坛、博客。常备梯子你懂得。多和大牛接触,交流(但是注意:没有大牛是闲着的)
  • 一定要写!写!写!不要只会看!读!
  • 技术不能脱离业务,多去思考业务痛点、团队工作流痛点、技术突破点。
  • 提高自己的技术思考能力,不仅仅要学习,更要学会去创新、去思考 why。

最后,我想说,其实我也依旧还有很多需要学习的地方。此篇文章,是对一直以来支持我的哥们一些疑惑的解答。因为我的确给不了最为有效的学习方法和建议,所以只能简述自己的情况提供参考。如若说的不对的地方,还望见谅。

勿忘初心!狂而不傲 peace~

福利

微信公众号内回复:【简历】 获取笔者面试大厂大门的敲门砖简历

下一篇我将介绍:阿里一面,我是如何面试 p6、p7 的(面试题以及打分分析)

查看原文

赞 31 收藏 21 评论 4

W3Fun 收藏了文章 · 2019-07-31

前端必看的数据可视化入门指南

作者:董晓庆 蚂蚁金服体验技术部

这是一篇给大家提供数据可视化开发的入门指南,介绍了可视化要解决的问题和可以直接使用的工具,我将从下面几个方面给大家介绍,同时以阿里/蚂蚁的可视化团队和资源举例说明:

  • 什么是数据可视化?
  • 怎样进行数据可视化?
  • 数据可视化的场景和工具
  • 数据可视化过程中常见的问题

什么是数据可视化

数据可视化研究的是,如何将数据转化成为交互的图形或图像等,以视觉可以感受的方式表达,增强人的认知能力,达到发现、解释、分析、探索、决策和学习的目的。

数据可视化(Data Visualization)和信息可视化(Infographics)是两个相近的专业领域名词。狭义上的数据可视化指的是数据用统计图表方式呈现,而信息可视化则是将非数字的信息进行可视化。前者用于传递信息,后者用于表现抽象或复杂的概念、技术和信息。而广义上的数据可视化则是数据可视化、信息可视化以及科学可视化等等多个领域的统称。
——《数据可视化之美

广义的数据可视化涉及信息技术、自然科学、统计分析、图形学、交互、地理信息等多种学科。

1531829243122-1c625a8c-2718-48b2-b506-588a2708a166.png

科学可视化(Scientific Visualization)、信息可视化(Information Visualization)和可视分析学(Visual Analytics)三个学科方向通常被看成可视化的三个主要分支。这三个分支整合在一起形成的新学科“数据可视化”,是可视化研究领域的新起点。
——《数据可视化

下面我们对科学可视化、信息可视化和可视分析学做简单的介绍:

科学可视化

科学可视化(Scientific Visualization)是可视化领域最早、最成熟的一个跨学科研究与应用领域。面向的领域主要是自然科学,如物理、化学、气象气候、航空航天、医学、生物学等各个学科,这些学科通常需要对数据和模型进行解释、操作与处理,旨在寻找其中的模式、特点、关系以及异常情况。

图片描述

信息可视化

信息可视化(Information Visualization)处理的对象是抽象数据集合,起源于统计图形学,又与信息图形、视觉设计等现代技术相关。其表现形式通常在二维空间,因此关键问题是在有限的展现空间中以直观的方式传达大量的抽象信息。与科学可视化相比,科学可视化处理的数据具有天然几何结构(如磁感线、流体分布等),信息可视化更关注抽象、高维数据。柱状图、趋势图、流程图、树状图等,都属于信息可视化最常用的可视表达,这些图形的设计都将抽象的数据概念转化成为可视化信息。

图片描述

可视分析学

可视分析学(Visual Analytics)被定义为一门以可视交互为基础的分析推理科学。它综合了图形学、数据挖掘和人机交互等技术,以可视交互界面为通道,将人感知和认知能力以可视的方式融入数据处理过程,形成人脑智能和机器智能优势互补和相互提升,建立螺旋式信息交流与知识提炼途径,完成有效的分析推理和决策。

图片描述

科学可视化、信息可视化与可视分析学,这三者有一些重叠的目标和技术,这些领域之间的边界尚未有明确的共识。

数据可视化的目标

数据可视化的本质是将数据通过各种视觉通道映射成图形,可以使得用户更快、更准确的理解数据。因此数据可视化要解决的问题是如何将数据通过视觉可观测的方式表达出来,同时需要考虑美观、可理解性,需要解决在展示的空间(画布)有限的情况下覆盖、杂乱、冲突等问题,再以交互的形式查看数据的细节。

怎样进行数据可视化

用一张经典的图来说明如何进行数据可视化:

图片描述

数据可视化过程可以分为下面几个步骤:

  1. 定义要解决问题
  2. 确定要展示的数据和数据结构
  3. 确定要展示的数据的维度(字段)
  4. 确定使用的图表类型
  5. 确定图表的交互

定义问题

首先明确数据可视化是要让用户看懂数据,理解数据。所以开始数据可视化前一定要定义通要解决的问题。例如:我想看过去两周销售额的变化,是增长了还是下跌了,什么原因导致的?
你可以从 趋势、对比、分布、流程、时序、空间、关联性等角度来定义自己要解决的问题。

确定要展示的数据

进行数据可视化首先要有数据,由于画布大小的限制,过量的数据不能够在直接显示出来,所以要确定展示的数据:

  • 我要展示的数据是否已经加工好,是否存在空值?
  • 是列表数据还是树形数据?
  • 数据的规模有多大?
  • 是否要对数据进行聚合,是否要分层展示数据?
  • 如何加载到页面,是否需要在前端对数据处理?

确定要显示的数据维度

进行可视化时要对字段进行选择,选择不同的字段在后面环节中选择适合的图表类型也不同。

图片描述

确定使用的图表类型

有非常多的图表类型可以使用,但是要根据要解决的问题、数据的结构、选择的数据维度来确定要显示的图表类型:

图片描述

如何选择图表类型可以参考:

数据可视化的场景和工具

目前互联网公司通常有这么几大类的可视化需求:

  • 通用报表
  • 移动端图表
  • 大屏可视化
  • 图编辑&图分析
  • 地理可视化

通用报表需求

开发过程中面临的 85% 以上的需求都是通用报表的需求,可以使用一般的图表库来满足日常的开发需求,行业内比较常用的图表库有:Highcharts、Echarts、amCharts 等,AntV 开源了基于图形语法的图表库:G2

G2 具备以下特性:

  • 千变万化、自由组合。从数据出发,仅需几行代码就可以轻松获得想要的图表展示效果
  • 生动、易实现。大量产品实践之上,提供绘图引擎、完备图形语法、专业设计规范
  • 丰富的交互能力。在图形语法的基础上提供了自定义交互的能力

目前阿里集团内部已经有大量基于 G2 封装的图表库,针对特定的框架和业务场景进行了封装,其中部分已开源:

  • bizcharts 阿里巴巴国际 UED 团队出品,G2 的 react 封装,主打电商业务图表可视化,沉淀电商业务线的可视化规范。在 React 项目中实现常见图表和自定义图表。
  • viser 阿里数据平台技术部出品,支持 vue, react,angularjs 三个框架。

移动端可视化

如果你面临的场景需要 PC 端和移动端都兼容那么使用 G2 然后适配移动端的屏幕即可,但是如果你在移动 APP 上使用 H5 或者小程序开发,那么就选择 F2

F2是一个专注于移动,开箱即用的可视化解决方案,完美支持 H5 环境同时兼容多种环境(node, 小程序,weex)。完备的图形语法理论,满足你的各种可视化需求。专业的移动设计指引为你带来最佳的移动端图表体验。

图片描述

F2 对多个平台提供支持,阿里集团的其他团队也做了一些封装,比如my-f2,这是针对小程序封装的版本,目前已开源:

https://github.com/antvis/my-f2

大屏可视化

大屏可视化聚焦于会议展览、业务监控、风险预警、地理信息分析等多种业务的展示,在图形渲染、可视化设计方面都有很高的要求,目前阿里集团内部的大屏可视化团队包括:

  • 蚂蚁金服的图形与艺术实验室
  • 阿里云的 DataV 团队
  • 阿里数据技术及产品部-数据之美

DataV的大屏

大屏目前几乎已成为to B项目的标配,应用场景越来越广泛。

图编辑 & 图分析

图可视化主要有两个大的领域:

  • 图编辑:用于图建模(ER图、UML图)、流程图、脑图等,需要用户深入参与关系的创建、编辑和删除的场景
  • 图分析:用于风控、安全、营销场景中的关系发现,对图的一些基本概念进行业务上解读,环、关键链路、连通量等。

目前主流的开源框架有:

  • jointjs 聚焦于图编辑,包含了常见的流程图和BPMN 图的功能,上手容易,开箱即用但是二次开发非常困难。
  • d3.js 非常底层的可视化库,有大量图分析场景的案例,上手成本高,demo 同业务的距离比较大。

目前 AntV 在图可视化方向开源了 图基础框架 G6

主要完成以下功能:

  • 节点和边的渲染,包括自定义节点和边
  • 事件交互机制,内嵌了大量常见的交互
  • 常见的布局,包括树布局和力导布局

图片描述

在 G6 的上层我们还针对图编辑和图分析提供了 G6-Analyzer 和 G6-Editor.

地理可视化

地理数据可视化主要是对空间数据域的可视化,主要有三大领域:

  • 信息图:主要用于展示位置相关的报表,信息图,路径变化等等。
  • 大屏应用:大屏展示一般以地理数据为载体,如建筑,道路,轨迹等数据可视化。
  • 地理分析应用:这类应用往往是海量地理数据的交互分析,用户基于位置的用户推荐,拉新,促活等业务运营系统,或者选址,风险监控等系统。

AntV G2 和 L7 都提供了地理数据可视化的方案,其中:

  • G2 提供通用地理数据图表的支持。
  • L7 是更加专业的地理数据可视化解决方案,采用WebGL渲染技术,支持海量地理数据可视化分析,支持多线程运算的矢量瓦片方案。能够满足大屏可视化地理分析应用的需求。

图片描述

阿里集团的其他地理可视化框架包括:

  • 高德的 Loca 
  • 菜鸟的 鸟图 

常见的问题

图表误用

图表的误用是最常见的问题,看下下面的一些场景:

例子1:分类过多的场景。下图是各个省的人口的占比情况,因为这张图上包含的分类过多,就出现了简介中提到的问题,很难清晰对比各个省份的人口数据占比情况,所以这种情况下,我们推荐使用横向柱状图。

图片描述

例子2:我们以一个不同游戏类型的销量对比的场景为例,对于表示分类对比的数据时,我们更应该使用柱状图,而不是折线图。

图片描述

移动端和PC端图表

AntV 提供了 G2 和 F2 两个统计图表框架,用户经常会面同时临移动端和 PC 端的业务,这时候会面临两个框架的选择问题。G2 本质上是为了传统的中后台产品设计的图表库,除了一般的报表显示外,还提供了大量的交互有很强的分析能力;而 F2 则专门为移动端开发,最关注的是代码大小、性能、表现力。

所以我们有以下建议:

  • 如果你的用户主要来自 PC 端,那么请使用 G2 ,G2 能支持更多的图表类型和交互。
  • 如果你在钱包等重型 app 上使用 H5、小程序开发,请使用 F2。我们对移动端的众多平台做了大量兼容性工作。
  • 如果你开发的是一个 BI 分析系统,除了报表功能外还需要一定的分析能力请使用 G2。
  • 如果你在开发监控等需要联动系统,主要的用户来自 PC 端则使用 G2。
  • 如果你开发的是报表系统,主要的用户通过移动端来看图表,那么请使用 F2(PC 端也可以看)。

数据量太大怎么办

我们在前端做的可视化,能做的仅仅是小规模数据的可视化,如果你遇到超大规模数据要进行可视化,那么可以选择:

  • 数据分层
  • 数据聚合
  • 数据抽样

总结

这是一篇可视化的入门文章,介绍了可视化要解决的问题和可以直接使用的工具,如果你对可视化感兴趣可以关注墨者学院

查看原文

W3Fun 赞了文章 · 2019-04-07

浏览器将标签转成 DOM 的过程

个人专栏 ES6 深入浅出已上线,深入ES6 ,通过案例学习掌握 ES6 中新特性一些使用技巧及原理,持续更新中,←点击可订阅。

点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。


浏览器基本的工作流程

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

进入主话题之前,先罗列一下浏览器的主要构成:

  1. 用户界面- 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分
  2. 浏览器引擎- 用来查询及操作渲染引擎的接口
  3. 渲染引擎- 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来
  4. 网络- 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作
  5. UI 后端- 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口
  6. JS解释器- 用来解释执行JS代码
  7. 数据存储- 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术

clipboard.png

解析

当浏览器获得了资源以后要进行的第一步工作就是 HTML 解析,,它由几个步骤组成:编码、预解析、标记和构建树。

编码

HTTP 响应主体的有效负载可以是从HTML文本到图像数据的任何内容。解析器的第一项工作是找出如何转制刚刚从服务器接收到的 bit

假设我们正在处理一个HTML文档,解码器必须弄清楚文本文档是如何被转换成比特(bit)的,以便反转这个过程。

clipboard.png

记住,最终即使是文本也会被计算机翻译成二进制,如上图所示,在本例中是 ASCII 编码—定义二进制值,如“01000100”表示字母“D”。

对于文本存在许多可能的编码—浏览器的工作是找出如何正确地解码文本。服务器应该通过 Content-Type 提供的信息同时在文本文件头部使用 Byte Order Mark 告知浏览器编码格式。

如果仍然无法确定编码,浏览器还会自行匹配一种解码格式来处理数据。有时候,解码格式也会写在 <meta> 标签中。

最坏的情况是,浏览器进行了有根据的猜测,然后开始解析之后发现一个矛盾的 <meta> 标签。在这些罕见的情况下,解析器必须重新启动,丢弃之前解码的内容。浏览器有时必须处理旧的 web内容(使用遗留编码),许多这样的系统都支持这一点。

我们现在经常在 HTML中使用的文件格式是 UTF-8,那是因为 UTF-8 能较完整的支持Unicode 字符范围,同时与 CSS、JavaScript 中常见的节字符具有良好的 ASCII 兼容性。一般浏览器默认的解码格式也是 UTF-8。当解码出错的时候,我们会看到屏幕上全部都是乱码字符。

预解析

在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。

预解析器不是完整的解析器,如,它不理解 HTML 中的嵌套级别或父/子关系。但是,预解析可以识别特定的 HTML 标签的名称和属性,以及 URL。例如,如果你的 HTML 内容中有一个<img data-original="https://somewhere.example.com/​images/​dog.png" alt=""> ,预解析将注意到src属性,并将获取这个图片的请求加到请求队列中。

请求图片的速度越快越好,将等待它从网络到达的时间降到最低。预解析还会注意到 HTML 中的某些显式请求,比如 preloadprefetch 指令,并将它们加入等待队友中进行处理。

标记化(Tokenization)

该算法的输出结果是 HTML 标记。该算法使用状态机来表示。每一个状态接收来自输入信息流的一个或多个字符,并根据这些字符更新下一个状态。当前的标记化状态和树结构状态会影响进入下一状态的决定。这意味着,即使接收的字符相同,对于下一个正确的状态也会产生不同的结果,具体取决于当前的状态。该算法相当复杂,无法在此详述,所以我们通过一个简单的示例来帮助大家理解其原理。

基本示例 - 将下面的 HTML 代码标记化:

<html>
  <body>
    Hello world
  </body>
</html>

初始状态是数据状态。遇到字符 < 时,状态更改为“标记打开状态”。接收一个 a-z 字符会创建“起始标记”,状态更改为“标记名称状态”。这个状态会一直保持到接收 > 字符。在此期间接收的每个字符都会附加到新的标记名称上。在本例中,我们创建的标记是 html 标记。

遇到 > 标记时,会发送当前的标记,状态改回“数据状态”。<body> 标记也会进行同样的处理。目前 html 和 body 标记均已发出。现在我们回到“数据状态”。接收到 Hello world 中的 H 字符时,将创建并发送字符标记,直到接收 </body> 中的 <。我们将为 Hello world 中的每个字符都发送一个字符标记。

现在我们回到“标记打开状态”。接收下一个输入字符 / 时,会创建 end tag token 并改为“标记名称状态”。我们会再次保持这个状态,直到接收 >。然后将发送新的标记,并回到“数据状态”。</html> 输入也会进行同样的处理。

图片描述

构建树(tree construction)

在创建解析器的同时,也会创建 Document 对象。在树构建阶段,以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。标记生成器发送的每个节点都会由树构建器进行处理。规范中定义了每个标记所对应的 DOM 元素,这些元素会在接收到相应的标记时创建。这些元素不仅会添加到 DOM 树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。其算法也可以用状态机来描述。这些状态称为“插入模式”

在上一步符号化以后,解析器获得这些标记,然后以合适的方法创建 DOM 对象并将这些符号插入到 DOM 对象中。DOM 对象的数据结构是树状的,所以这个过程称为构造树(tree construction)。另外,在 IE 的历史中,大部分时间里没有使用树结构。

图片描述

在创建解析器的同时,也会创建 Document 对象。在树构建阶段,以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。标记生成器发送的每个节点都会由树构建器进行处理。

规范中定义了每个标记所对应的 DOM 元素,这些元素会在接收到相应的标记时创建。这些元素不仅会添加到 DOM 树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。其算法也可以用状态机来描述。这些状态称为“插入模式”。

例如,考虑这个 HTML:

<p>sincerely<p>The authors</p>

这样可以确保结果树中的两个段落对象是兄弟节点,而忽略第二个打开的标签则与一个段落对象相对。 HTML表可能是解析器规则试图确保表具有适当结构的最复杂的表。

尽管存在所有复杂的解析规则,但是一旦创建了 DOM 树,所有试图创建正确 HTML 结构的解析规则就不再强制执行了。

使用 JavaScript,网页可以几乎以任何方式重新排列 DOM 树,即使它没有意义,例如,添加表格单元格作为 <video> 标签的子项,渲染系统负责弄清楚如何处理任何前后不一致标签。

HTML 解析中的另一个复杂因素是 JavaScript 可以在解析器执行其工作时添加更多要解析的内容。<script> 标签包含解析器必须收集的文本,然后发送到脚本引擎进行评估。 当脚本引擎解析并评估脚本文本时,解析器会等待。如果JavaScript文件内调用了 document.writeAPI,解析器将重新开始解析过程。

事件(Events)

当解析器完成时,它通过一个名为 DOMContentLoaded 的事件宣布完成。事件是内置在浏览器中的广播系统,JavaScript可以侦听和响应它。除了 DOMContentLoaded 事件,还有load 事件(表示所有资源已经加载完成,包括图片、视频、CSS等等)、unload 事件表示界面即将关闭、鼠标事件键盘事件等等。

浏览器在 DOM 中创建一个事件对象,并将其打包成有用的状态信息(例如屏幕上触摸的位置、按下的按键等等),当JavaScript触发事件的时候,就会同时产生事件对象。

DOM 的树结构通过允许在树的任何级别监听事件(如在树根、树叶或两者之间的任何地方)。在目标元素上触发事件的时候,需要 从DOM 树的根元素开始向子元素查找,这个过程俗称事件捕捉阶段。到达目标元素以后,还要逐级向上返回到根元素上,这个过程俗称事件冒泡阶段

图片描述

还可以取消一些事件,例如,如果表单没有正确填写,则可以停止表单提交。(提交事件是从<form> 元素触发的,JavaScript 侦听器可以检查表单,如果字段为空或无效,还可以选择取消事件。)

DOM

HTML语言提供了丰富的特性集,远远超出了解析器处理的标记。解析器构建一个结构,其中的元素包含其他元素,以及这些元素最初具有什么状态(它们的属性)。结构和状态的组合足以提供基本渲染和一些交互(例如通过内置控件,如<textarea>,<video>,<button>等)。 但是如果不添加 CSS 和 JavaScript,网络将非常枯燥(和静态)。 DOM 为 HTML 元素和与 HTML 无关的其他对象提供了额外的功能层。

元素接口

在解析器将元素放入DOM树之前,解析器会根据不同元素的名称赋予元素不同的接口功能。些通用特性包括:

  • 访问代表元素子元素的全部或子集的 HTML 集合
  • 能够查找元素的属性、子元素和父元素
  • 重要的是,创建新元素的方法(不使用解析器),并将它们附加到树中(或将它们从树中分离出来)

对于像 <table> 这样的特殊元素,该接口包含用于查找表中所有行,列和单元格的其他特定于表的功能,以及用于从表中删除和添加行和单元格的快捷方式。 同样,<canvas> 接口具有绘制线条,形状,文本和图像的功能。 使用这些 API 需要 JavaScript 仅仅使用 HTML 标签是不够的。

每当我们使用 JavaScript 操作 DOM 的时候,将会触发浏览器的一些连锁反应,这些反应是为了让更改后的页面更快的渲染在屏幕上。例如:

  • 用数字代表通用的元素名称和属性,浏览器用使用哈希表进行快速识别这些数字
  • 将频繁变更的子元素进行缓存,方便子元素快速迭代
  • sub-tree 的跟踪变化降到最低,避免‘污染’整个 DOM 树

其他API

DOM中的HTML元素及其接口是浏览器在屏幕上显示内容的唯一机制。CSS可以影响布局,但仅限于HTML元素中存在的内容。最终,如果你想在屏幕上看到内容,它必须通过作为树的一部分的HTML接口来完成。

  1. 访问存储系统(数据库,key/value存储,网络缓存存储(network cache storage));
  2. 设备(各种类型的地理定位,距离和方向传感器,USB,MIDI,蓝牙,游戏手柄);
  3. 网络(HTTP交换,双向服务器套接字,实时媒体流);
  4. 图形(2D和3D图形基元,着色器,虚拟和增强现实);
  5. 和多线程(具有丰富消息传递功能的共享和专用执行环境)。

随着主要浏览器引擎开发和实施新的Web标准,DOM公开的功能不断增加。然而,DOM的这些“额外”API中的大多数都超出了本文的范围。

总结

希望这部分对你关于 DOM 解析过程多多少少有点帮助,共进步!

你的点赞是我持续分享好东西的动力,欢迎点赞!

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

查看原文

赞 41 收藏 32 评论 2

W3Fun 赞了文章 · 2019-02-20

VuePress 手摸手教你搭建一个类Vue文档风格的技术文档/博客

前言:

VuePress是尤大为了支持 Vue 及其子项目的文档需求而写的一个项目,VuePress界面十分简洁,并且非常容易上手,一个小时就可以将项目架构搭好。现在已经有很多这种类型的文档,如果你有写技术文档的项目的话,VuePress绝对可以成为你的备选项之一。

VuePress特性:

  • 为技术文档而优化的 内置 Markdown 拓展
  • 在 Markdown 文件中使用 Vue 组件的能力
  • Vue 驱动的自定义主题系统
  • 自动生成 Service Worker
  • Google Analytics 集成
  • 基于 Git 的 “最后更新时间”
  • 多语言支持
  • 默认主题包含:

建议先看一下官方文档

效果:

可能你会搭建出一个类似这样的文档


搭建:

全局安装VuePress

yarn global add vuepress # 或者:npm install -g vuepress

新建文件夹

可以手动右键新建,也可以使用下面的命令新建文件夹:

mkdir project

项目初始化

进入到project文件夹中,使用命令行初始化项目:

yarn init -y # 或者 npm init -y

将会创建一个package.json文件,长这样子:

{
  "name": "project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

在project的根目录下新建docs文件夹:

这个文档将作为项目文档的根目录来使用:

mkdir docs

在docs文件夹下创建.vuepress文件夹:

mkdir .vuepress

所有 VuePress 相关的文件都将会被放在这里

.vuepress文件夹下面创建config.js:

touch config.js

config.js是VuePress必要的配置文件,它导出一个javascript对象。

你可以先加入如下配置:

module.exports = {
  title: 'Hello VuePress',
  description: 'Just playing around'
}

.vuepress文件夹下面创建public文件夹:

mkdir public

这个文件夹是用来放置静态资源的,打包出来之后会放在.vuepress/dist/的根目录。

首页(像VuePress文档主页一样)

在docs文件夹下面创建一个README.md

默认的主题提供了一个首页,像下面一样设置home:true即可,可以把下面的设置放入README.md中,待会儿你将会看到跟VuePress一样的主页。

---
home: true
heroImage: /logo.jpg
actionText: 快速上手 →
actionLink: /zh/guide/
features:
- title: 简洁至上
  details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。
- title: Vue驱动
  details: 享受 Vue + webpack 的开发体验,在 Markdown 中使用 Vue 组件,同时可以使用 Vue 来开发自定义主题。
- title: 高性能
  details: VuePress 为每个页面预渲染生成静态的 HTML,同时在页面被加载的时候,将作为 SPA 运行。
footer: MIT Licensed | Copyright © 2018-present Evan You
---

ps:你需要放一张图片到public文件夹中。

我们的项目结构已经搭好了:

project
├─── docs
│   ├── README.md
│   └── .vuepress
│       ├── public
│       └── config.js
└── package.json

package.json 里添加两个启动命令:

{
  "scripts": {
    "docs:dev": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  }
}

启动你的VuePress:

默认是localhost:8080端口。

yarn docs:dev # 或者:npm run docs:dev

构建:

build生成静态的HTML文件,默认会在 .vuepress/dist 文件夹下

yarn docs:build # 或者:npm run docs:build

基本配置:

最标准的当然是官方文档,可以自己的需求来配置config.js

可以参考一下我的config.js的配置:

module.exports = {
  title: '网站标题',
  description: '网站描述',
  // 注入到当前页面的 HTML <head> 中的标签
  head: [
    ['link', { rel: 'icon', href: '/favicon.ico' }], // 增加一个自定义的 favicon(网页标签的图标)
  ],
  base: '/web_accumulate/', // 这是部署到github相关的配置 下面会讲
  markdown: {
    lineNumbers: true // 代码块显示行号
  },
  themeConfig: {
    sidebarDepth: 2, // e'b将同时提取markdown中h2 和 h3 标题,显示在侧边栏上。
    lastUpdated: 'Last Updated' // 文档更新时间:每个文件git最后提交的时间
  }
};

导航栏配置:

module.exports = {
  themeConfig: {
    nav:[
      { text: '前端算法', link: '/algorithm/' }, // 内部链接 以docs为根目录
      { text: '博客', link: 'http://obkoro1.com/' }, // 外部链接
      // 下拉列表
      {
        text: 'GitHub',
        items: [
          { text: 'GitHub地址', link: 'https://github.com/OBKoro1' },
          {
            text: '算法仓库',
            link: 'https://github.com/OBKoro1/Brush_algorithm'
          }
        ]
      }        
    ]
  }
}

侧边栏配置:

侧边栏的配置相对麻烦点,我里面都做了详细的注释,仔细看,自己鼓捣鼓捣 就知道怎么搞了。


module.exports = {
  themeConfig: {
      sidebar:{
        // docs文件夹下面的accumulate文件夹 文档中md文件 书写的位置(命名随意)
        '/accumulate/': [
            '/accumulate/', // accumulate文件夹的README.md 不是下拉框形式
            {
              title: '侧边栏下拉框的标题1',
              children: [
                '/accumulate/JS/test', // 以docs为根目录来查找文件 
                // 上面地址查找的是:docs>accumulate>JS>test.md 文件
                // 自动加.md 每个子选项的标题 是该md文件中的第一个h1/h2/h3标题
              ]
            }
          ],
          // docs文件夹下面的algorithm文件夹 这是第二组侧边栏 跟第一组侧边栏没关系
          '/algorithm/': [
            '/algorithm/', 
            {
              title: '第二组侧边栏下拉框的标题1',
              children: [
                '/algorithm/simple/test' 
              ]
            }
          ]
      }
  }
}

其他:

代码块编译错误:

像下面这段代码会导致编译错误,VuePress会去找里面的变量,把它编译成text:

{{}} 啦 {{}}

所以我们的代码块要以这种形式书写:

//```js
{{}} 啦 {{}} // 注释需要打开 这样vuepress会把这里面包裹的当成代码块而不是js
//```

并且这样也会让我们的代码高亮显示(下图第一个没有高亮,第二个有高亮),阅读体验更好:

自定义容器了解一下:

更改标题:

::: tip 替换tip的标题
这里是内容。
:::

其实文档里有,我这里只是提一下。

支持Emoji

文档中只提了支持Emoji,我在GitHub上找到了Emoji的列表,分享一下。

一个命令行发布到github上:

docs/.vuepress/config.js 中设置正确的 base:

如果你打算发布到 https://<USERNAME>.github.io/,则可以省略这一步,因为 base 默认即是 "/"

如果你打算发布到 https://<USERNAME>.github.io/<REPO>/(也就是说你的仓库在 https://github.com/<USERNAME>/<REPO>),则将 base 设置为 "/<REPO>/"

module.exports = {
  base: '/test/', // 比如你的仓库是test
}

创建脚步文件:

project的根目录下,创建一个deploy.sh文件:

#!/usr/bin/env sh

# 确保脚本抛出遇到的错误
set -e

# 生成静态文件
npm run docs:build

# 进入生成的文件夹
cd docs/.vuepress/dist

# 如果是发布到自定义域名
# echo 'www.example.com' > CNAME

git init
git add -A
git commit -m 'deploy'

# 如果发布到 https://<USERNAME>.github.io  USERNAME=你的用户名 
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git master

# 如果发布到 https://<USERNAME>.github.io/<REPO>  REPO=github上的项目
# git push -f git@github.com:<USERNAME>/<REPO>.git master:gh-pages

cd -

设置package.json:

{
"scripts": {

"d": "bash deploy.sh"

}
}

部署:

然后你每次可以运行下面的命令行,来把最新更改推到github上:

    npm run d

如果你对运行项目和构建项目的命令行觉得很烦,你也可以像我这么做:

"scripts": {
    "dev": "vuepress dev docs", // 本地运行项目 npm run dev
    "build": "vuepress build docs", // 构建项目 nom run build
    "d": "bash deploy.sh" // 部署项目 npm run d
  },


更多:

实际上VuePress的配置、用法还有很多,像还可以配置PWA,以及在markdown里面使用Vue组件等,这些功能我也还在摸索,所以大家一定要去看文档!

结语

上面已经写得尽可能详细了,我遇到的坑都写上去了。搭建起来确实很简单,心动不如行动,随手花一两个小时搭建一下又不吃亏,何乐而不为?

希望看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励。

个人blog and 前端积累文档,如需转载,请放上原文链接并署名。码字不易,感谢支持!

如果喜欢本文的话,欢迎扫描关注我的订阅号,最新文章,面试题等都将第一时间发布在订阅号上。

以上2018.9.9

查看原文

赞 78 收藏 54 评论 12

W3Fun 赞了回答 · 2019-02-08

解决win10下," No module named 'pip' "如何解决?

我和题主遇到了一样的问题

  • 使用压缩包解压的方式安装python
  • 完整配置了python的相关环境变量
  • 运行python get-pip.py安装pip
  • 使用pip得到报错" No module named 'pip' "

经过观察,运行python get-pip.py后python的安装目录(对于题主来说是"D:\z_tools\python-3.7.0")多了文件夹Lib\site-packages,尝试将其中的pip目录复制到python的安装目录,再次运行pip命令,报错消失。

撤销复制,修改python安装目录下的python3x._pth文件,新添加一行Lib\site-packages

再次运行pip命令,一切正常,问题解决。

关注 7 回答 6

W3Fun 赞了文章 · 2019-01-12

如何对前端图片主题色进行提取?这篇文章详细告诉你

本文由云+社区发表

图片主题色在图片所占比例较大的页面中,能够配合图片起到很好视觉效果,给人一种和谐、一致的感觉。同时也可用在图像分类,搜索识别等方面。通常主题色的提取都是在后端完成的,前端将需要处理的图片以链接或id的形式提供给后端,后端通过运行相应的算法来提取出主题色后,再返回相应的结果。

这样可以满足大多数展示类的场景,但对于需要根据用户“定制”、“生成”的图片,这样的方式就有了一个上传图片---->后端计算---->返回结果的时间,等待时间也许就比较长了。由此,我尝试着利用 canvas在前端进行图片主题色的提取。

一、主题色算法

目前比较常用的主题色提取算法有:最小差值法、中位切分法、八叉树算法、聚类、色彩建模法等。其中聚类和色彩建模法需要对提取函数和样本、特征变量等进行调参和回归计算,用到 python的数值计算库 numpy和机器学习库 scikit-learn,用 python来实现相对比较简单,而目前这两种都没有成熟的js库,并且js本身也不擅长回归计算这种比较复杂的计算。我也就没有深入的研究,而主要将目光放在了前面的几个颜色量化算法上。

而最小差值法是在给定给定调色板的情况下找到与色差最小的颜色,使用的场景比较小,所以我主要看了中位切分法和八叉树算法,并进行了实践。

中位切分法

中位切分法通常是在图像处理中降低图像位元深度的算法,可用来将高位的图转换位低位的图,如将24bit的图转换为8bit的图。我们也可以用来提取图片的主题色,其原理是是将图像每个像素颜色看作是以R、G、B为坐标轴的一个三维空间中的点,由于三个颜色的取值范围为0~255,所以图像中的颜色都分布在这个颜色立方体内,如下图所示。

img

之后将RGB中最长的一边从颜色统计的中位数一切为二,使得到的两个长方体所包含的像素数量相同,如下图所示

img

重复这个过程直到切出长方体数量等于主题色数量为止,最后取每个长方体的中点即可。

img

在实际使用中如果只是按照中点进行切割,会出现有些长方体的体积很大但是像素数量很少的情况。解决的办法是在切割前对长方体进行优先级排序,排序的系数为体积 * 像素数。这样就可以基本解决此类问题了。

八叉树算法

八叉树算法也是在颜色量化中比较常见的,主要思路是将R、G、B通道的数值做二进制转换后逐行放下,可得到八列数字。如 #FF7880转换后为

R: 1111 1111
G: 0111 1000
B: 0000 0000

再将RGB通道逐列粘合,可以得到8个数字,即为该颜色在八叉树中的位置,如图。

img

在将所有颜色插入之后,再进行合并运算,直到得到所需要的颜色数量为止。

在实际操作中,由于需要对图像像素进行遍历后插入八叉树中,并且插入过程有较多的递归操作,所以比中位切分法要消耗更长的时间。

二、中位切分法实践

根据之前的介绍和网上的相关资料,此处贴上我自己理解实现的中位切分法代码,并且找了几张图片将结果与QQ音乐已有的魔法色相关算法进行比较,图一为中位切分法结果,图二为后台cgi返回结果

图一

img

图二

img

img

可以看到有一定的差异,但是差值相对都还比较小的,处理速度在pc上面还是比较快的,三张图分别在70ms,100ms,130ms左右。这里贴上代码,待后续批量处理进行对比之后再分析。

(function () {

    /**
     * 颜色盒子类
     *
     * @param {Array} colorRange    [[rMin, rMax],[gMin, gMax], [bMin, bMax]] 颜色范围
     * @param {any} total   像素总数, imageData / 4
     * @param {any} data    像素数据集合
     */
    function ColorBox(colorRange, total, data) {
        this.colorRange = colorRange;
        this.total = total;
        this.data = data;
        this.volume = (colorRange[0][1] - colorRange[0][0]) * (colorRange[1][1] - colorRange[1][0]) * (colorRange[2][1] - colorRange[2][0]);
        this.rank = this.total * (this.volume);
    }

    ColorBox.prototype.getColor = function () {
        var total = this.total;
        var data = this.data;

        var redCount = 0,
            greenCount = 0,
            blueCount = 0;

        for (var i = 0; i < total; i++) {
            redCount += data[i * 4];
            greenCount += data[i * 4 + 1];
            blueCount += data[i * 4 + 2];
        }

        return [parseInt(redCount / total), parseInt(greenCount / total), parseInt(blueCount / total)];
    }

    // 获取切割边
    function getCutSide(colorRange) {   // r:0,g:1,b:2
        var arr = [];
        for (var i = 0; i < 3; i++) {
            arr.push(colorRange[i][1] - colorRange[i][0]);
        }
        return arr.indexOf(Math.max(arr[0], arr[1], arr[2]));
    }

    // 切割颜色范围
    function cutRange(colorRange, colorSide, cutValue) {
        var arr1 = [];
        var arr2 = [];
        colorRange.forEach(function (item) {
            arr1.push(item.slice());
            arr2.push(item.slice());
        })
        arr1[colorSide][1] = cutValue;
        arr2[colorSide][0] = cutValue;
        return [arr1, arr2];
    }

    // 找到出现次数为中位数的颜色
    function getMedianColor(colorCountMap, total) {
        var arr = [];
        for (var key in colorCountMap) {
            arr.push({
                color: parseInt(key),
                count: colorCountMap[key]
            })
        }

        var sortArr = __quickSort(arr);
        var medianCount = 0;
        var medianColor = 0;
        var medianIndex = Math.floor(sortArr.length / 2)

        for (var i = 0; i <= medianIndex; i++) {
            medianCount += sortArr[i].count;
        }

        return {
            color: parseInt(sortArr[medianIndex].color),
            count: medianCount
        }

        // 另一种切割颜色判断方法,根据数量和差值的乘积进行判断,自己试验后发现效果不如中位数方法,但是少了排序,性能应该有所提高
        // var count = 0;
        // var colorMin = arr[0].color;
        // var colorMax = arr[arr.length - 1].color
        // for (var i = 0; i < arr.length; i++) {
        //     count += arr[i].count;

        //     var item = arr[i];

        //     if (count * (item.color - colorMin) > (total - count) * (colorMax - item.color)) {
        //         return {
        //             color: item.color,
        //             count: count
        //         }
        //     }
        // }

        return {
            color: colorMax,
            count: count
        }



        function __quickSort(arr) {
            if (arr.length <= 1) {
                return arr;
            }
            var pivotIndex = Math.floor(arr.length / 2),
                pivot = arr.splice(pivotIndex, 1)[0];

            var left = [],
                right = [];
            for (var i = 0; i < arr.length; i++) {
                if (arr[i].count <= pivot.count) {
                    left.push(arr[i]);
                }
                else {
                    right.push(arr[i]);
                }
            }
            return __quickSort(left).concat([pivot], __quickSort(right));
        }
    }

    // 切割颜色盒子
    function cutBox(colorBox) {
        var colorRange = colorBox.colorRange,
            cutSide = getCutSide(colorRange),
            colorCountMap = {},
            total = colorBox.total,
            data = colorBox.data;

        // 统计出各个值的数量
        for (var i = 0; i < total; i++) {
            var color = data[i * 4 + cutSide];

            if (colorCountMap[color]) {
                colorCountMap[color] += 1;
            }
            else {
                colorCountMap[color] = 1;
            }
        }
        var medianColor = getMedianColor(colorCountMap, total);
        var cutValue = medianColor.color;
        var cutCount = medianColor.count;
        var newRange = cutRange(colorRange, cutSide, cutValue);
        var box1 = new ColorBox(newRange[0], cutCount, data.slice(0, cutCount * 4)),
            box2 = new ColorBox(newRange[1], total - cutCount, data.slice(cutCount * 4))
        return [box1, box2];
    }

    // 队列切割
    function queueCut(queue, num) {

        while (queue.length < num) {

            queue.sort(function (a, b) {
                return a.rank - b.rank
            });
            var colorBox = queue.pop();
            var result = cutBox(colorBox);
            queue = queue.concat(result);
        }

        return queue.slice(0, 8)
    }

    function themeColor(img, callback) {

        var canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d'),
            width = 0,
            height = 0,
            imageData = null,
            length = 0,
            blockSize = 1,
            cubeArr = [];

        width = canvas.width = img.width;
        height = canvas.height = img.height;

        ctx.drawImage(img, 0, 0, width, height);

        imageData = ctx.getImageData(0, 0, width, height).data;

        var total = imageData.length / 4;

        var rMin = 255,
            rMax = 0,
            gMin = 255,
            gMax = 0,
            bMin = 255,
            bMax = 0;

        // 获取范围
        for (var i = 0; i < total; i++) {
            var red = imageData[i * 4],
                green = imageData[i * 4 + 1],
                blue = imageData[i * 4 + 2];

            if (red < rMin) {
                rMin = red;
            }

            if (red > rMax) {
                rMax = red;
            }

            if (green < gMin) {
                gMin = green;
            }

            if (green > gMax) {
                gMax = green;
            }

            if (blue < bMin) {
                bMin = blue;
            }

            if (blue > bMax) {
                bMax = blue;
            }
        }

        var colorRange = [[rMin, rMax], [gMin, gMax], [bMin, bMax]];
        var colorBox = new ColorBox(colorRange, total, imageData);

        var colorBoxArr = queueCut([colorBox], 8);

        var colorArr = [];
        for (var j = 0; j < colorBoxArr.length; j++) {
            colorBoxArr[j].total && colorArr.push(colorBoxArr[j].getColor())
        }

        callback(colorArr);
    }

    window.themeColor = themeColor

})()

三、八叉树算法实践

也许是我算法实现的问题,使用八叉树算法得到的最终结果并不理想,所消耗的时间相对于中位切分法也长了不少,平均时间分别为160ms,250ms,400ms还是主要看八叉树算法吧...同样贴上代码

img

(function () {

    var OctreeNode = function () {
        this.isLeaf = false;
        this.pixelCount = 0;
        this.red = 0;
        this.green = 0;
        this.blue = 0;
        this.children = [null, null, null, null, null, null, null, null];
        this.next = null;
    }

    var root = null,
        leafNum = 0,
        colorMap = null,
        reducible = null;

    function createNode(index, level) {
        var node = new OctreeNode();
        if (level === 7) {
            node.isLeaf = true;
            leafNum++;
        } else {
            // 将其丢到第 level 层的 reducible 链表中
            node.next = reducible[level];
            reducible[level] = node;
        }

        return node;
    }

    function addColor(node, color, level) {
        if (node.isLeaf) {
            node.pixelCount += 1;
            node.red += color.r;
            node.green += color.g;
            node.bllue += color.b;
        }
        else {
            var str = "";
            var r = color.r.toString(2);
            var g = color.g.toString(2);
            var b = color.b.toString(2);
            while (r.length < 8) r = '0' + r;
            while (g.length < 8) g = '0' + g;
            while (b.length < 8) b = '0' + b;

            str += r[level];
            str += g[level];
            str += b[level];

            var index = parseInt(str, 2);

            if (null === node.children[index]) {
                node.children[index] = createNode(index, level + 1);
            }

            if (undefined === node.children[index]) {
                console.log(index, level, color.r.toString(2));
            }

            addColor(node.children[index], color, level + 1);
        }
    }

    function reduceTree() {

        // 找到最深层次的并且有可合并节点的链表
        var level = 6;
        while (null == reducible[level]) {
            level -= 1;
        }

        // 取出链表头并将其从链表中移除
        var node = reducible[level];
        reducible[level] = node.next;

        // 合并子节点
        var r = 0;
        var g = 0;
        var b = 0;
        var count = 0;
        for (var i = 0; i < 8; i++) {
            if (null === node.children[i]) continue;
            r += node.children[i].red;
            g += node.children[i].green;
            b += node.children[i].blue;
            count += node.children[i].pixelCount;
            leafNum--;
        }

        // 赋值
        node.isLeaf = true;
        node.red = r;
        node.green = g;
        node.blue = b;
        node.pixelCount = count;
        leafNum++;
    }

    function buidOctree(imageData, maxColors) {
        var total = imageData.length / 4;
        for (var i = 0; i < total; i++) {
            // 添加颜色
            addColor(root, {
                r: imageData[i * 4],
                g: imageData[i * 4 + 1],
                b: imageData[i * 4 + 2]
            }, 0);

            // 合并叶子节点
            while (leafNum > maxColors) reduceTree();
        }
    }

    function colorsStats(node, object) {
        if (node.isLeaf) {
            var r = parseInt(node.red / node.pixelCount);
            var g = parseInt(node.green / node.pixelCount);
            var b = parseInt(node.blue / node.pixelCount);

            var color = r + ',' + g + ',' + b;
            if (object[color]) object[color] += node.pixelCount;
            else object[color] = node.pixelCount;
            return;
        }

        for (var i = 0; i < 8; i++) {
            if (null !== node.children[i]) {
                colorsStats(node.children[i], object);
            }
        }
    }

    window.themeColor = function (img, callback) {
        var canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d'),
            width = 0,
            height = 0,
            imageData = null,
            length = 0,
            blockSize = 1;

        width = canvas.width = img.width;
        height = canvas.height = img.height;

        ctx.drawImage(img, 0, 0, width, height);

        imageData = ctx.getImageData(0, 0, width, height).data;

        root = new OctreeNode();
        colorMap = {};
        reducible = {};
        leafNum = 0;

        buidOctree(imageData, 8)

        colorsStats(root, colorMap)

        var arr = [];
        for (var key in colorMap) {
            arr.push(key);
        }
        arr.sort(function (a, b) {
            return colorMap[a] - colorMap[b];
        })
        arr.forEach(function (item, index) {
            arr[index] = item.split(',')
        })
        callback(arr)
    }
})()

四、结果对比

在批量跑了10000张图片之后,得到了下面的结果

平均耗时对比(js-cgi)

img

可以看到在不考虑图片加载时间的情况下,用中位切分法提取的耗时相对较短,而图片加载的耗时可以说是难以逾越的障碍了(整整拖慢了450ms),不过目前的代码还有不错的优化空间,比如间隔采样,绘制到canvas时减小图片尺寸,优化切割点查找等,就需要后续进行更深一点的探索了。

颜色偏差

img

所以看来准确性还是可以的,约76%的颜色与cgi提取结果相近,在大于100的中抽查后发现有部分图片两者提取到的主题色各有特点,或者平分秋色,比如

img

img

五、小结

总结来看,通过canvas的中位切分法与cgi提取的结果相似程度还是比较高的,也有许多图片有很大差异,需要在后续的实践中不断优化。同时,图片加载时间也是一个难以逾越的障碍,不过目前的代码还有不错的优化空间,比如间隔采样,绘制到canvas时减小图片尺寸,优化切割点查找等,就需要后续进行更深一点的探索了。

参考文章

http://acm.nudt.edu.cn/~twcou...

https://xcoder.in/2014/09/17/...

http://blog.rainy.im/2015/11/...

https://xinyo.org/archives/66115

https://xinyo.org/archives/66352

https://github.com/lokesh/col...

http://y.qq.com/m/demo/2018/m...

此文已由作者授权腾讯云+社区在各渠道发布

获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

查看原文

赞 37 收藏 32 评论 5

W3Fun 发布了文章 · 2019-01-03

理解 JavaScript call()/apply()/bind()

理解 JavaScript this 文章中已经比较全面的分析了 this 在 JavaScript 中的指向问题,用一句话来总结就是:this 的指向一定是在执行时决定的,指向被调用函数的对象。当然,上篇文章也指出可以通过 call() / apply() / bind() 这些内置的函数方法来指定 this 的指向,以达到开发者的预期,而这篇文章将进一步来讨论这个问题。

先来回顾一下,举个简单的例子:

var leo = {
  name: 'Leo',
  sayHi: function() {
    return "Hi! I'm " + this.name;
  }
};

var neil = {
  name: 'Neil'
};

leo.sayHi(); // "Hi! I'm Leo"
leo.sayHi.call(neil); // "Hi! I'm Neil"

基本用法

在 JavaScript 中,函数也是对象,所以 JS 的函数有一些内置的方法,就包括 call(), apply() 和 bind(),它们都定义在 Function 的原型上,所以每一个函数都可以调用这 3 个方法。

Function.prototype.call(thisArg [, arg1 [, arg2, ...]]),对于 call() 而言,它的第一个参数为需要绑定的对象,也就是 this 指向的对象,比如今天的引例中就是这样。

第一个参数也可以是 null 和 undefined,在严格模式下 this 将指向浏览器中的 window 对象或者是 Node.js 中的 global 对象。

var leo = {
  name: 'Leo',
  sayHi: function() {
    return "Hi! I'm " + this.name;
  }
};

leo.sayHi.call(null); // "Hi! I'm undefined"

▲ this 指向 window,window.name 没有定义

除了第一个参数,call() 还可以选择接收剩下任意多的参数,这些参数都将作为调用函数的参数,来看一下:

function add(a, b) {
  return a + b;
}

add.call(null, 2, 3); // 5

▲ 等同于 add(2, 3)

apply() 的用法和 call() 类似,唯一的区别是它们接收参数的形式不同。除了第一个参数外,call() 是以枚举的形式传入一个个的参数,而 apply() 是传入一个数组。

function add(a, b) {
  return a + b;
}

add.apply(null, [2, 3]); // 5

注意:apply() 接受的第二个参数为数组(也可以是一个类数组对象),但不意味着调用它的函数接收的是数组参数。这里的 add() 函数依旧是 a 和 b 两个参数,分别赋值为 2 和 3,而不是 a 被赋值为 [2, 3]。

接下来说说 bind(),它和另外两个大有区别。

var leo = {
  name: 'Leo',
  sayHi: function() {
    return "Hi! I'm " + this.name;
  }
};
var neil = {
  name: 'Neil'
};
var neilSayHi = leo.sayHi.bind(neil);
console.log(typeof neilSayHi); // "function"
neilSayHi(); // "Hi! I'm Neil"

与 call() 和 apply() 直接执行原函数不同的是,bind() 返回的是一个新函数。简单说,bind() 的作用就是将原函数的 this 绑定到指定对象,并返回一个新的函数,以延迟原函数的执行,这在异步流程中(比如回调函数,事件处理程序)具有很强大的作用。你可以将 bind() 的过程简单的理解为:

function bind(fn, ctx) {
  return function() {
    fn.apply(ctx, arguments);
  };
}

如何实现

这一部分应该是经常出现在面试中。最常见的应该是 bind() 的实现,就先来说说如何实现自己的 bind()。

◆ bind() 的实现

上一节已经简单地实现了一个 bind(),稍作改变,为了和内置的 bind() 区别,我么自己实现的函数叫做 bound(),先看一下:

Function.prototype.bound = function(ctx) {
  var fn = this;
  return function() {
    return fn.apply(ctx);
  };
}

这里的 bound() 模拟了一个最基本的 bind() 函数的实现,即返回一个新函数。这个新函数包裹了原函数,并且绑定了 this 的指向为传入的 ctx。

对于内置的 bind() 来说,它还有一个特点:

var student = { id: '2015' };

function showDetail (name, major) {
    console.log('The id ' + this.id +
                ' is for ' + name +
                ', who major in ' + major);
}

showDetail.bind(student, 'Leo')('CS');
// "The id 2015 is for Leo, who major in CS"

showDetail.bind(student, 'Leo', 'CS')();
// "The id 2015 is for Leo, who major in CS"

在这里两次调用参数传递的方式不同,但是具有同样的结果。下面,就继续完善我们自己的 bound() 函数。

var slice = Array.prototype.slice;

Function.prototype.bound = function(ctx) {
  var fn = this;
  var _args = slice.call(arguments, 1);
  return function() {
    var args = _args.concat(slice.call(arguments));
    return fn.apply(ctx, args);
  };
}

这里需要借助 Array.prototype.slice() 方法,它可以将 arguments 类数组对象转为数组。我们用一个变量保存传入 bound() 的除第一个参数以外的参数,在返回的新函数中,将传入新函数的参数与 bound() 中的参数合并。

其实,到现在整个 bound() 函数的实现都离不开闭包,你可以查看文章 理解 JavaScript 闭包

在文章 理解 JavaScript this 中,我们提到 new 也能改变 this 的指向,那如果 new 和 bind() 同时出现,this 会听从谁?

function Student() {
  console.log(this.name, this.age);
}

Student.prototype.name = 'Neil';
Student.prototype.age = 20;

var foo = Student.bind({ name: 'Leo', age: 21 });
foo(); // 'Leo' 21

new foo(); // 'Neil' 20

从例子中已经可以看出,使用 new 改变了 bind() 已经绑定的 this 指向,而我们自己的 bound() 函数则不会:

var foo = Student.bound({ name: 'Leo', age: 21 });
foo(); // 'Leo' 21

new foo(); // 'Leo' 21

所以我们还要接着改进 bound() 函数。要解决这个问题,我们需要清楚原型链以及 new 的原理,在后面的文章中我再来分析,这里只提供解决方案。

var slice = Array.prototype.slice;

Function.prototype.bound = function(ctx) {
  if (typeof this !== 'function') {
    throw TypeError('Function.prototype.bound - what is trying to be bound is not callable');
  }
  var fn = this;
  var _args = slice.call(arguments);
  var fBound = function() {
    var args = _args.concat(slice.call(arguments));

    // 在绑定原函数 fn 时增加一次判断,如果 this 是 fBound 的一个实例
    // 那么此时 fBound 的调用方式一定是 new 调用
    // 所以,this 直接绑定 this(fBound 的实例对象) 就好
    // 否则,this 依旧绑定到我们指定的 ctx 上
    return fn.apply(this instanceof fBound ? this : ctx, args);
  };

  // 这里我们必须要声明 fBound 的 prototype 指向为原函数 fn 的 prototype
  fBound.prototype = Object.create(fn.prototype);

  return fBound;
}

大功告成。如果看不懂最后一段代码,可以先放一放,后面的文章会分析原型链和 new 的原理。

◆ call() 的实现

function foo() {
  console.log(this.bar);
}
var obj = { bar: 'baz' };
foo.call(obj); // "baz"

我们观察 call 的调用,存在下面的特点:

  • 当函数 foo 调用 call,并传入 obj 时,似乎是在 obj 的原型上增加了一个 foo 方法。
  • foo.call() 除第一个参数外的所有参数都应该传给 foo(),这一点在实现 bind() 时已处理过。
  • 不能对 foo 和 obj 做任何修改。

那就来看看,以示区别,我们自己实现的 call 叫做 calling。

Function.prototype.calling = function(ctx) {
  ctx.fn = this;
  ctx.fn();
}

我们完成了第一步。

在完成第二步时,我们需要用到 eval(),它可以执行一段字符串类型的 JavaScript 代码。

var slice = Array.prototype.slice;

Function.prototype.calling = function(ctx) {
  ctx.fn = this;
  var args = [];
  for (var i = 1; i < args.length; i++) {
    args.push('arguments[' + i + ']');
  }
  eval('ctx.fn(' + args + ')');
}

这里我们避免采用和实现 bind() 同样的方法获取剩余参数,因为要使用到 call,所以这里采用循环。我们需要一个一个的将参数传入 ctx.fn(),所以就用到 eval(),这里的 eval() 中的代码在做 + 运算时,args 会发生类型转换,自动调用 toString() 方法。

实现到这里,大部分的功能以及完成,但是我们不可避免的为 ctx 手动添加了一个 fn 方法,改变了 ctx 本身,所以要把它给删除掉。另外,call 应该有返回值,且它的值是 fn 执行过后的结果,并且如果 ctx 传入 null 或者 undefined,应该将 this 绑定到全局对象。我们可以得到下面的代码:

var slice = Array.prototype.slice;

Function.prototype.calling = function(ctx) {
  ctx = ctx || window || global;
  ctx.fn = this;
  var args = [];
  for (var i = 1; i < args.length; i++) {
    args.push('arguments[' + i + ']');
  }
  var result = eval('ctx.fn(' + args + ')');
  delete ctx.fn;
  return result;
}

◆ apply() 的实现

apply() 的实现与 call() 类似,只是参数的处理不同,直接看代码吧。

var slice = Array.prototype.slice;

Function.prototype.applying = function(ctx, arr) {
  ctx = ctx || window || global;
  ctx.fn = this;
  var result = null;
  var args = [];
  if (!arr) {
    result = ctx.fn();
  } else {
    for (var i = 1; i < args.length; i++) {
      args.push('arr[' + i + ']');
    }
    result = eval('ctx.fn(' + args + ')');
  }
  delete ctx.fn;
  return result;
}

小结

这篇文章在上一篇文章的基础上,更进一步地讨论了 call() / apply() / bind() 的用法以及实现,其中三者的区别和 bind() 的实现是校招面试的常考点,初次接触可能有点难理解 bind(),因为它涉及到闭包、new 以及原型链。

我会在接下来的文章中介绍对象、原型以及原型链、继承、new 的实现原理,敬请期待。

本文原文发布在公众号 cameraee,点击查看

文章参考

Function.prototype.call() / apply() / bind() | MDN

Invoking JavaScript Functions With 'call' and 'apply' | A Drop of JavaScript

Implement your own - call(), apply() and bind() method in JavaScript | Ankur Anand

JavaScript .call() .apply() and .bind() - explained to a total noob | Owen Yang

JavaScript call() & apply() vs bind()? | Stack Overflow

Learn & Solve: call(), apply() and bind() methods in JavaScript

JavaScript 系列文章

理解 JavaScript this

理解 JavaScript 闭包

理解 JavaScript 执行栈

理解 JavaScript 作用域

理解 JavaScript 数据类型与变量

Be Good. Sleep Well. And Enjoy.

cameraee

前端技术 | 个人成长

查看原文

赞 29 收藏 22 评论 1

W3Fun 赞了文章 · 2018-12-23

2019 - Web开发技术指南和趋势

以下内容来自我特别喜欢的一个Youtube频道: Traversy Media

这是一个2019年你成为前端,后端或全栈开发者的进阶指南:

  1. 你不需要学习所有的技术成为一个web开发者
  2. 这个指南只是通过简单分类列出了技术选项
  3. 我将从我的经验和参考中给出建议
  4. 首选我们会介绍通用的知识, 最后介绍2019年的Web的一些趋势

1. 基础前端开发者

1.1 HTML & CSS

HTML & CSS

最基础的知识:

  • 语义化的HTML元素
  • 基础的CSS语法
  • Flexbox & Grid
  • CSS变量
  • 浏览器开发者工具

1.2 响应式布局

响应式布局

响应式设计将不再是网页的加分项, 而是必须的

  • 设置viewport
  • 非固定宽度
  • 媒体查询
  • 使用 rem 替代 px
  • 移动优先,柱状显示

1.3 基础的部署工作

部署工作

学会如何部署一个静态网站到服务器

  • 注册一个域名(NameCheap, Google Domains)
  • 管理共享主机或虚拟机(Inmotion, Hostgator, Bluehost)
  • FTP, SFTP 文件上传(Filezilla, Cyberduck)
  • 静态页面托管(Netlify, Github Pages)

1.4 SASS预处理器

SASS预处理器

虽然不是必须的, 但是推荐去学, 基础知识的掌握很简单

  • 结构化CSS
  • 变量
  • 嵌套样式表
  • Minxins & 函数
  • 继承

1.5 原生JavaScript语法

JavaScript

不使用任何框架和库区学习原生的JS语法

  • 数据类型, 函数, 条件判断, 循环, 凑总府
  • DOM操作和事件
  • JSON
  • Fetch
  • ES6+(箭头函数, Promise, async/await, 解构)

1.6 满足了基本的前端开发者的条件

基本的前端开发

  • 构建静态站点
  • 构建UI布局(拿到设计图能够使用HTML/CSS还原)
  • 添加一些交互功能
  • 部署和维护网站

现在能找到最低水平的Web开发工作, 但是这是远远不够的....

2. 一个成熟的前端开发者

2.1 HTML & CSS框架

HTML & CSS框架

HTML/CSS框架目前没有以前那么有意义, 但是我还是介意你选择一个学习(这里作者想隐射的应该是, 在jquery时代, HTML/CSS框架的学习是必须的).

  • BootStrap
  • Materialize
  • Bulma

2.2 Git和其他工作流工具

Git和工具

Git绝对是每一个Web开发者必须掌握的工具, 这里也有一些其他的工作流工具的建议.,

  • 基础的命令行(touch, cd, mkdir什么的总得会, 命令行在下面的工具中都会用到)
  • Git(版本控制)
  • NPM 或 Yarn(包管理)
  • Webpack 或者 Parcel(打包工具)
  • Gulp 或者 Grunt(任务管理和构建工具)
  • 编辑器插件(ESLint, Prettier, Live Server等)

2.3 前端框架

前端框架

学习一个前端框架在目前前端开发中是必须的.

  • 在大公司开发中非常流行
  • 更多的交互 & 有趣的UI组件
  • 组件化 & 模块化前端代码
  • 对团队有利

2.4 状态管理

状态管理

对于使用框架的大型前端项目, 你也许需要使用状态管理工具去管理你的应用级的状态

  • Redux(Context API)
  • Apollo(GraphQL Client)
  • Vuex
  • NgRx

2.5 满足一个成熟的前端开发者条件

成熟的前端开发者的条件

  • 构建一个优秀的前端应用
  • 流畅和稳定的前端工作流
  • 多人开发 & 熟练使用Git
  • 请求后端API & 前端数据响应

满足以上条件, 你能够顺利的找到一个前端的工作并干得很出色~

3 全栈开发工程师

3.1 学习一门后端语言

后端语言

成为一个全栈工程师或软件工程师, 你将需要学习一个服务端语言和相关技术

  • Node.js
  • Python
  • PHP
  • C#
  • Go

学习的顺序:

  • 基础的后端语言语法
  • 数据结构和工作流
  • 包管理
  • HTTP/路由

3.2 服务端框架

服务端框架

不要重复造轮子, 学习一门框架去构建更好和更快的应用

  • Node.js(Express, Koa, Adonis)
  • Python(Django, Flask)
  • PHP(Laravel, Symfony)
  • C# (ASP.NET)

3.3 数据库

数据库

绝大多数觉得应用都会使用到数据库, 这里有一些选择:

  • 关系型数据库(MySQL, PostgreSQL, MS SQL)
  • 非关系型数据库 (MongoDB, Counchbase)
  • 云服务 (Firebase, AWS, Azure, DocumentDB)
  • 轻量级(SQLite, NeDB, Redis)

3.4 服务端渲染

服务端渲染

像React, Vue 和 Angular等端架都可以进行服务端渲染

  • Next.js(React)
  • Nuxt(Vue)
  • Angular Universal(Angular)

3.5 内容管理系统

CMS

内容管理系统允许快速开发并为您的客户提供更新内容的能力. 在你需要快速开发网站的时候, 它们是很适合的. 特别是对于自由开发者.

  • 基于PHP的 (Wordpress, Drupal)
  • 基于JS的 (Ghost, Keystone)
  • 基于Python的 (Mezzazine)
  • 基于.Net的 (Piranha, Orchard CMS)

3.6 DevOps 和部署

开发部署相关

学习语言和框架是一回事, 但是安装环境, 测试和部署有事另外一回事

  • 部署 (Linux, SSH, Git, Nginx, Apache)
  • 平台 (Digital Ocean, AWS, Heroku, Azure)
  • 可视化(Docker, Vagrant)
  • 测试 (单元测试, 集成测试, 函数式测试, 系统测试)

3.7 满足全栈工程师的条件

全栈工程师

  • 设置全栈的开发环境和工作流
  • 构建后端服务API和微服务
  • 数据库操作
  • 能够独立开发应用(前端和服务端)
  • 部署到云端(SSH, Git, Servers等等)

4. 2019技术趋势和其他

4.1原生应用开发

原生应用开发

  • React Native(使用React构建原生应用)
  • NativeScirpt(Angular, Typescript, JavaScript)
  • Ionic (HTML/CSS/JS 实现混合应用)
  • Flutter (使用Dart语言开发原生应用的移动端SDK)
  • Xamarin (使用C#开发的移动端应用)

4.2 使用Electron开发桌面应用

Electron开发桌面应用

Electron是一个使用JavaScript构建跨平台的桌面应用工具.

  • 使用到了 Chromium内核和Node.js
  • 兼容Windows, Mac & Linux
  • 崩溃报告, 调试和性能分析

4.3 GraphQL & Apollo

GraphQl是对于API的一种革命性新方法,查询语言比标准RESET严格得多

GraphQL

  • 只查询你想要的东西
  • 前端和后端可以合作得更为顺利
  • 查询语句非常简单且很像JSON语句
  • Apollo是一个发送请求到GraphQL的客户端
  • 使用的是Gatsby静态站点生成器

4.4 TypeScript

TypeScript

TypeScript是一个JavaScript的超集, 它添加了静态类型等很多特性.

  • 变量, 函数等类型
  • 其他ES6的特性
  • 在Angular中被使用到, 同时也可以在React和Vue中被使用

4.5 无服务架构

Serverless架构

无需创建和管理自己的服务器

  • 使用第三服务执行“无服务器功能”
  • 例如 AWS, Netify & Firebase
  • 在Gatsby静态站点生成器很流行
  • 无服务框架

4.6 AI和机器学习

AI和机器学习

AI和机器学习已经被广泛应用在所有的程序和技术中, 甚至包括web开发中.

  • 机器学习可以允许Web应用程序随时间进行调整
  • 虽然AI还有很长的路要走, 但是我们会看到它会更多的用在web中
  • 虽然目前绝大多数都是Python写的, 但也有Tensorflow.js和Brain.js这些JS的库

4.7 区块链技术

区块链技术

现在许多公司使用区块链技术进行数字交易, 因为它们更安全和有效率.

  • Solidity(一门智能合约的编程语言)
  • Mist(以太坊开发的浏览器, 用于发送交易和合约)
  • 比特币API(可以构建app和整和比特币的区块链开发)

4.8 PWA

PWA

Progressive Web Apps是一个web app但是在功能和样式上给用户带来原生应用使用体验的一项技术.

  • 响应式
  • 在离线环境下也能够提供服务
  • 类似App的交互
  • HTTPS
  • 可靠, 迅速, 更好

4.9 Web Assembly

Web Assembly

类似汇编的二进制格式的代码可以被浏览器执行. 可以使用类似C/c++和Rust等高级语言进行编写.

  • 比JavaScript执行效率快
  • 更安全 - 强制的浏览器同源和安全协议
  • 开放 & 可调试

Thank you

以上就是2019 - Web开发技术指南和趋势的全部内容, 要想知道更多细节, 请看Youtube视频: Web Development in 2019
查看原文

赞 171 收藏 131 评论 11

W3Fun 发布了文章 · 2018-12-19

理解 JavaScript this

这是本系列的第 5 篇文章。

还记得上一篇文章中的闭包吗?点击查看文章 理解 JavaScript 闭包

在聊 this 之前,先来复习一下闭包:

var name = 'Neil';

var person = {
  name: 'Leo',
  sayHi: function() {
    return function () {
      return 'Hi! My name is ' + this.name;
    }
  }
};

person.sayHi()(); // "Hi! My name is Neil"

上一篇文章说,我们可以把闭包简单地理解为函数返回函数。所以这里的闭包结构是:

// ...
function () {
  return 'Hi! My name is ' + this.name;
}
// ...

但是你有没有发现,这个函数执行的结果是 “Hi! My name is Neil” 。等等,我不是叫 Leo 吗?怎么给我改了个名字?!

我一分析,原来是 this 在其中作祟,且听我慢慢道来这“改名的由来”。

§ this 从何而来

首先,你得确保你已经清楚执行栈与执行上下文的知识。点击查看文章 理解 JavaScript 执行栈

ECMAScript 5.1 中定义 this 的值为执行上下文中的 ThisBinding。而 ThisBinding 简单来说就是由 JS 引擎创建并维护,在执行时被设置为某个对象的引用

在 JS 中有三种情况可以创建上下文:初始化全局环境、eval() 和执行函数。

§ 全局中的 this

var num = 1;

function getName () {
  return "Leo";
}

this.num; // 1
this.getName(); // Leo

this == window; // true

当我们在浏览器中运行这段代码,JS 引擎会将 this 设置为 window 对象。而声明的变量和函数被作为属性挂载到 window 对象上。当然,在严格模式下,全局中 this 的值设置为 undefined。

"use strcit";

var num = 1;
function getName () {
  return "Leo";
}

this.num; // TypeError
this.getName(); // TypeError

this == undefined; // true

开启严格模式后,全局 this 将指向 undefined,所以调用 this.num 会报错。

§ eval() 中的 this

eval() 不被推荐使用,我现在对其也不太熟悉,这里尝试着说一下。初学者可以直接跳到下一节。

结合所查阅的资料,目前我对 eval() 的理解如下:

eval(...) 直接调用,被理解为是一个 lvalue,也有说是 left unchanged,字面理解为余下不变。什么是“余下不变”?我理解为直接调用 eval(...),其中代码的执行环境不变,依旧为当前环境,this 也依旧指向当前环境中的调用对象。

而使用类似 (1, eval)(...) 的代码,被称为间接调用。(1, eval) 是一个表达式,你可以这样认为 (true && eval) 或者 (0 : 0 ? eval)。间接调用的 eval 始终认为其中的代码执行在全局环境,将 this 绑定到全局对象。

var x = 'outer';
(function() {
  var x = 'inner';
  // "direct call: inner"
  eval('console.log("direct call: " + x)');
  // "indirect call: outer"
  (1, eval)('console.log("indirect call: " + x)');
})();

关于 eval(),现在不敢确定,如有错误,欢迎指正。

§ 函数中的 this

◆ 一般情况

首先,我们需要明确的是,在 JS 中函数也属于对象,它可以拥有属性,this 就是函数在执行时获得的属性。一般情况下,在全局环境中直接调用函数,函数中的 this 会在调用时被 JS 引擎设置为全局对象 window(同样在严格模式下为 undefined)。

var name = "Leo";

function getName() {
  var name = "Neil";
  console.log(this); // [object Window]
  return this.name;
}

getName(); // Leo

◆ 作为对象的方法

函数可以作为对象的方法被该对象调用,那么这种情况 this 会被设置为该对象。

var name = 'Neil';

var person = {
  name: 'Leo',
  sayHi: function() {
    console.log(this); // person
    return 'Hi! My name is ' + this.name;
  }
};

person.sayHi(); // "Hi! My name is Leo"

当 person 对象调用 sayHi() 方法时,this 被指向 person。

◆ 特殊的内置函数

JS 还提供了一种供开发者自定义 this 的方式,它提供了 3 种方式。

  • Function.prototype.call(thisArg, argArray)
  • Function.prototype.apply(thisArg [, arg1 [, args2, ...]])
  • Function.prototype.bind(thisArg [, arg1 [, args2, ...]])

我们可以通过设置 thisArg 的值,来自定义函数中 this 的指向。

var leo = {
  name: 'Leo',
  sayHi: function () {
    return "Hi! My name is " + this.name;
  }
}

var neil = {
  name: 'Neil'
};

leo.sayHi(); // "Hi! My name is Leo"
​leo.sayHi.call(neil); // "Hi! My name is Neil"

这里,我们通过 call() 将 sayHi() 中 this 的指向绑定为 neil 对象,从而取代了默认 的 this 指向 leo 对象。

关于函数的 call(), apply(), bind() 我将在后面另写一篇文章,敬请期待。

§ this 引起的令人费解的现象

◆ 闭包

通过前面的介绍,我想你对 this 已经有了初步的印象。那么,回到文章开头的问题,this 是怎么改变了我的名字?换句话说,this 在闭包的影响下指向发生了怎样的变动?

再看一下代码:

var name = 'Neil';

var person = {
  name: 'Leo',
  sayHi: function() {
    return function () {
      return 'Hi! My name is ' + this.name;
    }
  }
};

person.sayHi()(); // "Hi! My name is Neil"

通过上一篇文章 理解 JavaScript 闭包,函数返回函数会形成闭包。在这种情况下,闭包往往所执行的环境与所定义的环境不一致,而 this 的值却是在执行时决定的。所以,当上面代码中的闭包在执行时,它所在的执行上下文是全局环境,this 将被设置为 window(严格模式下为 undefined)。

怎么解决?我们可以利用 call / apply / bind 来修改 this 的指向。

var name = 'Neil';

var person = {
  name: 'Leo',
  sayHi: function() {
    return function () {
      return 'Hi! My name is ' + this.name;
    }
  }
};

person.sayHi().call(person); // "Hi! My name is Leo"

这里利用 call() 将 this 指向 person。OK,我的名字回来了,“Hi! My name is Leo” ^^

当然,我们还有第二种解决方法,闭包的问题就让闭包自己解决。

var name = 'Neil';

var person = {
  name: 'Leo',
  sayHi: function() {
    var that = this; // 定义一个局部变量 that
    return function () {
      return 'Hi! My name is ' + that.name; // 在闭包中使用 that
    }
  }
};

person.sayHi()(); // "Hi! My name is Leo"

在 sayHi() 方法中定义一个局部变量,闭包可以将这个局部变量保存在内存中,从而解决问题。

◆ 回调函数

在回调函数中 this 的指向也会发生变化。

var name = 'Neil';

var person = {
  name: 'Leo',
  sayHi: function() {
    return 'Hi! My name is ' + this.name;
  }
};

var btn = document.querySelector('#btn');

btn.addEventListener('click', person.sayHi);
// "Hi! My name is undefined"

这里 this 既不指向 person,也不指向 window。那它指向什么?

btn 对象,它是一个 DOM 对象,有一个 onclick 方法,在这里定义为 person.sayHi。

{
  // ...
  onclick: person.sayHi
  // ...
}

所以,当我们执行上面的代码,this.name 的值为 undefined,因为 btn 对象上没有定义 name 属性。我们给 btn 对象自定义一个 name 属性来验证一下。

var btn = document.querySelector('#btn');

btn.name = 'Jackson';

btn.addEventListener('click', person.sayHi);
// "Hi! My name is Jackson"

原因说清楚了,解决方案同样可用过 call / apply / bind 来改变 this 的指向,使其绑定到 person 对象。

btn.addEventListener('click', person.sayHi.bind(person));
// "Hi! My name is Leo"

◆ 赋值

var name = 'Neil';

var person = {
  name: 'Leo',
  sayHi: function() {
    return 'Hi! My name is ' + this.name;
  }
};

person.sayHi(); // "Hi! My name is Leo"

var foo = person.sayHi;

foo(); // "Hi! My name is Neil"

当把 person.sayHi() 赋值给一个变量,这个时候 this 的指向又发生了变化。因为 foo 执行时是在全局环境中,所以 this 指向 window(严格模式下指向 undefined)。

同样,我们可以通过 call / apply / bind 来解决,这里就不贴代码了。

§ 别忘了 new

在 JS 中,我们声明一个类,然后 new 一个实例。

function Person(name) {
  this.name = name;
}

var her = Person('Angelia');
console.log(her.name); // TypeError

var me = new Person('Leo');
console.log(me.name); // "Leo"

如果我们直接把调用这个函数,this 将指向全局对象,Person 在这里就是一个普通函数,没有返回值,默认 undefined,而尝试访问 undefined 的属性就会报错。

如果我们使用 new 操作符,那么 new 其实会生成一个新的对象,并将 this 指向这个新的对象,然后将其返回,所以 me.name 能打印出 “Leo”。

关于 new 的原理,我会在后面的文章分享,敬请期待。

§ 小结

你看,this 是不是千变万化。但是我们得以不变应万变。

在这么多场景下,this 的指向万变不离其宗:它一定是在执行时决定的,指向调用函数的对象。在闭包、回调函数、赋值等场景下我们都可以利用 call / apply / bind 来改变 this 的指向,以达到我们的预期。

接下来,请期待文章《理解 JavaScript call/apply/bind》。

◆ 文章参考

§ JavaScript 系列文章

理解 JavaScript 闭包

理解 JavaScript 执行栈

理解 JavaScript 作用域

理解 JavaScript 数据类型与变量

Be Good. Sleep Well. And Enjoy.

原文发布在我的公众号 camerae,点击查看

clipboard.png

前端技术 | 个人成长

查看原文

赞 14 收藏 11 评论 1

W3Fun 发布了文章 · 2018-12-17

理解 JavaScript 闭包

这是本系列的第 4 篇文章。

作为 JS 初学者,第一次接触闭包的概念是因为写出了类似下面的代码:

for (var i = 0; i < helpText.length; i++) {
  var item = helpText[i];
  document.getElementById(item.id).click = function() {
    showHelp(item.help);
  }
}

给列表项循环添加事件处理程序。当你点击列表项时不会有任何反应。如何在初学就理解闭包?你需要接着读下去。

§ 什么是闭包

说闭包前,你还记得词法作用域吗?

var num = 0;
function foo() {
  var num = 1;
  function bar() {
    console.log(num);
  }
  bar();
}
foo(); // 1

执行上面的代码打印出 1。

bar 函数是 foo 函数的内部函数,JS 的词法作用域允许内部函数访问外部函数的变量。那我们可不可以在外部访问内部函数的变量呢?理论上不允许。

但是我们可以通过某种方式实现,即将内部函数返回。

function increase() {
  let count = 0;
  function add () {
    count += 1;
    return count;
  }
  return add;
}

const addOne = increase();

addOne(); // 1
addOne(); // 2
addOne(); // 3

内部函数允许访问其父函数的内部变量,那么将内部函数返回到出来,它依旧引用着其父函数的内部变量。

这里就产生了闭包。

简单来说,可以把闭包理解为函数返回函数

上面的代码中,当 increase 函数执行,压入执行栈,执行完毕返回一个 add 函数的引用,所以 increase 函数内部的变量对象依旧保存在内存中,不会被销毁。

调用 addOne 函数,相当于执行内部函数 add,它可以访问其父函数的内部变量,从而修改变量 count。而调用 addOne 函数所在的环境为全局作用域,不是定义 add 函数时的函数作用域。

所以,我理解的闭包是一个函数,它在执行时与其定义时所处的词法作用域不一致,并且具有能够访问定义时词法作用域的能力。MDN 这样定义:闭包是函数和声明该函数的词法环境的组合

§ 闭包的利与弊

◆ 利

第一,闭包可以在函数外部读取函数内部的变量。

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
})();

Counter.value(); // 0
Counter.increment();
Counter.increment();
Counter.value(); // 2
Counter.decrement();
Counter.value(); / 1

上面这种模式称为模块模式。我们使用立即执行函数 IIFE 将代码私有化但是提供了可访问的接口,通过公共接口来访问函数私有的函数和变量。

第二,闭包将内部变量始终保存在内存中。

function type(tag) {
  return function (data) {
    return Object.prototype.toString.call(data).toLowerCase() === '[object ' + tag + ']';
  }
}

var isNum = type('number');
var isString = type('string');

isNum(1); // true
isString('abc'); // true

利用闭包将内部变量(参数)tag 保存在内存中,来封装自己的类型判断函数。

◆ 弊

第一,既然闭包会将内部变量一直保存在内存中,如果在程序中大量使用闭包,势必造成内存的泄漏。

$(document).ready(function() {
  var button = document.getElementById('button-1');
  button.onclick = function() {
    console.log('hello');
    return false;
  };
});

在这个例子中,click 事件处理程序就是一个闭包(在这里是个匿名函数),它将引用着 button 变量;而 button 在这里本身依旧引用着这个匿名函数。从而产生循环引用,造成网页的性能问题,在 IE 中可能会内存泄漏。

解决办法就是手动解除引用。

$(document).ready(function() {
  var button = document.getElementById('button-1');
  button.onclick = function() {
    console.log('hello');
    return false;
  };
  button = null; // 添加这一行代码来手动解除引用
});

第二,如果你将函数作为对象使用,将闭包作为它的方法,应该特别注意不要随意改动函数的私有属性。

§ 闭包的经典问题

◆ 循环

现在我们来解决一下文章开头出现的问题。

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

for (var i = 0; i < helpText.length; i++) {
  var item = helpText[i];
  document.getElementById(item.id).click = makeHelpCallback(item.help);
}

额外声明一个 makeHelpCallBack 的函数,将循环每次的上下文环境通过闭包保存起来。

◆ setTimeout

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
};

结果为 1 秒后,打印 5 个 5。

我们可以利用闭包保留词法作用域的特点,来修改代码达到目的。

for (var i = 0; i < 5; i++) {
  setTimeout((function(i) {
    return function () {
      console.log(i);
    }
  }(i)), 1000);
};

结果为 1 秒后,依次打印 0 1 2 3 4。

§ 小结

闭包在 JS 中随处可见。

闭包是 JS 中的精华部分,理解它需要具备一定的作用域、执行栈的知识。理解它你将收获巨大,你会在 JS 学习的道路上走得更远,比如会在后面的文章来讨论高阶函数和柯里化的问题。

◆ 文章参考

闭包 | MDN

学习 JavaScript 闭包 | 阮一峰

Understanding JavaScript Closures: A practical Approach | Paul Upendo

闭包造成问题泄漏的解决办法 | CSDN

§ JavaScript 系列文章

理解 JavaScript 执行栈

理解 JavaScript 作用域

理解 JavaScript 数据类型与变量

欢迎关注我的公众号 cameraee

clipboard.png

前端技术 | 个人成长

查看原文

赞 39 收藏 28 评论 1

认证与成就

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

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-03-06
个人主页被 1.1k 人浏览