金沙国际官网_金沙国际平台登录

因为这个金沙国际官网_金沙国际平台登录网站与很多的大型澳门赌场都有合作,金沙国际官网_金沙国际平台登录尽职尽责,高效执行,保持好奇心,不断学习,追求卓越,点击进入金沙国际官网_金沙国际平台登录马上体验吧,所以现在也正式地开始了营业。

您的位置:金沙国际官网 > web前端 > 与浏览器历史堆栈管理,制作动画

与浏览器历史堆栈管理,制作动画

发布时间:2019-11-01 12:36编辑:web前端浏览(76)

    使用 Snap.svg 制作动画

    2017/02/22 · HTML5 · SVG

    原文出处: 凹凸实验室   

    图片 1

    前端基础进阶(六):在chrome开发者工具中观察函数调用栈、作用域链与闭包

    2017/02/26 · CSS, 基础技术 · 1 评论 · Chrome, 作用域链, 函数调用栈, 闭包

    原文出处: 波同学   

    图片 2

    配图与本文无关

    在前端开发中,有一个非常重要的技能,叫做断点调试

    在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量对象,闭包,this等关键信息的变化。因此,断点调试对于快速定位代码错误,快速了解代码的执行过程有着非常重要的作用,这也是我们前端开发者必不可少的一个高级技能。

    当然如果你对JavaScript的这些基础概念[执行上下文,变量对象,闭包,this等]了解还不够的话,想要透彻掌握断点调试可能会有一些困难。但是好在在前面几篇文章,我都对这些概念进行了详细的概述,因此要掌握这个技能,对大家来说,应该是比较轻松的。

    为了帮助大家对于this与闭包有更好的了解,也因为上一篇文章里对闭包的定义有一点偏差,因此这篇文章里我就以闭包有关的例子来进行断点调试的学习,以便大家及时纠正。在这里认个错,误导大家了,求轻喷 ~ ~

    History API 与浏览器历史堆栈管理

    2016/07/25 · HTML5 · History, HTML5, 浏览器

    本文作者: 伯乐在线 - 欲休 。未经作者许可,禁止转载!
    欢迎加入伯乐在线 专栏作者。

    移动端开发在某些场景中有着特殊需求,如为了提高用户体验和加快响应速度,常常在部分工程采用SPA架构。传统的单页应用基于url的hash值进行路由,这种实现不存在兼容性问题,但是缺点也有–针对不支持onhashchange属性的IE6-7需要设置定时器不断检查hash值改变,性能上并不是很友好。

    而如今,在移动端开发中HTML5规范给我们提供了一个History接口,使用该接口可以自由操纵历史记录。本文并不详细介绍History接口,而是探究History接口如何影响浏览器历史堆栈,并且利用这个规律应用到具体的实际业务中,提出两种历史记录保存策略,使路由逻辑更清晰,让SPA更容易。

    一、Snap.svg是什么

    从主要功能上说,Snap.svg.js 是一个操纵 SVG 节点/制作 SVG 动画的框架,简单点理解可以看下面文字:

    Snap.svg 是一个可以使你操纵 SVG 资源和 jQuery 操作 DOM 一样简单的类库

    ——译自官网

    拿 Snap.svg (下文简称 Snap ) 和 jQuery (下文简称 JQ ) 来做对比最合适不过,很可能作者也是参考了 JQ 的 API 设计,那么它们的相似程度有多高呢?请看下面的对比表:

    / context(上下文) 选择器 事件绑定 节点操作 属性操作 链式写法
    Snap svg Snap.select(‘circle’) el.click(…)/el.touchend(…) after()/remove()/append() attr() svg.paper.circle(50,50,40).attr({fill:”#f00”});
    JQ document jQuery(‘div’) el.click(…) after()/remove()/append() attr() elem.addClass(‘hide’).remove();

    在 JQ 中,可操作的最外层 DOM 边界是 document 。而在 Snap 的概念里,可操作的最外层的节点是 svg ,svg 节点的选择、事件绑定都需要在这个上下文里完成。

    在上面的对比图可以看出很多 JQ 的影子,无论是选择器、事件绑定、节点操作等等,都是非常的类似 JQ ,有 JQ 基础的同学基本可以半天掌握 Snap 的全部 API。

    一、基础概念回顾

    函数在被调用执行时,会创建一个当前函数的执行上下文。在该执行上下文的创建阶段,变量对象、作用域链、闭包、this指向会分别被确定。而一个JavaScript程序中一般来说会有多个函数,JavaScript引擎使用函数调用栈来管理这些函数的调用顺序。函数调用栈的调用顺序与栈数据结构一致。

    History API回顾

    HTML5 History API包括2个方法:history.pushState()和history.replaceState(),和1个事件:window.onpopstate。

    二、Snap 的代码结构

    图片 3

    笔者根据 Snap 的 API 制作了上面的图表,并且简单标注了注释方便大家理解,可以重点关注一下 Element 和 Paper 这两个类。

    二、认识断点调试工具

    在尽量新版本的chrome浏览器中(不确定你用的老版本与我的一致),调出chrome浏览器的开发者工具。

    浏览器右上角竖着的三点 -> 更多工具 -> 开发者工具 -> Sources

    1
    浏览器右上角竖着的三点 -> 更多工具 -> 开发者工具 -> Sources

    界面如图。

    图片 4

    断点调试界面

    在我的demo中,我把代码放在app.js中,在index.html中引入。我们暂时只需要关注截图中红色箭头的地方。在最左侧上方,有一排图标。我们可以通过使用他们来控制函数的执行顺序。从左到右他们依次是:

    • resume/pause script execution
      恢复/暂停脚本执行
    • step over next function call
      跨过,实际表现是不遇到函数时,执行下一步。遇到函数时,不进入函数直接执行下一步。
    • step into next function call
      跨入,实际表现是不遇到函数时,执行下一步。遇到到函数时,进入函数执行上下文。
    • step out of current function
      跳出当前函数
    • deactivate breakpoints
      停用断点
    • don‘t pause on exceptions
      不暂停异常捕获

    其中跨过,跨入,跳出是我使用最多的三个操作。

    上图左侧第二个红色箭头指向的是函数调用栈(call Stack),这里会显示代码执行过程中,调用栈的变化。

    左侧第三个红色箭头指向的是作用域链(Scope),这里会显示当前函数的作用域链。其中Local表示当前的局部变量对象,Closure表示当前作用域链中的闭包。借助此处的作用域链展示,我们可以很直观的判断出一个例子中,到底谁是闭包,对于闭包的深入了解具有非常重要的帮助作用。

    pushState

    history.pushState(stateObject, title, url),包括三个参数。

    第一个参数用于存储该url对应的状态对象,该对象可在onpopstate事件中获取,也可在history对象中获取。

    第二个参数是标题,目前浏览器并未实现。

    第三个参数则是设定的url。一般设置为相对路径,如果设置为绝对路径时需要保证同源。

    pushState函数向浏览器的历史堆栈压入一个url为设定值的记录,并改变历史堆栈的当前指针至栈顶。

    > 在这里笔者使用历史堆栈和当前指针,用以说明浏览器对历史记录的管理策略。文档中并没有使用这样的词汇,笔者为了更形象的介绍接口对浏览器历史记录的影响,使用这样的描述,如有不当之处请及时指出(不过目前以这套模型为基础的逻辑实现中并未出现悖论)。

    1. Element

    这个部分是节点操作相关的方法集,也是该类库最基础的部分。

    JavaScript

    // 选择节点 var svg = Snap('#svg'); svg.select('circle'); // 选择 svg.select('.rect_01'); // 选择

    1
    2
    3
    4
    // 选择节点
    var svg = Snap('#svg');
    svg.select('circle'); // 选择
    svg.select('.rect_01'); // 选择

    JavaScript

    // 事件绑定 var svg = Snap('#svg'); svg.select('circle').click(function() { // do something });

    1
    2
    3
    4
    5
    // 事件绑定
    var svg = Snap('#svg');
    svg.select('circle').click(function() {
    // do something
    });

    更多方法请参考文后 API 资料。

    三、断点设置

    在显示代码行数的地方点击,即可设置一个断点。断点设置有以下几个特点:

    • 在单独的变量声明(如果没有赋值),函数声明的那一行,无法设置断点。
    • 设置断点后刷新页面,JavaScript代码会执行到断点位置处暂停执行,然后我们就可以使用上边介绍过的几个操作开始调试了。
    • 当你设置多个断点时,chrome工具会自动判断从最早执行的那个断点开始执行,因此我一般都是设置一个断点就行了。

    replaceState

    该接口与pushState参数相同,含义也相同。唯一的区别在于replaceState是替换浏览器历史堆栈的当前历史记录为设定的url。需要注意的是,replaceState不会改动浏览器历史堆栈的当前指针。

    2. Paper

    这部分是画图相关的方法集,这是几乎每个动画框架都有的部分,类似于createjs的Graphics。

    SVG 有6种基本图形:矩形、圆形 、椭圆、线条、折线、多边形。还有另外一种:路径(path),path 是最复杂的一种绘图方式,它可以绘制复杂的图形——当然6种基本图形也不在话下。而关于基本图像与 path 之间的转换,可以参考本站的另外一篇文章:聊聊 SVG 基本形状转换那些事。

    图片 5

    Paper 方法集主要可以绘制6种基本图形(节点),以及文本(节点)、图片(节点)、渐变等。

    JavaScript

    // 画一个圆 var svg = Snap('#svg'); svg.paper.circle({ cx: 100, cy: 100, r: 50, fill: '#f00' });   // 创建一张图片 svg.paper.image('url.jpg', 0, 400, 300, 300);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 画一个圆
    var svg = Snap('#svg');
    svg.paper.circle({
    cx: 100,
    cy: 100,
    r: 50,
    fill: '#f00'
    });
     
    // 创建一张图片
    svg.paper.image('url.jpg', 0, 400, 300, 300);
    四、实例

    接下来,我们借助一些实例,来使用断点调试工具,看一看,我们的demo函数,在执行过程中的具体表现。

    JavaScript

    // demo01 var fn; function foo() { var a = 2; function baz() { console.log( a ); } fn = baz; } function bar() { fn(); } foo(); bar(); // 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // demo01
     
    var fn;
    function foo() {
        var a = 2;
        function baz() {
            console.log( a );
        }
        fn = baz;
    }
    function bar() {
        fn();
    }
     
    foo();
    bar(); // 2

    在向下阅读之前,我们可以停下来思考一下,这个例子中,谁是闭包?

    这是来自《你不知道的js》中的一个例子。由于在使用断点调试过程中,发现chrome浏览器理解的闭包与该例子中所理解的闭包不太一致,因此专门挑出来,供大家参考。我个人更加倾向于chrome中的理解。

    • 第一步:设置断点,然后刷新页面。

    图片 6

    设置断点

    • 第二步:点击上图红色箭头指向的按钮(step into),该按钮的作用会根据代码执行顺序,一步一步向下执行。在点击的过程中,我们要注意观察下方call stack 与 scope的变化,以及函数执行位置的变化。

    一步一步执行,当函数执行到上例子中

    图片 7

    baz函数被调用执行,foo形成了闭包

    我们可以看到,在chrome工具的理解中,由于在foo内部声明的baz函数在调用时访问了它的变量a,因此foo成为了闭包。这好像和我们学习到的知识不太一样。我们来看看在《你不知道的js》这本书中的例子中的理解。

    图片 8

    你不知道的js中的例子

    书中的注释可以明显的看出,作者认为fn为闭包。即baz,这和chrome工具中明显是不一样的。

    而在备受大家推崇的《JavaScript高级编程》一书中,是这样定义闭包。

    图片 9

    JavaScript高级编程中闭包的定义

    图片 10

    书中作者将自己理解的闭包与包含函数所区分

    这里chrome中理解的闭包,与我所阅读的这几本书中的理解的闭包不一样。具体这里我先不下结论,但是我心中更加偏向于相信chrome浏览器。

    我们修改一下demo01中的例子,来看看一个非常有意思的变化。

    JavaScript

    // demo02 var fn; var m = 20; function foo() { var a = 2; function baz(a) { console.log(a); } fn = baz; } function bar() { fn(m); } foo(); bar(); // 20

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // demo02
    var fn;
    var m = 20;
    function foo() {
        var a = 2;
        function baz(a) {
            console.log(a);
        }
        fn = baz;
    }
    function bar() {
        fn(m);
    }
     
    foo();
    bar(); // 20

    这个例子在demo01的基础上,我在baz函数中传入一个参数,并打印出来。在调用时,我将全局的变量m传入。输出结果变为20。在使用断点调试看看作用域链。

    图片 11

    闭包没了,作用域链中没有包含foo了。

    是不是结果有点意外,闭包没了,作用域链中没有包含foo了。我靠,跟我们理解的好像又有点不一样。所以通过这个对比,我们可以确定闭包的形成需要两个条件。

    • 在函数内部创建新的函数;
    • 新的函数在执行时,访问了函数的变量对象;

    还有更有意思的。

    我们继续来看看一个例子。

    JavaScript

    // demo03 function foo() { var a = 2; return function bar() { var b = 9; return function fn() { console.log(a); } } } var bar = foo(); var fn = bar(); fn();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // demo03
     
    function foo() {
        var a = 2;
     
        return function bar() {
            var b = 9;
     
            return function fn() {
                console.log(a);
            }
        }
    }
     
    var bar = foo();
    var fn = bar();
    fn();

    在这个例子中,fn只访问了foo中的a变量,因此它的闭包只有foo。

    图片 12

    闭包只有foo

    修改一下demo03,我们在fn中也访问bar中b变量试试看。

    JavaScript

    // demo04 function foo() { var a = 2; return function bar() { var b = 9; return function fn() { console.log(a, b); } } } var bar = foo(); var fn = bar(); fn();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // demo04
     
    function foo() {
        var a = 2;
     
        return function bar() {
            var b = 9;
     
            return function fn() {
                console.log(a, b);
            }
        }
    }
     
    var bar = foo();
    var fn = bar();
    fn();

    图片 13

    这个时候闭包变成了两个

    这个时候,闭包变成了两个。分别是bar,foo。

    我们知道,闭包在模块中的应用非常重要。因此,我们来一个模块的例子,也用断点工具来观察一下。

    JavaScript

    // demo05 (function() { var a = 10; var b = 20; var test = { m: 20, add: function(x) { return a + x; }, sum: function() { return a + b + this.m; }, mark: function(k, j) { return k + j; } } window.test = test; })(); test.add(100); test.sum(); test.mark(); var _mark = test.mark(); _mark();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // demo05
    (function() {
     
        var a = 10;
        var b = 20;
     
        var test = {
            m: 20,
            add: function(x) {
                return a + x;
            },
            sum: function() {
                return a + b + this.m;
            },
            mark: function(k, j) {
                return k + j;
            }
        }
     
        window.test = test;
     
    })();
     
    test.add(100);
    test.sum();
    test.mark();
     
    var _mark = test.mark();
    _mark();

    图片 14

    add执行时,闭包为外层的自执行函数,this指向test

    图片 15

    sum执行时,同上

    图片 16

    mark执行时,闭包为外层的自执行函数,this指向test

    图片 17

    _mark执行时,闭包为外层的自执行函数,this指向window

    注意:这里的this指向显示为Object或者Window,大写开头,他们表示的是实例的构造函数,实际上this是指向的具体实例

    上面的所有调用,最少都访问了自执行函数中的test变量,因此都能形成闭包。即使mark方法没有访问私有变量a,b。

    我们还可以结合点断调试的方式,来理解那些困扰我们很久的this指向。随时观察this的指向,在实际开发调试中非常有用。

    JavaScript

    // demo06 var a = 10; var obj = { a: 20 } function fn () { console.log(this.a); } fn.call(obj); // 20

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // demo06
     
    var a = 10;
    var obj = {
        a: 20
    }
     
    function fn () {
        console.log(this.a);
    }
     
    fn.call(obj); // 20

    图片 18

    this指向obj

    更多的例子,大家可以自行尝试,总之,学会了使用断点调试之后,我们就能够很轻松的了解一段代码的执行过程了。这对快速定位错误,快速了解他人的代码都有非常巨大的帮助。大家一定要动手实践,把它给学会。

    最后,根据以上的摸索情况,再次总结一下闭包:

    • 闭包是在函数被调用执行的时候才被确认创建的。
    • 闭包的形成,与作用域链的访问顺序有直接关系。
    • 只有内部函数访问了上层作用域链中的变量对象时,才会形成闭包,因此,我们可以利用闭包来访问函数内部的变量。
    • chrome中理解的闭包,与《你不知道的js》与《JavaScript高级编程》中的闭包理解有很大不同,我个人更加倾向于相信chrome。这里就不妄下结论了,大家可以根据我的思路,探索后自行确认。在之前一篇文中我根据从书中学到的下了定义,应该是错了,目前已经修改,对不起大家了。

    大家也可以根据我提供的这个方法,对其他的例子进行更多的测试,如果发现我的结论有不对的地方,欢迎指出,大家相互学习进步,谢谢大家。

    1 赞 2 收藏 1 评论

    图片 19

    onpopstate

    该事件是window的属性。该事件会在调用浏览器的前进、后退以及执行history.forward、history.back、和history.go触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针。在不改变document的前提下,一旦当前指针改变则会触发onpopstate事件。

    3. Snap 工具方法

    Snap下有不少实用工具,比如 Snap.ajax、Snap.format模板、颜色格式转换和插件方法等。

    JavaScript

    // 扩展Snap,为其添加插件方法 Snap.plugin(function (Snap, Element, Paper, global, Fragment) { Snap.newmethod = function () {}; Element.prototype.newmethod = function () {}; Paper.prototype.newmethod = function () {}; });

    1
    2
    3
    4
    5
    6
    // 扩展Snap,为其添加插件方法
    Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
    Snap.newmethod = function () {};
    Element.prototype.newmethod = function () {};
    Paper.prototype.newmethod = function () {};
    });

    History API与业务实践

    最常见的单页应用场景:列表页、商品详情页以及其内部的其他链接入口如图片页、评论页及其推荐其他商品详情页。以上提到的已经涉及到了4个单独业务逻辑页面(推荐的商品可复用商品详情页逻辑),分别是:列表、详情、图片详情和评论。将这4个页面合并到一个页面中,这就是最简单的SPA。为了用户的良好体验,必须设计合理的交互逻辑,最直观的就是浏览器(或手机app、微信公众号)的后退前进必须合乎业务逻辑特点。因此,这就涉及到了History API的使用,也牵扯到浏览器的历史记录管理。

    图片 20

    上图为具体的逻辑示意图。在列表页,点击其中一个商品,这里是商品1,进入详情页。详情页包括了该商品的轮播图、商品的图片详情入口、评论入口和推荐的其他商品入口。接下来进行如下操作:进入图片详情页,后退至详情页再进入评论页;后退至商品1详情页再由推荐商品入口进入商品9详情页,同样在商品9详情页进入图片详情页和评论页,再后退至商品9详情页;由推荐商品入口进入商品34详情页,再进行类似操作。最后保证在商品34图片详情页或评论页可以顺利后退至最初的商品列表页。

    > 上文中加粗的“后退”,意味着使用浏览器后退按钮,或者使用手机自带的返回,再或者使用页面上提供的后退按钮。

    这样一个很细小的需求,但是一旦真正放手去做却不是那么容易。仅仅根据History API的2个函数和1个事件去盲目的尝试实现,这属于盲人摸象,鲁棒性不高。不清楚浏览器的历史记录管理策略,不了解当前页面的历史记录数量,此种情况若要实现上述场景就有些麻烦。所以在具体动手写业务代码之前,需要搞懂History的pushState和replaceState具体如何影响历史记录栈。

    本文由金沙国际官网发布于web前端,转载请注明出处:与浏览器历史堆栈管理,制作动画

    关键词: