阿升

阿升 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

邮箱:duosanglee@gmail.com
qq:7575792499

个人动态

阿升 评论了文章 · 2018-06-29

原生js实现瀑布流效果

image

前言

最近在整理js基础知识,接触到了几个常用的页面特效,其中觉得用原生js实现瀑布流的案例十分有趣,于是与大家分享一下。

瀑布流

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。

image

1、首先瀑布流所有的图片应该保持宽度一致,高度是由内容决定。

image

左浮动的话,我们可以看到第6个盒子直接就在第4个盒子旁边停下了,因为第4个高度最高,挡住了它左浮动的去路。第6个盒子是第2行的最后一个,所以第7个盒子只能在第3行排列了。当排到第12个盒子的时候,盒子会以第11个盒子的位置为基础左浮动(这就是第12个盒子为什么没有‘跳到’第9个盒子下面的原因),碰到第8个盒子后又被挡住了。

image

通过定位的方式是我们实现瀑布流的最基本的原理,只要我们动态的设置它的top值、left值,就能让它排列。

2、定位后确定浏览器显示区域内,一行能放多少列图片盒子。

  • 获取页面的宽度
  • 获取图片盒子的宽度
  • 显示的列数 = 页面宽度/图片盒子宽度
  • column = pageWidth / itemWidth

image

3、为了美观我们可以加上一个空隙

  • 显示的列数 = 页面宽度/(图片盒子宽度+间隙);
  • column = pageWidth / (itemWidth + gap);

image

4、 确定列数之后,排列第一行

  • 下面还有很多图片盒子,我们先要排列第1行,所以在for循环里就要判断一下,当i(所有图片盒子的索引) < column(显示列数)的时候,说明在第1行;
  • 知道在第1行之后,动态设置每个图片盒子的left值就能排好第1行。
  • left = i * ( itemWidth + gap );

image

5、第1行排列好之后,获取第1行所有图片盒子的高度

  • 需要定义一个数组arr,将获取到的高度存在数组中,因为第2行排列的时候需要考虑top值,此时只能根据第1行图片盒子的高度来设置;
  • 获取图片高度的时候要注意,程序必须写在入口函数onload里面,因为图片的加载特性是:等页面都加载完之后才去请求加载,所以不写在入口函数里可能会出现高度获取不到的情况。

image

6、排列第2行

  • 获取到刚刚数组中,高度最小的那一列,将第2行的第1个图片盒子放置在它的下方;
  • 此时的left值就是高度最小列的offsetLefttop值就是:第1行高度最小列的高度(为了布局美观可以加上上下间隙gap)。
  • 记录下高度最小列的索引index,后面计算会用到;
  • 设置完成之后,会发现后面所有的图片都叠在这个高度最小列的下面,原因就是此时的最小列高度没有改变,应该加上下面图片的高度,得出一个新高度。

image

7、改变最小列当前高度

  • 此时的这一列高度其实已经发生改变了,所以需要将新高度赋值给数组
  • 当前高度最小列的高度 = 当前高度最小列的高度 + 间隙 + 下面图片盒子的高度

image

8、触发resize事件

  • 将整个设置样式的部分封装成一个函数,在onload里面注册一个resize事件,只要页面一发生改变,就触发样式部分的代码。
  • 实时改变pageWidth的宽度,这样瀑布流就会是一个响应式的效果了

9、懒加载效果

  • 目前我们用的是30张图片,假如一个页面中有几百张图片的时候,我们不可能等到它都加载完再显示,所有这里引入一个懒加载的概念,我们规定第30张为显示的最后一张图片,当滚动条滚动到30张的时候,应该加载下一批图片。
  • 即页面可视区高度+滚动条卷去的高度 = 第30图片的offsetTop;的时候加载下面的图片。

image

完整代码:

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        position: relative;
    }
    
    img {
        width: 220px;
        display: block;
    }
    
    .item {
        box-shadow: 2px 2px 2px #999;
        position: absolute;
    }
</style>

<!-- html 部分 -->
<div id="box">
    <div class="item"><img data-original="../image/瀑布流/001.jpg" alt=""></div>
                                .
                                .
                                .
    <div class="item"><img data-original="../image/瀑布流/030.jpg" alt=""></div>
</div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');
    var items = box.children;
    // 定义每一列之间的间隙 为10像素
    var gap = 10;

    window.onload = function() {
        // 一进来就调用一次
        waterFall();
        // 封装成一个函数
        function waterFall() {
            // 1- 确定列数  = 页面的宽度 / 图片的宽度
            var pageWidth = getClient().width;
            var itemWidth = items[0].offsetWidth;
            var columns = parseInt(pageWidth / (itemWidth + gap));
            var arr = [];
            for (var i = 0; i < items.length; i++) {
                if (i < columns) {
                    // 2- 确定第一行
                    items[i].style.top = 0;
                    items[i].style.left = (itemWidth + gap) * i + 'px';
                    arr.push(items[i].offsetHeight);

                } else {
                    // 其他行
                    // 3- 找到数组中最小高度  和 它的索引
                    var minHeight = arr[0];
                    var index = 0;
                    for (var j = 0; j < arr.length; j++) {
                        if (minHeight > arr[j]) {
                            minHeight = arr[j];
                            index = j;
                        }
                    }
                    // 4- 设置下一行的第一个盒子位置
                    // top值就是最小列的高度 + gap
                    items[i].style.top = arr[index] + gap + 'px';
                    // left值就是最小列距离左边的距离
                    items[i].style.left = items[index].offsetLeft + 'px';

                    // 5- 修改最小列的高度 
                    // 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 间隙的高度
                    arr[index] = arr[index] + items[i].offsetHeight + gap;
                }
            }
        }
        // 页面尺寸改变时实时触发
        window.onresize = function() {
            waterFall();
        };
        // 当加载到第30张的时候
        window.onscroll = function() {
            if (getClient().height + getScrollTop() >= items[items.length - 1].offsetTop) {
                // 模拟 ajax 获取数据    
                var datas = [
                    "../image/瀑布流/001.jpg",
                            ...
                    "../image/瀑布流/030.jpg"
                ];
                for (var i = 0; i < datas.length; i++) {
                    var div = document.createElement("div");
                    div.className = "item";
                    div.innerHTML = '<img data-original="' + datas[i] + '" alt="">';
                    box.appendChild(div);
                }
                waterFall();
            }

        };
    };

    // clientWidth 处理兼容性
    function getClient() {
        return {
            width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
            height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
        }
    }
    // scrollTop兼容性处理
    function getScrollTop() {
        return window.pageYOffset || document.documentElement.scrollTop;
    }
</script>

效果图:

image

查看原文

阿升 提出了问题 · 2018-06-11

webpack打包后的文件能不能反编译出来(没有sourcemap)。。mac自燃。。硬盘烧坏。。

由于mac自燃。。硬盘烧坏。。导致了只有线上打包后的代码。。想问下各位大佬有什么办法能够把打包后的文件还原回来。。

关注 5 回答 4

阿升 关注了用户 · 2018-01-25

Larry_ @larry_

FE

关注 169

阿升 赞了文章 · 2017-08-24

关于下一代前端框架的一些个人想法

断断续续想了一个多星期, 想不下去了, 索性写成文章看看对别人是否有用吧.
中间有好多我不知道怎么解决的问题, 工具链不成熟, 特别是个人能力不够.
问题是, 下一代的前端框架的是怎样的, 我如何实现出来?
我原来在想 Facebook 搞 ReasonML 了, 新语言很重要, 那么我应该努力学,
然而仔细想想, 问题没那么直接, 我们真的需要那么强大的语言吗?
Haskell 甚至 Idris 已经把问题研究到那样的强大了, 可是对于前端用得着吗?

需要解决的问题

翻看公司局部的业务代码, 我注意到逻辑代码的比例很小, 需要类型验证的并没有那么多,
一个页面当中大部分的代码实际上是 CSS, 样式代码, 样式的细节很多,
而且由于 CSS 天性上抽象能力不够, 加上业务确实有大量的不同, 细节很多,
结果我们有很多手写的样式. 然后是的 HTML 部分, 或者说是 DOM 的描述,
相对而言, 定义组件状态, 访问请求, 同步数据到组件状态, 花费的代码很小,
而且 UI 需要调试, 从设计稿到最终网站上线存在不小的重复工作.

抛开前端代码, 当你想要做个简单的手机页面, 首先需要的是设计,
先用设计规范好页面每个细节, 然后增加交互的能力. 最后提交给用户.
或者说就像是做一个海报, 打印很多份然后贴出去, 没有大的区别,
而现在的问题是有交互, 直接打印是不够的, 就需要人力一点点堆出来了,
人力嘛, 毕竟远远比不上机器来的可靠和高效. 但是现在毕竟机器做不到.

极端理想的网站上线流程, 也就是去掉所有重复的机械工作, 只留下需要人做的部分才投入人力,
需要人力的有:

  • 定义需求

  • 优化视觉(某种程度上未来人工智能也可以完成一部分)

  • 增加基本一些交互

  • 增加复杂的交互

  • 验证网站的可用性(部分 UI 测试可以由测试方案完成)

设计师完成了界面, 然后再程序员用代码写一遍, 这是最明显的重复劳动.
所以自然目标就是用软件生成界面, 也就是类比之前问题到的 Lottie,
用交互软件制作好动画, 导出 JSON 文件, 然后在平台上用代码重现出来,
某种意义上网页上我们需要的也是如此, 用一个软件去编辑出来,
然后导出 JSON, 最后页面上根据 JSON 直接生成网站. 界面做好就好了.

难点: UI 能力有限

这个思路大家都知道, 但顺着这条路想下去, 难点也会很明确,
总有不能用图形直接描述的部分. UI 很容易, 但涉及到逻辑怎么办?
编程当中的问题往往是用数据类型和操作模拟问题本身, 然后用计算解决,
也就是说用来模拟的方案必定包含和问题本身那么大的复杂度.
所以那些包含了复杂逻辑代码, 用 UI 表示出来依然会一样地复杂,
真正用了 UI 而简化, 只是原本抽象不方便调试的 UI 部分.

明确这一点之后, 那些网络相关的逻辑代码, 或者状态相关代码, 比如剥离,
或者说跟状态和操作相关的功能, 不应该用界面去生成, 还是要手写,
那么混杂在 HTML 当中的逻辑了, 比如 if, each 这之类的?
当然还有的 filter 之类的, 说起来可能比较含混, 但是跟 MVVM 多少可以对应.
MVVM 当中大量概念都抽象到类似 HTML 的语法上去了, 貌似很简单.
在内部, 相当于重新定义了功能. 和 React 那样严谨的方案有很大差别.

顺着想下去, HTML 当中逻辑少不了, 那么结果面对的是类似模板引擎的写法,
或者说类似 Vue 的 template 部分的写法, 这是适合用图形界面来表示的.
同时样式部分自然而然很容易对应到图形编辑的属性操作工具上去.
最终剩下的就是 data, computed, method, filter 这些概念的逻辑定义.
由于 UI 表达能力有限, 那些逻辑最终还是代码的形式去表述更为合适.

所以在这个阶段面对的问题就是如何, 这个 template 用界面去编辑,
用图形界面的方式来编辑图形界面, 显然是设计师惯用的解决方案,
在此之外, 我们要引入屏幕尺寸和 Store 相关的代码, 以便工具能够被使用,
也就是说, 即便是生成的, 它最终得到的就是前端制作的网页的渲染效果,
能够响应屏幕的尺寸, 能够根据 Store 内容不同渲染不同页面,
也就是用图形工具来编辑模板引擎, 最终生成代码可用的模板引擎及其样式.

难点: 如何组件化链接 UI 和代码

思考到模板引擎这一步, 可以认为用图形工具是可以替代模板引擎的功能了,
但是对于真实的程序来说, 特别是面对状态管理, 当然是不够的,
比如说子组件的菜单选择后, 修改父组件状态, 从而实现菜单选择的功能,
这里就会遇到状态管理, 而且在实际应用当中难以避免, 甚至会有更复杂的场景,
我们可以做到的复杂组件由开发人员制作, 然后用图形工具使用, 但状态管理少不了,
特别是还会存在表单和页面组合使用的情况. 表单当中的状态更不会少.

除了之间内部的状态, 另一部分是全局状态. 也就是需要 dispatch action.
其实处理 dispatch 相对较容易, 因为 action 已经很好地进行了解耦,
解耦之后代码可以独立出来开发, UI 部分和 reducer 或者说 updater 是分开的,
这里的问题主要在于如何模块化. UI 部分是一起开发的, 逻辑又是在一起开发的.
注意我并不是想在图形工具当中强行写代码, 那么弊大于利,
写逻辑代码还是应该用文本编辑器, 目前没有特别合适的其他选择.
而这样代码和逻辑实际上的是分开的, 需要想办法分析出来, 进行模块化.

所以某种程度上我们需要很强大的编译器, 来分析代码的, 处理模块之间的关系.
我理想化的目标是界面需求用图形工具完成, 大致对应设计或者产品的角色,
然后由比如 API 开发人员来绑定 API 等操作, 相当于服务器开发人员,
然后中间依靠强大的编译方案, 保证两方的合作能够衔接到一起.
同时, 方案需要组件化, 因为有些组件必然靠跨越业务共用的, 否则也损害到效率.
这样推演下来, 强行脱离代码首先导致了组件化方案不能灵活使用的问题.

难点: 调试

不方便调试这个问题应该比较好理解了, 设计的方面太多, 定位问题也就更难.
Vue 出现过这样的问题, 由于设计了很多 DSL 需要自行解析, 细节很繁琐,
相对而言, 纯粹代码的描述的 React 可以直接借助 js debugger 定位问题,
如果为了实现图形化工具引入更多的抽象层级, 调试势必会成为大麻烦,
我们可以想办法说打印更多规范化的 log, 增加自己实现的 validation 之类的,
但是调试的麻烦对于效率的伤害还是很不小的. 肯定不会轻松了.

结尾

整个方案核心的想法是用图形工具来修改一个模板引擎之类的 DSL,
这个 DSL 可以被用在生成 HTML 和更新 DOM 操作, 也就包含一些简单的逻辑,
理论上做个图形工具来对于其功能, 也不是多大的问题, 模板引擎本身并不难,
难的地方在于状态管理难以用图形工具完成, 最终还是依赖代码编辑器,
而这样之后, 组件化和文件链接等方案面临重重困难. 同样也影响到后续的可靠性.
所以结果呢, 卡在这个地方不知道怎么办了. 暂时就想到这里.

为什么说是"下一代前端框架"呢? 比前端这一代好在哪里? 为什么好了一代?
...当然这个东西造不出来的话连可比性都没有, 现在不就没造出来么.
只是从推演上说, 前端要解决的问题主要就是网页的开发效率,
从早先的服务器渲染, 到前端模板渲染, 到声明式渲染, 到同构渲染,
这一路走下来改变了很多. 重点就是在开发速度使用体验上交替改进.
而微调界面一直都是前端开发当中的麻烦事, 而且阻碍了设计师独自发网页.
我认为, 如果能用图形工具直接生成出来, 对前端开发来说是有巨大的跨越的.
届时设计师/前端/后端之间的分工也将会发生一些变化. 有点意思.

查看原文

赞 5 收藏 8 评论 7

阿升 赞了文章 · 2017-08-19

【教学向】150行代码教你实现一个低配版的MVVM库(1)- 原理篇

适读人群

本文适合对MVVM有一定了解(如有主流框架ng,vue等使用经验配合本文服用则效果更佳),虽然会用这类框架,但是对框架底层核心实现又不太清楚,或者能说出个所以然,但是让他自己动手写又没有头绪的码友。如果还没听说过MVVM,不妨先收藏着。。。
图片描述

名词定义

  • 先给低配版的库起一个响亮的名字,以便于开展教学,入乡随俗我们就叫ta -- SegmentFault.js 吧 (以下简称sf.js
    图片描述

  • 设置在DOM Element上的自定义属性前缀统一以 sf- 开头 (如 <input type="text" sf-value="xxx">)

为什么是低配版?

1. 没有sf-repeat
2. 不支持select,checkbox,radio等控件的双向绑定
3. 没有sf-if
4. 很多都没有

图片描述

由于是教学向,力图用最简短易读的代码来实现MVVM最主要最基本的功能,故砍掉了部分实现。

先看演示图,图中就是使用sf.js写得DEMO
演示01

100多行的低配版不能要求太多,如果看不上低配版的库,请关闭本教程。
图片描述

老生常谈

什么是双向绑定

首先明白一个概念,什么是双向绑定?在说双向绑定之前,我们先说说单向显示
单向显示 说白了就是view动态地显示变量。比如在ng或其它一些主流框架里类似这种写法

    //scope.message= "segmentfault"; 
    <h3 ng-bind="message"></h3> 
    <!-- 运行时生成 -->
    <h3>segmentfault</h3>

为什么说是单向呢,因为都是 viewModel上某个变量(message) -> view (h3)的一个过程,viewModel上的变量被view所呈现。

再来看看 逆向修改
前面说了单向是viewMode->view的过程,那逆向就是 viewModel <- view的过程,换句话说就是viewModel被view修改的过程。例如angular中

<input type="text" ng-model="message">

一旦用户在input控件中输入值,便会实时地改变viewModel中message这个变量的值。这是一个view -> viewModel 的过程。

所谓的双向绑定就是一个 viewMode ->(显示) view ->(修改)viewModel 的过程。

双向绑定 = 单向显示 + 逆向修改
注意: 单向显示可能发生在所有的类型DOM节点上,而逆向修改只可能发生在INPUT,SELECT,TEXTAREA等交互型控件上。

如果整明白什么是双向绑定了,我们就来谈谈设计思路,没有整明白的同学请再阅读一遍.

单向显示的设计思路(viewModel -> view)

先看看API的设计

<!-- view -->
<div>
    <h3 sf-text="vm.message"></h3>
</div>

<script>
    // --- viewModel ---
    function ViewModel(){
        this.message = "segmentfault";
    }
    var vm = new ViewModel();
</script>

要实现这个功能,我们的sf库应该需要哪几步操作呢?(先自己想想,独立思考下)

1. 注册ViewModel,我们的库需要知道哪些object是viewModel
2. 扫描整个DOM Tree找到有哪些DOM节点上被配置了sf-xxxx这个attribute
3. 纪录这些被单向绑定的DOM节点和viewModel之间的映射关系
4. 使用DOM API, element.innerText = vm.prop, element.value = vm.prop, element.xxxx = vm.prop 来显示数据

思考题1

Q:如果我们要单向绑定不是innerText,value 而是作为样式的class,style呢?
A:没错,使用sf-class="vm.myClass" sf-style="vm.myStyle"就好了,其它原生属性也以此类推
"sf-" + native attribute is good!

逆向修改的设计思路(viewModel <- view)

主流的一些mvvm框架上一般这么设计API,还是拿angular举例子

<input type="text" ng-model="message">

这里我个人并不认同这种xx-model的命名方式来作为双向绑定的一种标识,比如angular的ng-model,或Vue的v-model,撇开前缀不说,这个model很让人困惑,我们知道input控件是本身就有value这个native的属性的,这个属性就是代表着input输入和输出的值,如果要给一个input进行双向绑定我们应该很自然而然地使用一个 *-value就可以了,完全没有必要弄出一个新的attribute叫做*-model的,从而增加学习成本。

所以,我们就设计一个叫做sf-value的attribute来做API

<input type="text" sf-value="vm.message">

拍脑袋想想,view要改变数据只可能发生在可以和用户交互的一些html控件上,比如input家族(text, radio, checkbox), select, textarea上。 像h1~hn家族,这辈子是没有机会的。
图片描述

要实现view改写viewModel,我们的库应该需要哪几步操作呢?(也先自己想想,千万不要丢掉独立思考能力)

1.扫描整个DOM Tree,找到哪些INPUT,SELECT,TEXTAREA节点上被配置了sf-value这个attribute
2.纪录这些被双向绑定的DOM节点和viewModel之间的映射关系
3.sf.js库自动给这个写DOM加上onchange或者oninput的事件监听
4.一旦监听到change/input事件,立即获取这个DOM的value值,把这个element.value赋给与之绑定的viewModel的变量上。

思考题2

Q:那么问题来了,vm.message被input修改了,谁去通知其它同样绑定了vm.message的view呢?
A:请看下一段

同步机制

图片描述

脏检查大法 这三个字想必大家已经如雷贯耳,我2年多前出去面试的时候被问及最多的就是angular的脏检查,什么是脏检查?angular脏检查的时机是什么?

脏检查的原理就是,拷贝一份copy_viewModel在内存中,一旦有用户点击,输入操作,或ajax请求,setInterval,setTimeout等这些可能导致viewModel发生改变的行为,框架都会把copy_viewModel和最新的viewModel进行深度比较,一旦发现有属性(如vm.message)发生变化,则重新渲染与message绑定的DOM节点。

这也是为什么,一旦你没有使用ng自带的$http,$interval,$timeout,ng-click这些angular自己封装的API去操作viewModel,angular都不会自动去同步view,因为已经超出他的管辖范围了,你必须手动调用apply函数去强制执行一次脏检查,以同步view。

setter大法
听说VUE是使用的这种同步机制,其核心原理就是使用Object.defineProperty(obj, prop, descriptor)(不了解defineProperty的请戳)这个API,在setter中加点料,一旦有任何地方执行 vm.message = "new value"语句,则setter都会被调用,由setter去触发重新渲染view的逻辑。

相较这两种同步机制,似乎setter更加轻便,性能更好。所以本文使用了setter的方式来实现同步机制(关键是实现setter机制使用的代码较少)。

设计思路

给setter加点料
图片描述
http://jsbin.com/gosigoh/edit...

总体设计图

所以归纳来说一个MVVM库主要由3块组成
MVVM库 = 单向显示 + 逆向修改 + 同步机制
下图为SegmentFault.js的实现机制
其中Renderer负责单向显示和逆向修改,Watcher负责监视viewModel为同步机制的核心模块,
Scanner负责sf.js初始化时扫描DOM Tree生成view和viewModel的映射关系。
SegmentFault模块则负责维护view-viewModel Map,以及各个模块间的调度

图片描述

思考题3

Q:了解了MVVM的实现机制,你能否自己动手也试着用百来行代码实现一个MVVM库呢?

好了!本教程第一部分设计篇就写到这里,具体coding请移步(下一篇 【教学向】150行代码教你实现一个低配版的MVVM库(2)- 代码篇
我会用Typescript给出一版实现。

写在最后

这篇文章的目的

2年前写了我受够了angular的笨重,学习曲线陡峭等缺点,自己一怒之下写下一个轻量的MVVM库,给她起名叫【Ukulele.js】(跟我一起念『尤克里里.杰爱死』,当然本文不是这个库的安利文,请安心服用),一开始写这个库出于好玩,后来也加入了越来越多的功能,诸如web component的支持,我渐渐发现,其实要写一个MVVM库也并不是很难,难的是你有没有决心敲下第一行代码。后来我把她和【精通angularjs】一起写在里简历里,然后就去找工作了。面试的时候被问及最多的问题就是:"说说MVVM的实现机制"。

我今天写下此文,1是希望有机会看到这篇文章的码友能真正掌握MVVM的核心机制,2是鼓励下大家能静下心来,自己动手写写库,写写框架,有些你现在觉得蛮高大上的东西,你仔细一分析,动动脑,真的没有那么高大上,普通的码农也能自己实现

相关阅读

【教学向】150行代码教你实现一个低配版的MVVM库(1)- 原理篇
【教学向】150行代码教你实现一个低配版的MVVM库(2)- 代码篇
【教学向】再加150行代码教你实现一个低配版的web component库(1) —设计篇
【教学向】再加150行代码教你实现一个低配版的web component库(2) —原理篇

查看原文

赞 45 收藏 139 评论 10

阿升 赞了文章 · 2017-08-18

Restful 应用分析

Restful API 近年来应用越来越广泛,各大互联网公司纷纷推出了自己的 Restful API 服务。

本文将从实际应用出发,从 REST 到 Restful 再到 Restful API ,逐一进行介绍和分析。


REST 风格

REST 风格最早由 Roy Thomas Fielding 博士提出, REST 是一种系统架构设计风格,主要面向基于网络的软件架构设计。这一架构风格,包含了以下一些基本要求:

客户-服务器

在 REST 风格中,最基本的要求就是对于一个程序来说,应当分离用户接口和数据存储,改善用户接口跨平台迁移的可移植性,同时简化服务器组件,改善系统的可伸缩性。

对于这一点,目前我们所接触到的大多数应用都已经使用了这一模式,无论是浏览器,还是自主开发的客户端程序,其根本的实现方式都是采用了 客户-服务器 模式。客户端负责与用户之间的交互处理,而服务器端则实现数据存储以及相关的业务逻辑。

同时,对于服务器端,完整的系统大部分情况下都会包含多个不同的模块,这些模块之间的调用也应当遵循 客户-服务器 的模式,模块之间通过接口进行互相访问。

无状态

服务端在设计接口时,应当设计为无状态接口。也就是说,服务器端不保存任何与客户端相关的状态上下文信息,客户端在每次调用服务端接口时,需要提供足够的信息,以供服务端完成操作。

在无状态的设计中,服务端减少了保存客户端相关上下文数据,因此,一方面服务端能够更加容易的实现动态扩展,而不至于影响客户端使用;另一方面则减少了服务端从故障中恢复的任务量。

但无状态也会带来额外的问题。客户端将需要保存完整的用户状态信息,在每次与服务端交互时可能需要增加与用户状态相关的上下文信息,这样将导致请求数据的重复和增大。

缓存

根据接口的实际情况,应当在接口设计中增加缓存策略,服务端可以决定是否可以缓存当前返回的数据。通过此种方式,可以在一定程度上减少实际到达服务端请求,从而提高网络访问性能。

但缓存需要谨慎使用,缓存哪些数据,缓存过期时间都是需要根据实际情况进行设计。适当的缓存可以有效的提高系统效率,但是如果设计不当,将有可能导致大量的过期数据,进而影响系统运行。

一般而言,数据字典类数据、修改频率非常低的数据、实时性要求很低的数据等,这些数据可以设计一定的缓存策略,以提高系统运行效率。

系统分层

在设计系统,尤其是大型系统,通常需要将系统按照不同的功能进行横向和纵向的分层。例如横向分层一般可分为交互层、服务层、数据层等,而纵向分层则通常会按照不同的业务功能对系统进行切分。经过分层后,系统将划分为不同的模块进行独立开发部署运行。系统分层后,不同的模块可以独立进化,实现功能解耦,提高整个系统的可扩展性。

统一接口

统一接口,即是不同系统模块之间的调用接口统一规范,使用统一的调用协议,统一的数据格式等。统一接口带来的是系统交互的规范化,接口调用与业务解耦,各模块独立进化。

以上即是对于 REST 风格的解释说明。REST 本身是一种系统架构设计风格,因此其更加偏向理论性。以下的内容将专注于实际场景中运用 REST 风格。


Restful 实现

REST 风格是面向网络的软件架构设计风格,其针对的是基于网络的软件架构。当软件架构设计符合 REST 风格时,则可以描述为该设计是 Restful 的。而在实际场景中,REST 论文中描述了应用 REST 风格基本方式,即 REST (表述性状态转移)。

面向资源

网络上的一个实体,一个具体信息,都可以描述为一个 资源 ,资源可以是文本、图片、音频、服务等具体存在。在网络中,每种资源都对应与一个 URI (统一资源标识符)地址,通过 URI 就可以访问到该资源。而我们通常的上网,即是对资源的各种操作。

在 Restful 架构中,所有的接口应当采用面向资源的接口设计,即对于接口的访问地址指向其 URI 地址。

表述性

资源在网络上呈现出来的可能是多种形式,例如 HTML 、 XML 、 JSON 、图片等等。而客户端与服务器之间则传输的是资源的这种具体表现形式。客户端与服务端的互动,本质上就是通过这些表现形式,实现对资源的操作。

按照面向资源接口设计的要求,通常所见到的 URI 地址中,*.html / *.xml / *.json 等扩展名,其实都指向了当前资源的具体表现形式,而 URI 严格意义上仅指向了资源实体,并不包含具体表现形式。

状态转移

为了使操作资源,也即使资源发生状态转移,按照 REST 的要求,客户端若想要操作服务端资源,需要通过 HTTP 协议进行操作。而在 HTTP 协议中,规定了若干用于具体操作的动词,指向了不同的操作类型。

一般而言,对于资源的操作可以表示 CRUD 四类最基本的操作,即 增删改查 。而 HTTP 协议中的通常用以下动词表示这四类具体的操作:

  • GET :查询资源操作。

  • POST :新建资源操作,也可以用于更新资源。

  • PUT :更新资源操作。

  • DELETE :删除资源操作。

在实际应用中,客户端与服务端之间的交互,即是建立在 HTTP 协议之上,通过面向资源的接口地址,使用 HTTP 协议动词作为操作描述,进而实现客户端与服务端的交互过程。


Restful API 设计

在 REST 提出多年来,当前对于 REST 风格的应用最多的即是 Restful API 。

Restful API 一般分为对外和对内。对外的 Restful API 为面向公网的公共服务接口,此类接口一般可以通过公网直接访问,或是经过一定的安全认证后通过公网访问。而对内的 Restful API 则主要是一整套系统内部各个子系统或模块之间交互的标准接口,相对于对外的 Restful API 接口,内部 API 接口更加标准化。

按照 REST 的要求,Restful API 的设计可以总结出以下的一些具体要求。

HTTPS + 域名

Restful API 的无状态性,要求客户端需要在调用接口时传入足够的信息以供服务端用于操作指定的资源,这就有可能使得接口数据在网络传输过程中遭到拦截导致更多的数据泄漏。因此在提供 Restful API ,特别是对外的 API 时,应当尽可能的使用 HTTPS 协议,以确保传输过程的安全。

另一方面,在 API 地址中使用域名,可以进一步解耦服务端与客户端,服务端可以更加容易的迁移和扩展,而不会影响服务端的使用。

例如:

https://open.domain.com/

实际应用过程中,使用 HTTPS 协议,更多应用与对外的 Restful API 接口,而对内网的 Restful API 来说,可以在信任内网安全的前提下,使用 HTTP 协议,以降低复杂度,提高效率。

URL 指向资源,HTTP 动词指向操作

按照 REST 的要求,Restful API 的 URL 地址应指向具体的一个资源,例如用户 user 。URL 中应当只包含资源名词,不应该包含指向操作的动词,例如新建、查询、修改、删除等。具体操作通过 HTTP 动词( GET / POST / PUT / DELETE )指定。

例如,传统的访问地址,获取用户信息:

https://open.domain.com/app/getUser

此时的 URL 地址指向了 获取用户 这一具体的操作过程,这也就是传统的 RPC 形式。而按照 Restful API 的设计,该实例将设计为如下形式:

https://open.domain.com/app/user

// HTTP GET 请求

此时的 URL 地址指向了 user 这一资源实体,而通过 HTTP 协议中的 GET 动词指定了该请求为 获取用户 请求。

指定 API 版本号

在设计 Restful API 时,特别是对外的 API ,通常需要考虑 API 多版本的问题,因为 API 会进行升级,而客户端则处于不可控状态,可能无法及时对 API 调用过程进行配合升级。因此,服务端需要提供对不同版本 API 的支持,同时,客户端在调用 API 时也需要指定特定的版本号,以确保调用过程正常进行。

版本号的指定,可以在 URL 中,也可以在 HTTP 头信息中。

例如,在 URL 中指定版本号:

https://open.domain.com/v1/app/user

这种指定版本号的方式相对简单直观,但将导致指向统一资源的 URL 产生多个,增加了管理 URL 的成本和复杂度。

例如,在 HTTP 头信息中增加版本号:

https://open.domain.com/app/user

HTTP 头:
API-Vsersion: 1

以上实例中,在 HTTP 请求的头信息中增加了自定义字段,用于表示 Restful API 版本。这种方式不会产生多个 URL ,但其问题是不够直观和简洁,客户端和服务端增加了区分版本号的难度,在在一些特定情况下,例如浏览器端的 HTTP 请求,难以对 HTTP 请求的头信息进行更改。

指定参数

在 Restful API 请求中,可能需要根据不同的情况进行过滤,需要增加操作参数。一般来说,针对 GET 和 DELTE 请求需要增加操作参数的情况较多,而 POST 和 PUT 更多的是通过 HTTP 报文体提供操作数据信息。

指定参数可以通过两种方式:URL 地址参数,? 参数。

URL 地址参数:即在 URL 之后,继续按照 URL 格式拼接参数,服务端接到请求后,通过识别 URL 中字符串的位置,获取不同的参数。

例如:

https://open.domain.com/app/user/123456/Admin

在以上实例中, 123456 代表用户 ID 参数值, Admin 则代表用户类型参数值,后台解析该 URL 后即可根据字符串位置获取到特定的参数。

? 参数:即通过 QueryString 查询字符串的形式,拼接到 URL 之后,查询字符串的格式如下:

?key1=value1&key2=value2&...

例如:

https://open.domain.com/app/user?id=123456&type=Admin

以上实例中,服务端通过解析 ? 之后的字符串获取特定的参数。

使用 JSON 作为返回数据格式

相对于 XML 格式来说,JSON 格式更加简洁易用,占用数据量更小,在以网络为基础,使用 HTTP 协议的 Restful API 中,使用 JSON 作为返回数据格式更加合理。

例如:

// JSON 格式
{"name":"user","type":"Admin"}

// XML 格式
<user><name>user</name><type>Admin</type></user>

使用安全认证机制

在使用 Restful API ,特别是对公网开放的 Restful API,通常需要通过一定的安全认证机制来进行实现访问控制。目前主流的方案是通过 OAuth2.0 实现安全认证。


Restful API 分析

Restful API 的兴起,证明了其具备相比于传统 RPC 接口的优势。当同样的,Restful API 在实际应用过程中,也存在这自己的劣势和问题。

Restful API 优势

Restful API 充分利用了 HTTP 协议的设计,使用面向资源的接口设计,相对于传统 RPC 降低了接口设计的复杂度。

例如,使用传统 RPC 形式设计针对用户对象的 CRUD 操作:

// 新建用户
https://open.domain.com/app/addUser
// 查询用户
https://open.domain.com/app/retrieveUser
// 更新用户
https://open.domain.com/app/updateUser
// 删除用户
https://open.domain.com/app/deleteUser

在以上实例中,需要通过四个 URL 来实现 CRUD 操作。而通过 Restful API 设计,可为如下实例:

// GET: 查询用户;POST: 新建用户;PUT: 更新用户;DELETE: 删除用户
https://open.domain.com/app/user

在以上实例中,通过 HTTP 动词指定了不同的 CRUD 操作,将接口 URL 简化为了同一个地址,仅需要改变 HTTP 动词即可实现不同的操作。

另一方面,相对于 SOAP/XML 形式的 RPC 服务,Restful API 采用 HTTP/JSON 的形式传递数据,降低了传输数据量,同时提高了数据解析的效率,单位时间内的负载能力会高于 SOAP WebService 服务。

例如,对于 SOAP WebService 来说,基本的请求响应格式如下:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Header>
    </soapenv:Header>
    <soapenv:Body>
    </soapenv:Body>
</soapenv:Envelope>

而对于 Restful API 来说,其请求格式符合 HTTP 协议的要求,返回格式则符合标准的 JSON 格式即可。

Restful API 劣势

Restful API 面向资源设计接口,而对于一些复杂操作来说,接口设计难度将远大于 RPC 形式。

例如,用户登录验证,使用 RPC 形式设计接口如下:

https://open.domain.com/app/checkUser

在以上实例中,对于 Restful API 而言,很难将该业务归类为 HTTP 动词中的某一种。又比如“下单”这一功能,它涉及到了订单、用户、支付账户等多个资源实体的多种操作,因此同样也难以设计为 Restful API 。

Restful API 具备高效简洁的特点,但这也因此造成 Restful API 没有类似于 SOAP 协议的规范性协议,Restful API 中的数据格式、标准、安全性等都需要由开发者决定,这也就造成了无法建立统一的 Restful API 标准,作为客户端可能需要适配多种格式的 Restful API 。

Restful API 可能遇到的问题

当 Restful API 存在多版本时,服务端需要维护多版本的接口,这会导致更多的维护成本,而且随着 API 的不断升级,多版本的成本将会越来越高,如何通知客户端进行接口版本升级同样也是可能遇到的问题。

当使用 URL 地址参数形式时,对于可选参数,服务端难以实现正确的读取。在 API 存在可选参数的情况下,URL 地址中的参数位置是不固定的,因此服务端很难判断参数所处的正确位置。

针对特定的业务功能,难以完全按照 Restful 要求进行设计。例如用户验证、订单提交等涉及到多个资源实体和多种操作的业务流程,其一个接口中需要完成多项相关联的操作,若使用 RPC 形式设计接口,则设计为一个统一的接口即可,而使用 Restful API 则可能出现多个接口且接口间存在调用依赖,这将会增加客户端和服务端的处理难度。

Restful 本身并没有安全性方面的标准,需要根据不同的使用场景设计 API 的安全控制方案。目前常用的安全方案即是通过 OAuth2.0 进行安全认证,但不同的 API 在安全认证机制方面也会存在一定的差别,如果设计统一完善的安全机制,也是需要考虑的问题。


Restful API 使用场景

根据 Restful API 的特定,其应用场景可以参考以下的场景:

  • 资源集中型服务,例如针对用户的信息查询,针对订单的信息查询的等,这类型服务以资源实体为中心,操作大多为简单的 CRUD 操作,业务逻辑简单。

  • 访问量大,且对访问时效要求比较高的服务。Restful API 相对于 SOAP WebService 来说,数据量更小,解析更快,在网络环境下能够提高访问的速度和承载能力。

  • 面向公网的,且安全性要求较低的开放型 API 服务。这类服务通常由开发者向公网的所有使用者开放,Restful API 的形式能够简化服务调用过程,提高访问效率。

对于复杂业务操作,例如保全申请提交,理赔申请提交等,使用 Restful API 形式难以进行设计,因此此类的业务可以使用传统 RPC 的接口形式进行设计,SOAP WebService 或者 HTTP/JSON 形式的 RPC 都是可行的选择,使用 RPC 形式反而会更加简单。

选择哪种方式的接口设计,需要根据实际的应用情况进行调整,没有最好的,只有最合适的。


传统 RPC 服务改造

在已经成型的系统当中,多数调用采用了 RPC 形式进行设计,并使用 SOAP WebService 作为具体的实现方式,而且开发者也已经习惯了使用了 RPC 的形式设计服务接口。在这种情况下,若想要向 Restful API 形式改造,将付出大量的改造成本。已有系统中存在错综复杂的调用逻辑,而接口也多数都是面向过程的,这种情况下改造接口,需要在接口设计上进行大幅改动才能够实现 Restful API。同时,开发者也需要在设计开发接口时转变思想,以面向资源的方式进行接口设计,这也是需要面临的问题。

而对于新建系统来说,不存在历史接口,则可以从头按照 REST 风格进行设计,接口可设计为 Restful API,其成本要小于对已有系统的改造。


参考文章:

  1. 《架构风格与基于网络的软件架构设计》 Roy Thomas Fielding 博士论文

  2. 《理解RESTful架构》 http://www.ruanyifeng.com/blog/2011/09/restful.html

  3. 《RESTful API 设计指南》 http://www.ruanyifeng.com/blog/2014/05/restful_api.html

  4. 《SOAP Webservice和RESTful Webservice》 http://blog.sina.com.cn/s/blog_493a845501012566.html

查看原文

赞 11 收藏 92 评论 1

阿升 回答了问题 · 2017-08-17

解决Vue路由的$router.back(-1)回退时如何判断有没有上一个路由

通过history对象的length属性会返回浏览器历史列表中的URL数量。这样判断history.length就能知道有没有上一条路由的历史记录了。
vue中的路由就是通过操控history对象的方法来实现路由跳转的

关注 14 回答 7

阿升 发布了文章 · 2017-08-16

http协议缓存机制

前言

http有关知识一直是前端之路上的必备知识点之一~而我最近对于http缓存这块的知识发现已经有点生疏,这次图文并茂得和大家来一起温习一遍http缓存知识~

话不多说先上图,下面这张图是http缓存的流程,可以让大家有个基本的概念~

http缓存流程

http报文的组成

在具体介绍HTTP缓存之前,作为知识铺垫,先简单介绍一下HTTP报文的组成~

  1. 包含属性的首部(header):
    附加信息(cookie,缓存信息等)与缓存相关的规则信息,均包含在header中

常见的请求头:

常见的请求头:
Accept: text/html,image/*                                      #浏览器可以接收的类型
Accept-Charset: ISO-8859-1                                     #浏览器可以接收的编码类型
Accept-Encoding: gzip,compress                                 #浏览器可以接收压缩编码类型
Accept-Language: en-us,zh-cn                                   #浏览器可以接收的语言和国家类型
Host: www.lks.cn:80                                            #浏览器请求的主机和端口
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT               #某个页面缓存时间
Referer: http://www.lks.cn/index.html                          #请求来自于哪个页面
User-Agent: Mozilla/4.0 compatible; MSIE 5.5; Windows NT 5.0   #浏览器相关信息
Cookie:                                                        #浏览器暂存服务器发送的信息
Connection: close1.0/Keep-Alive1.1                             #HTTP请求的版本的特点
Date: Tue, 11 Jul 2000 18:23:51GMT                             #请求网站的时间
Allow:GET                                                      #请求的方法 GET 常见的还有POST
Keep-Alive:5                                                   #连接的时间;5
Connection:keep-alive                                          #是否是长连接
Cache-Control:max-age=300                                      #缓存的最长时间 300s
Accept: text/html,image/*                                      #浏览器可以接收的类型
Accept-Charset: ISO-8859-1                                     #浏览器可以接收的编码类型
Accept-Encoding: gzip,compress                                 #浏览器可以接收压缩编码类型
Accept-Language: en-us,zh-cn                                   #浏览器可以接收的语言和国家类型
Host: www.lks.cn:80                                            #浏览器请求的主机和端口
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT               #某个页面缓存时间
Referer: http://www.lks.cn/index.html                          #请求来自于哪个页面
User-Agent: Mozilla/4.0 compatible; MSIE 5.5; Windows NT 5.0   #浏览器相关信息
Cookie:                                                        #浏览器暂存服务器发送的信息
Connection: close1.0/Keep-Alive1.1                             #HTTP请求的版本的特点
Date: Tue, 11 Jul 2000 18:23:51GMT                             #请求网站的时间
Allow:GET                                                      #请求的方法 GET 常见的还有POST
Keep-Alive:5                                                   #连接的时间;5
Connection:keep-alive                                          #是否是长连接
Cache-Control:max-age=300                                      #缓存的最长时间 300s
  1. 包含数据的主体部分(body)
    HTTP请求真正想要传输的部分

http缓存规则

HTTP缓存有多种规则,根据是否需要重新向服务器发起请求来分类,可以将其分为两大类(强制缓存,对比缓存),强制缓存如果生效,不需要再和服务器发生交互,而对比缓存不管是否生效,都需要与服务端发生交互。
两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则。

强制缓存

对于强制缓存来说,header中会有两个字段来标明失效规则(Expires/Cache-Control),指的是当前资源的有效期

强制缓存

为方便大家理解,我们认为浏览器存在一个缓存数据库,用于存储缓存信息。
在客户端第一次请求数据时,此时缓存数据库中没有对应的缓存数据,需要请求服务器,服务器返回后,将数据存储至缓存数据库中。

Expires/Cache-Control规则

Expires
Expires的值为服务端返回的到期时间,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。不过Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。Expires 的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,比较的时间是客户端本地设置的时间,所以有可能会导致差错,所以在HTTP 1.1版开始,使用Cache-Control替代。

Cache-Control
用于定义所有的缓存机制都必须遵循的缓存指示,这些指示是一些特定的指令,包括public、private、no-cache(表示可以存储,但在重新验正其有效性之前不能用于响应客户端请求)、no-store、max-age、s-maxage以及must-revalidate等;Cache-Control中设定的时间会覆盖Expires中指定的时间;

对比缓存

对比缓存,顾名思义,需要进行比较判断是否可以使用缓存。
浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。
再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。

对比缓存
在对比缓存生效时,状态码为304,并且报文大小和请求时间大大减少。
原因是,服务端在进行标识比较后,只返回header部分,通过状态码通知客户端使用缓存,不再需要将报文主体部分返回给客户端。
对于对比缓存来说,缓存标识的传递是我们着重需要理解的,它在请求header和响应header间进行传递,一共分为两种标识传递,接下来,我们分开介绍。

Last-Modified/If-Modified-Since规则

Last-Modified:
服务器在响应请求时,告诉浏览器资源的最后修改时间。

If-Modified-Since:
再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。
服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。
若资源的最后修改时间大于If-Modified-Since,说明资源又被改动过,则响应整片资源内容,返回状态码200;
若资源的最后修改时间小于或等于If-Modified-Since,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。

Etag/If-None-Match规则(优先级高于Last-Modified/If-Modified-Since)

Etag:
服务器资源的唯一标识符, 浏览器可以根据ETag值缓存数据, 节省带宽. 如果资源已经改变, etag可以帮助防止同步更新资源的相互覆盖. ETag 优先级比 Last-Modified 高.

If-None-Match:
再次请求服务器时,通过此字段通知服务器客户段缓存数据的唯一标识。
服务器收到请求后发现有头If-None-Match 则与被请求资源的唯一标识进行比对,
不同,说明资源又被改动过,则响应整片资源内容,返回状态码200;
相同,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。

看到这里,你也许会问,既然已经有了 Last-Modified 已经能够知道本地缓存是否是最新的了,为什么还需要 Etag 呢?
主要是基于以下几个原因:Last-Modified 标注的最后修改时间只能精确到秒,如果有些资源在一秒之内被多次修改的话,他就不能准确标注文件的新鲜度了如果某些资源会被定期生成,当内容没有变化,但 Last-Modified 却改变了,导致文件没使用缓存有可能存在服务器没有准确获取资源修改时间,或者与代理服务器时间不一致的情形。

用户的操作对于缓存的影响

用户的操作对于缓存

不能缓存的请求:

  • 不能被缓存的请求HTTP 信息头中包含Cache-Control:no-cache,pragma:no-cache,或Cache-Control:max-age=0 等告诉浏览器不用缓存的请求

  • 需要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的

  • 经过HTTPS安全加密的请求(有人也经过测试发现,ie 其实在头部加入 Cache-Control:max-age 信息,firefox 在头部加入 Cache-Control:Public 之后,能够对HTTPS的资源进行缓存)

  • HTTP 响应头中不包含 Last-Modified/Etag,也不包含 Cache-Control/Expires 的请求无法被缓存

  • 目前浏览器的实现是不会对POST请求的响应做缓存的(从语义上来说也不应该),并且规范中也规定了返回状态码不允许是304。不过这并不表示POST的响应不能被缓存,根据RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content中描述的,如果在POST请求对应的响应中包含Freshness相关信息的话,这次响应也是可以被缓存,具体可以参考上面的那个链接。

在这里就大体介绍完了http缓存方面的知识,但是这也只是http知识的凤毛麟角。。如果感兴趣的同学可以去刷RFC文档~

https://www.zhihu.com/questio...
http://www.cnblogs.com/chenqf...
https://zhuanlan.zhihu.com/p/...
https://zhuanlan.zhihu.com/p/...
https://www.zhihu.com/questio...
https://www.zhihu.com/questio...
http://louiszhai.github.io/20...

查看原文

赞 33 收藏 47 评论 1

阿升 关注了用户 · 2017-08-15

Aresn @aresn

正直 进取 合作 创新

关注 1754

阿升 赞了文章 · 2017-08-15

[面试专题]一线互联网大厂面试总结

道阻且长啊TAT(前端面试总结)

前端 面试 笔试


面试

腾讯一面

  • 1.浏览器工作原理
浏览器的主要组件包括: 用户界面- 包括地址栏、后退/前进按钮、书签目录 浏览器引擎- 用来查询及操作渲染引擎的接口 渲染引擎-
渲染界面:Firefox、Chrome和Safari是基于两种渲染引擎构建的,Firefox使用Geoko——Mozilla自主研发的渲染引擎,Safari和Chrome都使用webkit.
网络- 用来完成网络调用,例如http请求 UI 后端-
用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口 JS解释器- 解释执行JS代码
数据存储- 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据
  • 2.Web安全,举例说明
  • 3.状态码
  • 4.同源:同端口,同域名,同协议
  • 5.对象继承
  • 6.ES6历史以及新特性有哪些?
  • 7.promise原理
  • 8.事件模型
  • 9.常见兼容性问题,列举(移动端/PC端)
  • 10.性能优化   

腾讯二面(机试)

类似百度搜索的提示框,兼容各大浏览器,可用键盘控制.
勉强憋了出来,但是挂掉了,犯了一些低级错误,显示经验不足.
面试官建议多看书,多写组件.

阿里一面

  • 1.Ajax原理
  • 2.浏览器解析过程
流程: 解析html以构建dom树->构建render树->布局render树->绘制render树
参考文章
  • 3.垂直居中
  • 4.数据类型判断
  • 5.路由实现
  • 6.数据本地存储
  • 7.跨域 参考
  • 8.数据双向绑定单向绑定优缺点

阿里二面

  • 1.无线性能优化
  • 2.Tap事件,Touch
  • 3.数据存储

搜狐一面

  • 1.Dom操作
  • 2.移动布局方案
  • 3.前后端协作
  • 4.原生Ajax实现过程

搜狐二面

  • 1.单链表反转
  • 2.快排
  • 3.即时通信(除了Ajax和websocket)
懵逼了
总结一下其他方式
1.Comet技术:基于HTTP长连接的Web端实时通信技术
2.SSE:服务器发送事件,使用长链接进行通讯
  • 4.服务器代理转发如何处理cookie(nginx)
proxy_cookie_domain localhost example.org;
proxy_cookie_domain ~\.([a-z]+\.[a-z]+)$ $1;

proxy_cookie_path /one/ /;
proxy_cookie_path / /two/;
  • 5.对象继承
  • 6.this
  • 7.rem布局的优缺点

腾讯Alloy Team

一面(记录两个,其他都还好)

  • 实现动画有哪些途径

    • CSS3
    • JS帧动画,定时器,requestAnimateFrame
    • Canvas动画
    • SVG
    • 图片
  • 对象继承的实现

二面(跪了)

  • web安全

    • https加密过程,证书用途
    • xss几种形式,防范手段,过滤哪些字符?
    • xsrf原理,实例,防范手段(Laravel的token)
    • Sql注入
  • 性能优化

    • 代码优化(html,css,js)
    • 网络性能优化:

      1. Cache缓存之强制缓存和协商缓存.看具体详情
      2. CDN原理及应用
      3. HTTP压缩之gzip
  • 上下文环境对象
  • 设计模式(要求说出如何实现,应用,优缺点):

    • 单例模式
    • 工厂模式
    • 发布订阅模式
  • 跨域(产生原因)

    • JSONP原理
    • CORS如何设置
    • Nginx代理
  • 读过哪些框架源码?
  • 如何写一个CSS库,要注意哪些东西?

京东二面

  • JS面向对象之封装,继承,多态的体现和应用?
  • BST
  • promiseAll如何实现,以及如何实现多层异步回调?
  • 加油站问题(贪心算法)

阿里二面

  • XHR具体底层原理和API
  • 单例模式实现/设计模式
  • JSONP原理,回调过程
  • Latex怎么解析
  • hash算法实现,如何避免冲突,如何在冲突里检测
  • Generater
  • ES6怎么编译成ES5,css-loader原理,过程
  • ES6转成ES5的常见例子
  • 常见数据结构,常见算法
  • node多线程实现
  • 进程和线程

阿里三面

  • 对前端的理解
  • 说说rn,weex原理
  • 说说XHR
  • Jq的选择器引擎,$('.a .b')以及$('.a').find('.b')
  • MD5摘要算法其他用途

百度一面

  • 前端架构的理解
  • 前端框架的看法
  • 如何做前后端分离
  • 在不使用后端模板渲染的情况下,如何做前端数据直出(不使用异步请求数据)?
  • SSR适用什么场景?SPA首屏空白原因?
  • vue构建过程原理,具体流程
模板到DOM大致流程:
template模板经过parse处理后返回AST 获得一棵AST后再经过generate()生成渲染函数
执行渲染函数后会获得一个VNode,即虚拟DOM patch函数,负责把虚拟DOM变为真正DOM。
  • vue2新增内容?独立构建(standalone)和运行时构建(runtime-only)的差别和应用?详情
  • 爬虫的实现原理?如何实现一个爬虫,如何解析文件?
  • 如何解决爬虫慢的问题?
  • 多线程如何保障各个线程的安全?
  • webpack工具和node了解哪些?node各个模块的底层原理?
  • CSS3实现卡片翻转?

百度二面

  • 说说职业规划?
  • 说说HTML难点,语义化的体现?
  • 说说CSS重要难点?如何实现垂直水平居中?
  • JS中this是什么?如何改变this?
  • 讲讲时间复杂度计算?
  • 算法题:假定26个字母对应1-26,将一串数字转成字母有多少种可能?
    暴力解法复杂度是多少?

(百度外卖业务:包含移动客户端(RN,hybrid),PC web端,H5,后台界面vue,桌面应用包含编辑器插件,前端构建工具,node中间层)

饿了么

  • 盒模型之box-sizing
  • XHR原理及API
  • session和sessionStorage
  • map forEach(数组各个API细节)
  • dom事件代理,有什么优点?
  • 三种隐藏方式差别:visibility:hidden,display:none,opacity:0
渲染上的差异:
1.将元素设置为display:none后,元素在页面上将彻底消失,元素本来占有的空间就会被其他元素占有,也就是说它会导致浏览器的回流和重绘。

2.设置元素的visibility为hidden,和display:none的区别在于,元素在页面消失后,其占据的空间依旧会保留着,所以它只会导致浏览器重绘而不会回流。

3.opacity:0,只是看不到元素,元素依然存在并且占有原有位置. 注: 事件绑定的差异: 1、display:none:元素彻底消失,不会触发绑定的事件.
2、visibility:hidden:无法触发其点击事件,有一种说法是display:none是元素看不见摸不着,而visibility:hidden是看不见摸得着,这种说法是不准确的,设置元素的visibility后无法触发点击事件,说明这种方法元素也是消失了,只是依然占据着页面空间。
3、opacity:0:可以触发点击事件,设置元素透明度为0后,元素只是相对于人眼不存在而已,对浏览器来说,它还是存在的,所以可以触发绑定事件
动画属性的差异: 1、display:none:完全不受transition属性的影响,元素立即消失
2、visibility:hidden:元素消失的时间跟transition属性设置的时间一样,但是没有动画效果.
3、opacity:0,动画属性生效,能够进行正常的动画效果.

代码演示地址

头条一面

  1. inline和block元素列举,block和inline-block区别?
  2. span包含12px大小英文'abc',问span高度?baseline怎么理解?
  3. css实现自适应正方形?
  4. string查找第一个不重复字符.如果换成字符串呢?
  5. Angular数据绑定机制?
  6. 重绘和回流的理解
  7. querySelectAll和ByClassName所获取元素的区别?如何绑定事件?
  8. getComputedStyle用法?
  9. addEventListener绑定事件?参数不同的执行顺序.
  10. 正则匹配电话号码.exec,match,search用法?推荐一篇高质量正则文章
  11. 数据类型判断的方法,toString判断的来源?
  12. ES6 class原理?
  13. function rest参数长度?function.length如何判断与arguments对象长度区别?

头条二面

  • var a;typeof a;let a;考察点
  • mvp,mvc,mvvm
  • vuex原理,watch对象
  • js defer async顺序,模块依赖(AMD,CMD差别)
  • 设计模式和应用
  • session和cookie
  • 清浮动,BFC
  • cookie存储在哪里?(memory)

搜狐提前批

  • BFC
  • Flexbox用法,详见总结
  • 排序,找出最大三个数
  • 快排原理,复杂度计算
  • 继承的实现方式
  • vdom原理,实现
  • webpack打包是如何处理css图片的
  • 模块

CVTE面试

  • 四栏布局
  • 数组,数组对象去重
  • 数组查某个字符出现次数
  • 选择器权值
  • 右键菜单定制

Ruff

  • 函数不定参
  • 定时器原理
  • url=>渲染:script阻塞
  • 数据结构应用
  • 数据双向绑定(vue,ng原理)

其他小公司:

  1. 面向对象三要素
  2. 闭包
  3. 三栏布局
  4. 内联元素包含块元素的表现
  5. 三种隐藏方式的区别
  6. 求最大子数组和.
  7. 块元素和内联元素渲染区别?
  8. ul li实现计数.
  9. flex-box用法
  10. svg与canvas
  11. JPG,png图片特点,场景
  12. CSS写轮播
  13. 拖拽组建
  14. PV统计
  15. URL监听变化

笔试

人人网

  • 1.数组操作
  • 2.排序算法
  • 3.动态规划(书包问题)
  • 4.编程题(正则)

CVTE

  • 线程
  • 各类通信协议
  • 二叉树
  • 算法复杂度
  • 栈封装,利用栈对数组reverse(编程)

美团

  • 各类排序算法以及复杂度计算
  • Node的一些用法
  • 二叉树遍历
  • JS内部属性
  • 页面之间传参
  • 程序题:数组找公共元素

网易

  • 出入栈可能性计算
  • 数组去重
查看原文

赞 57 收藏 267 评论 22

认证与成就

  • 获得 113 次点赞
  • 获得 14 枚徽章 获得 2 枚金徽章, 获得 4 枚银徽章, 获得 8 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-03-30
个人主页被 1k 人浏览