精华内容
下载资源
问答
  • 该插件已使用 Backbone 版本 0.9.1 进行测试模型上配置 getter 您的模型应该扩展 Backbone.GSModel 而不是 Backbone.Model 以支持 getter: var MyModel = Backbone . GSModel . extend ( { } ) ; 通过为每...
  • 与此同时,相当一部分研究聚焦于探讨Transformer的必要性,并由此出现了多层感知机(Multi layer perceptron, MLP)、傅里叶变换(Fourier transform)等替代Transformer组件构建backbone的研究。 本文力图将现有前沿同...

    简介

    随着Vision Transformer的发展,利用CNN与Transformer相结合、基于纯Transformer设计的网络架构层出不穷。与此同时,相当一部分研究聚焦于探讨Transformer的必要性,并由此出现了多层感知机(Multi layer perceptron, MLP)、傅里叶变换(Fourier transform)等替代Transformer组件构建网络模型的研究。

    本文力图将现有前沿同Transformer相关或力图替代Transformer结构的相关研究汇总到一起,将其模型的架构分别简要列出,并统一汇总各个backbone模型对应的效果。

    FC: Fully-Connected layer 全连接层,可用1*1卷积等价替代(Network in Network 论文)。
    SA: Self-attention 自注意力模块
    FT: Fourier transform 傅里叶变换
    FF: Feed-Forward layer 前馈网络层
    MHSA: Multi-Head Self-attention 多头自注意力模块
     

    CNN&Transformer Network设计

    Conformer:Local Features Coupling Global Representations for Visual Recognition

    CNN中的卷积操作聚焦于提取图片的局部信息。Transformer能够通过构造patch embeddings提取到图片的全局表示。局部信息的小而精和全局表示的大而全会使得图像的特征提取过程出现提取能力不足和信息冗余的缺点。

    为了更好地平衡两者信息各自包含的特点,作者提出了FCU(Feature Coupling Unit) 单元,作为CNN分支和Transformer分支的信息交互渠道,并在此基础上构建整个网络模型。为了解决两个分支的特征大小不匹配的问题,CNN采用 1×1 conv再上采样传递到Trans block中,Trans block采用下采样和1×1 conv传递到CNN。

    其网络总结构图如下图所示:Conformer
     

    Scaling Local Self-Attention for Parameter Efficient Visual Backbones

    作者考虑了self-attention和CNN操作在广义的空间池化上的等价性,将其统一到了一起。作者阐明,该过程的本质是对图像中某一区域利用一个权重矩阵进行一个线性变换。因此,在HaloNet中,作者将原始图像划分后的patch的感知区域进行一定比例的扩大,引入一个Transformation matrix 对扩大后的区域进行线性变化。将每个patch进行上述操作之后,汇总成一个维度同输入图片的queies,keys和values的生成类似。
    上述操作示意图如下所示:
    在这里插入图片描述
    HaloNet网络家族结构如下所示:
    HaloNet
     

    MLP Network设计

    MLP-Mixer: An all-MLP Architecture for Vision(ViT团队)

    手写数字识别实验(MNIST)中MLP模型能够取得较好的效果。随着图像复杂程度和数据集类别数目的增涨,MLP难以有较为高效的特征提取能力。为了避免特征提取中的参数过于冗余,因此出现了卷积/attention等操作。

    为了融合图像像素特征信息和其位置信息,如ViT相同,将原始 H × W × C 1 H×W×C1 H×W×C1图像构建patch,得到 S = H × W / p 2 S = H×W/p^2 S=H×W/p2个patch,再将每个patch通过MLP映射成C2维,该过程已经将原始patch的序列信息融合到了C之中。因此图像整体输入由三维 H × W × C 1 H×W×C1 H×W×C1,映射成了二维 S × C 2 S×C2 S×C2,该过程参数量压缩比率为:
    H × W × C 1 / S × C 2 = C 1 × p 2 / C 2 H×W×C1/S×C2=C1×p^2/C2 H×W×C1/S×C2=C1×p2/C2

    按照这个基本思路,MLP-Mixer整体架构如下图所示:
    MLP-Mixer
    作者在更大的图像识别数据集ImageNet-21k和JET-300上面也进行了一些实验,获得了较有竞争力的实验结果。(JET-300数据集并未开源,为谷歌的私有数据集)
     

    RepMLP: Re-parameterizing Convolutions into Fully-connected Layers for Image Recognition(RepVGG团队)

    该模型构造思路同样从局部先验性质、全局建模能力、位置信息获取的角度进行构建。卷积操作具有较强的局部特征提取的能力,而FC层具有全局建模能力和先验知识。

    利用结构重参数技术,作者在训练模型时,为每一个MLP层添加平行的卷积+BN分支,构成一个RepMLP Block。在部署时,把平行的卷积分支等效为MLP分支,使deploy模型只有MLP操作。

    与RepVGG的一卷到底相比,RepMLP运行速度更快,并且具备全局建模能力和位置先验性质。其网络结构如下图所示:
    RepMLP
     

    ResMLP: Feedforward networks for image classification with data-efficient training(DeiT作者一作)

    为了解决MLP-Mixer模型需要大量数据进行训练的问题,作者利用残差连接和知识蒸馏操作(在和其他模型对比性能指标时未考虑知识蒸馏),只需要ImageNet-1k数据就能够达到较好的性能。其模型结构简要如下图所示:
    ResMLP
    输入图像的处理方式同ViT、MLP-Mixer均相同。图中,T代表矩阵转置操作,A代表Affine Transformation,其定义如下所示:
    affine操作
    式中alpha和beta均为模型学习的参数,这里的x为每一个patch,该操作单独对一个patch进行简单的重新缩放和增加偏置。ResMLP中并未使用任何正则化方法,而是用A代替了正则方法。
     

    Pay attention to MLPs (将self-attention更换为空间上的线性变换,谷歌大脑)

    本文聚焦于self-attention在网络模块当中的必要性。可以将空间编码过程和self-attention理解为对每一个patch的特征进行拓展,同时将特征建立空间联系。本文意在使用MLP模块对特征的表示空间和位置关系的表示空间均建立联系。网络模块如下图所示:
    gMLP
    其中,Channel proj 代表对输入的n*d embedding中每一个token从特征维度进行线性映射(及n中每一个元素对应的d经过MLP)。而 Spatial Gating Unit(SGU) 对输入的n*d embedding中每一个位置对应的特征进行线性映射(所有n个元素在位置d处的特征经过MLP)
     
    因为,SGU单元考虑到了跨token的位置信息,所以可以替代Transformer中的position encoding环节。而通道映射同样带来了特征本身的信息,因此self-attention可以完全被替代了。
     
    在细节处理上,作者发现在通道映射过程中把特征分成两个部分效果更好。具体的SGU单元定义如下所示:
    通道线性映射
    SGU单元
     

    Do You Even Need Attention?(Transformer中用FF替代self-attention层, FFN)

    为了验证self-attention在transformer block中的有效性,Luke Melas-Kyriazi将block中attention部分完全替代为FF层,整体网络结构如下图所示:
    FFN
    由图可见,在每一个block中完全没有attention层,而是用全连接层来提取patch embedding之后的信息。由后续的实验结果知,在模型较小的情况下其能与trans based模型有相近的性能,在模型较大的情况下其超越了trans based模型。
     

    Transformer相关特性研究

    Intriguing Properties of Vision Transformers论文系统研究了基于Transformer和CNN构造的图像分类器一些很多有趣的特性,包括纹理与形状信息的提取效果不同。作者对模型的鲁棒性,抗攻击性,抗噪声性,泛化性等诸多特性进行了较为系统的研究,并得出了很多有趣的结论
    论文解析为此链接。

    Transformer中用FT替代self-attention层

    Transformer中的SA模块对于长序列有巨大的计算开销,这种开销成为了Transformer使用限制的主要原因。为了代替SA模块,同时保留对输入序列次序特征的敏感性,在Transformer中的encoder部分中,采用FT代替SA层,使得BERT模型在保留92%准确率的同时在GPU上训练速度提升7倍。

    对于一个序列{Xn},n属于[0, N-1]其傅里叶变换如下式表达:
    傅里叶变换
    利用傅里叶变换构造的encoder和Transformer中的encoder模块对比如下图所示。(左:原始的Trans,右:FT)
    FT替代SA模块
    目前该范式只用于自然语言处理的基准测试,暂未应用到vision transformer中。
     

    模型效果汇总

    图像识别:在公开的ImageNet-1k数据集上的top1和top5性能指标,对照标准为未使用其他额外数据,未使用知识蒸馏、迁移学习,使用适当的数据增强方法。
    对于每一种模型架构提出的不同参数量的结构设计,取其性能指标最好的结果进行统计。
    img_size 代表训练时图片的尺寸大小

    NameStyletop1img_sizeParametersFLOPs
    ResNet-50CNN78.0325.53M8343M
    ResNet-101CNN79.4044.49M15919M
    ConformerCNN&Trans84.1083.3M
    HaloNetCNN&Trans84.9067M
    FFN-BFF74.922463M
    ViTCNN&Trans71.2306M
    FFN-LFF71.9224206M
    MLP-MixerMLP76.4459M
    RepMLP-R50MLP80.0787.38M8354M
    ResMLP-36MLP79.7060045M8900M
    gMLP-BMLP81.6022473M31.6B

     

    参考文献

    2021.3.30 Scaling Local Self-Attention for Parameter Efficient Visual Backbones[CVPR 2021oral] (HaloNet)
    2021.5.4 MLP-Mixer: An all-MLP Architecture for Vision [arXiv]
    2021.5.6 Do You Even Need Attention? A Stack of Feed-Forward Layers Does Surprisingly Well on ImageNet [arXiv]
    2021.5.5 RepMLP: Re-parameterizing Convolutions into Fully-connected Layers for Image Recognition[arXiv]
    2021.5.7 ResMLP: Feedforward networks for image classification with data-efficient training[arXiv]
    2021.5.9 Conformer:Local Features Coupling Global Representations for Visual Recognition [arXiv]
    2021.5.9 FNet: Mixing Tokens with Fourier Transforms [arXiv]
    2021.5.17 Pay attention to MLPs[arXiv]

    知乎:Vision MLP超详细解读 (原理分析+代码解读) (一)

    展开全文
  • 1 前言 最近自己在联系写作Xception,在测试的时候,希望写一下笔记记录...主要是从张量运算的角度出发,测试模型的运算是否正确, 具体来说,就是测试Module的forward过程是否能正常执行,而不会出现编译错误; ...

    1 前言

    最近自己在联系写作Xception,在测试的时候,希望写一下笔记记录自己的经验和体会~

    2 主干网络backbone的测试方法

    2.1 自洽性(self consistency)测试

    主要是从张量运算的角度出发,测试模型的运算是否正确,

    具体来说,就是测试Module的forward过程是否能正常执行,而不会出现编译错误;

    展开全文
  • 在一点点慢慢地写一个简单的SPA应用,在这样的一个过程里,我们也不得不写一些测试以方便重构。 Backbone Collection Bacbkbone主要由三部分组成 model:创建数据,进行数据验证,销毁或者保存到服务器上。 ...

    在一点点慢慢地写一个简单的SPA应用,在这样的一个过程里,我们也不得不写一些测试以方便重构。

    Backbone Collection

    Bacbkbone主要由三部分组成

    1. model:创建数据,进行数据验证,销毁或者保存到服务器上。

    2. collection:可以增加元素,删除元素,获取长度,排序,比较等一系列工具方法,说白了就是一个保存 models的集合类。

    3. view:绑定html模板,绑定界面元素的事件,初始的渲染,模型值改变后的重新渲染和界面元素的销毁等。

    于是我们简单地定义了一个Model以及一个Collection

    define(['backbone'], function(Backbone) {
        var RiceModel = Backbone.Model.extend({});
        var Rices = Backbone.Collection.extend({
            model: RiceModel,
            url: 'http://localhost:8080/all/rice',
            parse: function (data) {
                return data;
            }
        });
        return Rices;
    });
    

    用来获取数据,接着我们便可以创建一个测试,测试代码如下

    define([
        'sinon',
        'js/Model/Rice_Model'
    ], function( sinon, Rices) {
        'use strict';
    
        beforeEach(function() {
            this.server = sinon.fakeServer.create();
            this.rices = new Rices();
    
        });
    
        afterEach(function() {
            this.server.restore();
        });
    
        describe("Collection Test", function() {
            it("should request the url and fetch", function () {
                this.rices.fetch();
                expect(this.server.requests.length)
                    .toEqual(1);
                expect(this.server.requests[0].method)
                    .toEqual("GET");
                expect(this.server.requests[0].url)
                    .toEqual("http://localhost:8080/all/rice");
            });
    
        });
    });
    

    在这里我们用sinon fake了一个简单的server

    this.server = sinon.fakeServer.create();
    

    这样我们就可以在fetch的时候mock一个response,在这时我们就可以测试这里的URL是不是我们想要的URL。

                this.rices.fetch();
                expect(this.server.requests.length)
                    .toEqual(1);
                expect(this.server.requests[0].method)
                    .toEqual("GET");
                expect(this.server.requests[0].url)
                    .toEqual("http://localhost:8080/all/rice");
    

    这样我们便可以完成一个简单地测试的书写。

    展开全文
  • Model是Backbone中所有数据模型的基类,用于封装原始数据,并提供对数据进行操作的方法,我们一般通过继承的方式来扩展和使用它。 如果你做过数据库开发,可能对ORM(对象关系映射)不会陌生,而Backbone中的Model...

    Model是Backbone中所有数据模型的基类,用于封装原始数据,并提供对数据进行操作的方法,我们一般通过继承的方式来扩展和使用它。

    如果你做过数据库开发,可能对ORM(对象关系映射)不会陌生,而Backbone中的Model就像是映射出来的一个数据对象,它可以对应到数据库中的某一条记录,并通过操作对象,将数据自动同步到服务器数据库。(下一节即将介绍的Collection就像映射出的一个数据集合,它可以对应到数据库中的某一张或多张关联表)。

    1.1 创建数据模型
    我们先通过一段代码来看看如何创建数据模型

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            defaults: {
                    name: 'unknown',
                    author: 'unknown',
                    price: 0
            }
    });
    
    // 实例化模型对象
    var javabook = new Book({
            name: 'Thinking in Java',
            author: 'Bruce Eckel',
            price: 395.70
    });

    我们通过Model.extend方法,定义一个自己的模型类Book。

    Backbone模块类(包括子类)都包含一个extend静态方法用于实现继承。给extend方法传递的第一个参数应该是一个对象,对象中的属性和方法将被添加到子类,我们可以通过extend方法扩展子类或重载父类的方法。

    从Backbone模块类继承的子类,都包含一个__super__静态属性,这是一个指向父类原型对象的引用,例如:

    var Book = Backbone.Model.extend({
            constructor: function() {
                    Book.__super__.constructor.call(this)
            }
    });

    在这个例子中,我们重载了Model类的构造函数,但我们希望在子类被实例化时,调用父类的构造函数,因此我们可以通过引用Book.__super__.constructor来调用它。

    实际上我们一般并不会重载模块类的constructor方法,因为在Backbone中所有的模块类都提供了一个initialize方法,用于避免在子类中重载模块类的构造函数,当模块类的构造函数执行完成后会自动调用initialize方法。

    回到本节的第一个例子,我们在定义Book类的时候,传递了一个defaults参数,它用于定义模型数据的默认状态,虽然我们在创建Book实例后再添加它们,但为每个数据模型定义属性列表和默认值,是一个好的编码习惯。

    最后,我们通过new关键字,创建了一个Book的实例,并向它的构造函数中传递了一系列初始化数据,它们将覆盖defaults中定义的默认状态。

    2、初始化和读取数据
    在我们定义好一个模型类之后,可以通过new关键字实例化该模型的对象。
    如果模型类在定义时设置了defaults默认数据,这些数据将被复制到每一个实例化的对象中,如:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            defaults: {
                    name: 'unknown',
                    author: 'unknown',
                    price: 0
            }
    });
    
    // 实例化模型对象
    var javabook = new Book();

    上面的代码创建了一个Book实例javabook,它包含了模型类在定义时的默认数据。

    我们将实例化的代码稍作修改:

    // 实例化模型对象
    var javabook = new Book({
            name: 'Thinking in Java'
    });
    
    // 通过get和escape()方法获取模型中的数据
    var name = javabook.get('name');
    var author = javabook.escape('author');
    var price = javabook.get('price');
    
    // 在控制台输出模型中的数据name
    console.log(name); // 输出Thinking in Java
    console.log(author); // 输出unknown
    console.log(price); // 输出0

    我们在实例化对象时传递了初始数据,它将覆盖Book类定义时defaults中的默认数据,这一点很容易理解。

    上面的例子中我们通过get()和escape()方法获取模型中的数据,它们的区别在于:
    get()方法用于直接返回数据
    escape()方法先将数据中包含的HTML字符转换为实体形式(例如它会将双引号转换为"形式)再返回,用于避免XSS攻击。

    模型将原始数据存放在对象的attributes属性中,因此我们也可以通过javabook.attributes属性直接读取和操作这些数据,例如:

    // 在控制台直接输出对象的attributes属性
    console.dir(javabook.attributes);
    
    // 控制台输出结果
    // {
    //     author: 'unknown',
    //     name: 'Thinking in Java',
    //     price: 0
    // }

    但通常并不会这样做,因为模型中数据状态的变化会触发一系列事件、同步等动作,直接操作attributes中的数据可能导致对象状态异常。更安全的做法是:通过get()或escape()方法读取数据,通过set()等方法操作数据。

    3、修改数据
    我们通常可以调用模型对象的set()方法,来修改模型中的数据,例如:

    // 实例化模型对象
    var javabook = new Book();
    
    // 通过set方法设置模型数据
    javabook.set('name', 'Java7入门经典');
    javabook.set('author', 'Ivor Horton');
    javabook.set('price', 88.50);
    
    // 获取数据并将数据输出到控制台
    var name = javabook.get('name');
    var author = javabook.get('author');
    var price = javabook.get('price');
    
    console.log(name); // 输出Java7入门经典
    console.log(author); // 输出Ivor Horton
    console.log(price); // 输出88.50
    //set()方法也允许同时设置多个属性,例如:
    javabook.set({
            name: 'Java7入门经典',
            author: 'Ivor Horton',
            price: 88.50
    });

    当调用set()方法修改模型中的数据时,会触发一系列事件,我们常常通过监听这些事件,来动态调整界面中数据的显示,我们先来看一个例子:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            defaults: {
                    name: 'unknown',
                    author: 'unknown',
                    price: 0
            }
    });
    
    // 实例化模型对象
    var javabook = new Book();
    
    // 监听模型"change"事件
    javabook.on('change', function(model) {
            console.log('change事件被触发');
    });
    // 监听模型"change:name"事件
    javabook.on('change:name', function(model, value) {
            console.log('change:name事件被触发');
    });
    // 监听模型"change:author"事件
    javabook.on('change:author', function(model, value) {
            console.log('change:author事件被触发');
    });
    // 通过set()方法设置数据
    javabook.set({
            name: 'Thinking in Java',
            author: 'unknown',
            price: 395.70
    });
    
    // 控制台输出结果:
    // change:name事件被触发
    // change事件被触发

    在本例中,我们监听了模型对象的change事件,该事件在模型对象的任何数据发生改变时被触发,change事件触发时,会将当前模型作为参数传递给监听函数。

    我们还监听了change:name和change:author两个属性事件,属性事件是当模型中对应属性的数据发生改变时被触发,属性事件按照“change:属性名”来命名,因此它并不固定。属性事件触发时,会将当前模型和最新的数据作为参数传递给监听函数。

    本例执行后在控制台的输出结果为:
    “change:name事件被触发
    change事件被触发”

    从结果中看,并没有触发我们监听的change:author事件,因为在调用set()方法时,它会在内部检查新的数据比较上一次是否发生变化,只有发生变化的数据才会被设置和触发监听事件。

    另一个细节是,我们先监听了change事件,然后监听了属性事件,但事件在触发时,总是会先触发属性事件,然后再触发change事件。

    Backbone允许我们在修改模型数据时获取上一个状态的数据,这常常用于数据比较和数据回滚。

    例如在下面的例子中,我们希望当price价格被改变时,提示用户价格的变化情况:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            defaults: {
                    name: 'unknown',
                    author: 'unknown',
                    price: 0
            }
    });
    
    // 实例化模型对象
    var javabook = new Book();
    
    // 监听"change:price"事件
    javabook.on('change:price', function(model, value) {
            var price = model.get('price');
    
            if (price < value) {
                    console.log('价格上涨了' + (value - price) + '元.');
            } else if (price > value) {
                    console.log('价格下降了' + (value - price) + '元.');
            } else {
                    console.log('价格没有发生变化.');
            }
    });
    // 设置新的价格
    javabook.set('price', 50);
    
    // 控制台输出结果:
    // 价格没有发生变化.

    我们通过监听change:price事件来监听价格的变化,并希望将最新的价格和当前(上一次)的价格作比较,但控制台的输出结果却是“价格没有发生变化.”。这是因为当change事件或属性事件被触发时,模型中的数据已经被修改,因此通过get()方法获取到的是模型中最新的数据。

    这时,我们可以通过previous()和previousAttributes()方法来获取数据被修改之前的状态。

    我们将代码稍作修改,只需要修改监听事件的函数即可

    // 监听"change:price"事件
    javabook.on('change:price', function(model, value) {
            var price = model.previous('price');
    
            if (price < value) {
                    console.log('价格上涨了' + (value - price) + '元.');
            } else if (price > value) {
                    console.log('价格下降了' + (value - price) + '元.');
            } else {
                    console.log('价格没有发生变化.');
            }
    });

    我们将get()方法修改为previous()方法,用来获取价格在修改之前的状态,此时控制台输出的结果为:“价格上涨了50元.”

    model.get()方法取到的是模型中最新的数据
    model.previous()方法接收一个属性名,并返回该属性在修改之前的状态;
    previousAttributes()方法返回一个对象,该对象包含上一个状态的所有数据。

    需要注意的是,previous()和previousAttributes()方法只能在数据修改过程中调用(即在模型的change事件和属性事件中调用),比如下面的例子就是错误的调用方法:

    // 设置新的价格
    javabook.set('price', 50);
    
    var prevPrice = javabook.previous('price');
    var newPrice = javabook.get('price');
    
    if (prevPrice < newPrice) {
            console.log('价格上涨了' + (newPrice - prevPrice) + '元.');
    } else if (prevPrice > newPrice) {
            console.log('价格下降了' + (newPrice - prevPrice) + '元.');
    } else {
            console.log('价格没有发生变化.');
    }
    
    // 控制台输出结果:
    // 价格没有发生变化.

    控制台输出的结果是“价格没有发生变化.”,因为在set()方法被调用完毕后,模型的上一个状态也会被新数据替换。
    (有一种特殊情况是当我们使用了silent配置时,上面的代码可以得到我们想要的结果,关于silent配置将在后面“数据验证”章节中介绍)

    4、数据验证
    Backbone模型提供了一套数据验证机制,确保我们在模型中存储的数据都是通过验证的,我们通过下面的例子来说明这套验证机制:

    执行这段代码,你会在控制台看到这段信息:“书籍价格不应低于1元.”

    在定义模型类时,我们可以添加一个validate方法,该方法会在模型中的数据发生改变之前被自动调用(就像我们通过set()方法修改数据时一样)。

    validate方法接收一个参数,表示需要进行验证的数据集合,如果validate方法没有任何返回值(即undefined),则表示验证通过;如果验证不通过,我们常常会返回一个错误字符串或自定义对象。但实际上,当你返回一个false也会被认为验证通过,因为Backbone内部会将validate的返回值转换为布尔类型,如果为false则认为验证通过,反之则认为不通过(虽然这听起来有些别扭)。

    当validate验证不通过时,会触发invalid事件,并将模型对象和validate方法的返回值传递给invalid事件的监听函数(就像例子中的那样)。

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            validate: function(data) {
                    if (data.price < 1) {
                            return '书籍价格不应低于1元.';
                    }
            }
    });
    
    var javabook = new Book();
    
    // 监听invalid事件,当验证失败时触发
    javabook.on('invalid', function(model, invalid) {
            alert(invalid);
    });
    
    javabook.save({
            price: 0
    });

    上面的例子中,我们监听了javabook对象的invalid事件,用于在验证不通过时提示用户。但在某个场景下,我希望以另一种方式提示用户,我可以在invalid监听函数中判断是否处于这种场景下,然后作出不同的提示,但这显然不是最好的办法。

    因此,Backbone提供了另一种方式对invalid事件进行覆盖,来看看这个例子:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            validate: function(data) {
                    if (data.price < 1) {
                            return '书籍价格不应低于1元.';
                    }
                    return false;
            }
    });
    
    var javabook = new Book({
            price: 50
    });
    
    // 监听invalid事件,当验证失败时触发
    javabook.on('invalid',
    function(model, invalid) {
            console.log(invalid);
    });
    
    // 在调用set()方法时,传递了一个配置对象,包含自定义的invalid处理方法
    javabook.save('price', 0, {
            invalid: function(model, invalid) {
                    console.log('自定义错误:' + invalid);
            }
    });

    在这段代码中,我们在调用save()方法时,传递了第三个参数,它是一个用于描述配置信息的对象,我们设定了一个invalid函数。当validate方法验证失败时,会优先调用配置中传递的invalid函数,如果没有传递invalid函数,则会触发invalid事件。

    var Chapter = Backbone.Model.extend({
            validate: function(attrs, options) {
                    if (attrs.end < attrs.start) {
                            return "can't end before it starts";
                    }
            }
    });
    
    var one = new Chapter({
            title: "Chapter One: The Beginning"
    });
    
    one.set({
            start: 15,
            end: 10
    });
    
    if (!one.isValid()) {
            alert(one.get("title") + " " + one.validationError);
    }

    5、删除数据
    Backbone中删除模型数据的操作相对简单,我们常常用unset()和clear()方法来删除模型中的数据:

    unset()方法用于删除对象中指定的属性和数据
    clear()方法用于删除模型中所有的属性和数据

    我们来看一个unset()方法的例子:

    // 定义Book模型类
    var Book = Backbone.Model.extend();
    
    // 实例化模型对象
    var javabook = new Book({
            name: 'Java7入门经典',
            author: 'Ivor Horton',
            price: 88.50
    });
    
    // 输出: Java7入门经典
    console.log(javabook.get('name'));
    
    // 删除对象name属性
    javabook.unset('name');
    
    // 输出: undefined
    console.log(javabook.get('name'));
    //当我们对模型的name属性执行unset()方法后,模型内部会使用delete关键字将name属性从对象中删除。
    
    //clear()方法与unset()方法执行过程类似,但clear()方法会删除模型中的所有数据,例如:
    // 定义Book模型类
    var Book = Backbone.Model.extend();
    
    // 实例化模型对象
    var javabook = new Book({
            name: 'Java7入门经典',
            author: 'Ivor Horton',
            price: 88.50
    });
    
    // 删除对象name属性
    javabook.clear();
    
    // 以下均输出: undefined
    console.log(javabook.get('name'));
    console.log(javabook.get('author'));
    console.log(javabook.get('price'));

    在调用unset()和clear()方法清除模型数据时,会触发change事件,我们也同样可以在change事件的监听函数中通过previous()和previousAttributes()方法获取数据的上一个状态。

    6、将模型数据同步到服务器
    Backbone提供了与服务器数据的无缝连接,我们只需要操作本地Model对象,Backbone就会按照规则自动将数据同步到服务器。

    如果需要使用Backbone默认的数据同步特性,请确定你的服务器数据接口已经支持了REST架构。在REST架构中,客户端会通过请求头中的Request Method告诉服务器我们将要进行的操作(包括create、read、update和delete,它们对应的Request Method分别为POST、GET、PUT和DELETE),而对于没有良好支持REST发送方式的浏览器,Backbone会使用另外一些方法来实现,这在本节中会详细讨论。

    在讨论数据同步相关方法之前,你需要先了解一些Backbone中与数据同步息息相关的内容:
    a、数据标识:
    设想一下,如果我们需要通过服务器接口删除一条数据,仅仅在报文头中通过Request Method标识告诉服务器进行delete操作是不够的,更重要的是还需要告诉服务器删除哪一条数据,这需要我们传递给服务器一个数据的唯一标识(例如记录id)。

    Backbone中每一个模型对象都有一个唯一标识,默认名称为id,你可以通过idAttribute属性来修改它的名称。

    id应该由服务器端创建并保存在数据库中,在与服务器的每一次交互中,模型会自动在URL后面加上id,而对于客户端新建的模型,在保存时不会在URL后加上id标识(我们可以通过模型的isNew()方法来检查,该模型对象是否是由客户端新建的)。

    a、URL规则:
    Backbone默认使用path info的方式来访问服务器接口。

    例如:我们在删除一个模型数据时,模型会在报文头的Request Method中声明delete操作,并在服务器接口后自动加上模型id,格式类似于http://urlRoot/10001,其中urlRoot是我们设置的服务器接口地址,而10001是模型id。请注意它是通过URL路径的方式自动追加到接口地址后的,因此服务器也必须要支持PATHINFO的解析方式。

    使用PATHINFO方式,因为它更直观,更利于SEO,还可以避免与Backbone中的路由器发生混淆(关于路由器将在后面的章节中介绍)。

    如果我们希望让Backbone自动与服务器接口进行交互,首先应该配置模型的URL,Backbone支持3种方式的URL配置:
    第一种是urlRoot方式:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service'
    });
    
    // 创建实例
    var javabook = new Book({
            id: 1001,
            name: 'Thinking in Java',
            author: 'Bruce Eckel',
            price: 395.70
    });
    
    // 保存数据
    javabook.save();

    在这个例子中,我们创建了一个Book模型的实例,并调用save()方法将数据保存到服务器。(可能你对save()方法还不太了解,但这并不重要,因为我们马上就会讲到它,现在你只需知道我们用它将模型中的数据保存到服务器)

    你可以抓包查看请求记录,你能看到请求的接口地址为:http://localhost/service/1001

    其中localhost是我的主机名,因为我在本地搭建了一个Web服务器环境。
    service是该模型的接口地址,是我们在定义Book类时设置的urlRoot。
    1001是模型的唯一标识(id),我们之前说过,模型的id应该是由服务器返回的,对应到数据库中的某一条记录,但这里为了能直观的测试,我们假设已经从服务器端拿到了数据,且它的id为1001。

    这段内容很容易理解,接下来,我们将save()方法换成destroy()方法(该方法用于将模型中的数据从服务器删除):

    // 删除数据
    javabook.destroy();

    你能看到请求的接口地址仍然为:http://localhost/service/1001。这并不奇怪,如果你细心观察,会发现两次请求头中的Request Method参数分别为PUT和DELETE,服务器接口会根据它来判断你所做的操作。

    如果你的浏览器不支持REST发送方式,你可能会看到Request Method始终是POST类型,且在Form Data中会多出一个_method参数,PUT和DELETE操作名被放在了这个_method参数中。这是Backbone为了适配低版本浏览器而设计的另一种方法,你的服务器接口也必须同时支持这种方式。

    我们再来看第二种URL方式的例子:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service',
            url: '/javaservice'
    });
    
    // 创建实例
    var javabook = new Book({
            id: 1001,
            name: 'Thinking in Java',
            author: 'Bruce Eckel',
            price: 395.70
    });
    
    // 保存数据
    javabook.save();

    在这个例子中,我们在定义Book类时,新增了参数url,执行这段代码,你会发现请求的接口地址为http://localhost/javaservice。它没有再使用urlRoot定义的参数,也没有将模型的id追加到接口地址中,urlRoot和url参数我们一般只会同时定义一个,它们的区别在于:

    urlRoot参数表示服务器接口地址的根目录,我们无法直接访问它,只能通过连接模型id来组成一个最终的接口地址
    url参数表示服务器的接口地址是已知的,我们无需让Backbone自动连接模型id(这可能是在url本身已经设置了模型id,或者不需要传递模型id)
    如果同时设置了urlRoot和url参数,url参数的优先级会高于urlRoot。
    (另一个细节是,url参数不一定是固定的字符串,也可以是一个函数,最终使用的接口地址是这个函数的返回值。)

    最后一种URL方式的例子:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service',
            url: '/javaservice'
    });
    
    // 创建实例
    var javabook = new Book({
            id: 1001,
            name: 'Thinking in Java',
            author: 'Bruce Eckel',
            price: 395.70
    });
    
    // 保存数据
    javabook.save(null, {
            url: '/myservice'
    });

    在这个例子中,我们在调用save()方法的时候传递了一个配置对象,它包含一个url配置项,最终抓包看到的请求地址是http://localhost/myservice。因此你可以得知,通过调用方法时传递的url参数优先级会高于模型定义时配置的url和urlRoot参数。

    在Backbone中,模型对象提供了3个方法用于和服务器保持数据同步:
    save()方法:在服务器创建或修改数据
    fetch()方法:从服务器获取数据
    destroy()方法:从服务器移除数据

    下面我们将依次介绍这些方法的使用:

    save()方法:
    save()方法用于将模型的数据保存到服务器,它可能是一条新的数据,也可能是修改服务器现有的某一条数据,这取决于模型中是否存在id(唯一标识)。

    首先我们来看一个创建数据的例子:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service'
    });
    
    // 创建实例
    var javabook = new Book();
    
    // 设置初始化数据
    javabook.set({
            name: 'Thinking in Java',
            author: 'Bruce Eckel',
            price: 395.70
    });
    
    // 从将数据保存到服务器
    javabook.save();

    在这个例子中,我们创建了一个新的Book实例,并设置了一些数据(实际上它们可能是由用户输入的),我们通过save()方法将这些数据提交到服务器。

    如果你抓包看一下报文头信息,能看到Request Method参数为POST,这是因为模型内部会通过isNew()方法检查是否为客户端新建,如果是客户端新建的数据,会通过POST方式发送。如果是修改服务器现有的数据,则通过PUT方式发送。

    如果服务器接口的报文体中没有返回任何数据,你会发现保存之后的模型较之前没有发生任何变化,在你下一次调用save()方法的时候,它仍然会以POST方式通知服务器创建一
    条新的数据。这是因为模型对象并没有获取到刚刚服务器创建成功的记录id,因此我们希望服务器接口在将数据保存成功之后,同时将新的id返回给我们,就像这样:

    {
            "id": "1001",
            "name": "Thinking in Java(修订版)",
            "author": "Bruce Eckel",
            "price": "395.70"
    }

    这一段是服务器接口返回的数据,它除了返回新记录的id,还返回了修改后的name数据(当然,你也可以只返回新记录的id,我们常常都是这样做的)。这时我们再来看现在模型中的数据,它多了一个id属性,并且name属性的值也发生了变化,也就是说模型使用服务器返回的最新数据替换了之前的数据。

    我们将代码稍作修改:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service'
    });
    
    // 创建实例
    var javabook = new Book();
    
    // 设置初始化数据
    javabook.set({
            name: 'Thinking in Java',
            author: 'Bruce Eckel',
            price: 395.70
    });
    
    // 将数据保存到服务器
    javabook.save(null, {
            success: function(model) {
                    // 数据保存成功之后, 修改price属性并重新保存
                    javabook.set({
                            price: 388.00
                    });
                    javabook.save();
            }
    });

    我们修改了save()方法的调用参数,像例子中那样,你可以设置一个success回调函数用来表示保存成功之后将要进行操作(你也可以设置一个error回调函数用来表示保存失败时将要进行的操作)。

    在数据保存成功之后,我们将修改模型的price值,并从新调用save()方法保存数据。
    我们抓包看一下请求头,发生了一些什么变化:
    Request Method变成了PUT。
    请求的接口地址变成了http://localhost/service/1001(这与我们刚刚讨论的URL配置有关,如果不明白可以重新阅读本节)。
    当然,还有提交的数据也变成了我们修改后的。

    在调用save()方法时,我们可以传递一个配置项对象,上面我们已经使用它传递了一个success回调函数。

    在配置项中,还可以包含一个wait配置,如果我们传递了wait配置为true,那么数据会在被提交到服务器之前进行验证,当服务器没有响应新数据(或响应失败)时,模型中的数据会被还原为修改前的状态。如果没有传递wait配置,那么无论服务器是否保存成功,模型数据均会被修改为最新的状态、或服务器返回的数据。

    我们还是用一个例子来说明:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            defaults: {
                    name: 'unknown',
                    author: 'unknown',
                    price: 0
            },
            urlRoot: '/service'
    });
    
    // 创建实例
    var javabook = new Book();
    
    // 从将数据保存到服务器
    javabook.save({
            name: 'Thinking in Java',
            author: 'Bruce Eckel',
            price: 395.70
    },
    {
            wait: true
    });

    请运行这个例子中的代码,并且将服务器接口返回的数据设置为空(或404状态),你能看到在save()方法调用完成之后,模型中的数据被恢复成最初defaults中定义的数据,因为我们在调用save()方法时传递了wait配置。(你也可以试着将wait配置去掉,然后再运行它,你会发现虽然服务器接口并没有返回数据或保存成功,但模型对象中仍然保持着最新的数据)

    正如我们最开始所讲得那样,save()方法用于添加一条新的数据到服务器,或修改现有的一条数据。
    其实save()方法也可以同时实现数据修改和保存,例如:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service'
    });
    
    // 创建实例
    var javabook = new Book();
    
    // 从将数据保存到服务器
    javabook.save({
            name: 'Thinking in Java',
            author: 'Bruce Eckel',
            price: 395.70
    });

    在本例中,我们在调用时将数据传递给save()方法,而不是先通过set()方法设置数据。当然,你也可以像set()方法一样,只设置某一个值:
    javabook.save('name', 'Thinking in Java');
    无论你通过什么方式来保存数据,它都会自动将数据同步到服务器接口(如果你没有设置url或urlRoot参数,那么所有的操作只会在本地进行)。

    我们来讨论另一个问题:上面提到服务器接口返回的数据会被覆盖到当前模型中,在刚刚的例子里,接口返回的数据就是模型需要的数据。而实际开发中往往并没有这么顺利,我们接口返回的数据可能是这样:

    {
            "resultCode": "0",
            "error": "null",
            "data": [{
                    "isNew": "true",
                    "bookId": "1001",
                    "bookName": "Thinking in Java(修订版)",
                    "bookAuthor": "Bruce Eckel",
                    "bookPrice": "395.70"
            }]
    }

    你能看到,接口返回的数据无论从结构、还是属性名,都与模型中定义的不一样(有时甚至会返回XML或其它格式)。还好Backbone提供了一个parse()方法,用于在将服务器返回的数据覆盖到模型前,对数据进行解析。

    parse()方法默认不会对数据进行解析,因此我们只需要重载该方法,就可以适配上面的数据格式,例如:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service',
            // 重载parse方法解析服务器返回的数据
            parse: function(resp, xhr) {
                    var data = resp.data[0];
                    return {
                            id: data.bookId,
                            name: data.bookName,
                            author: data.bookAuthor,
                            price: data.bookPrice
                    }
            }
    });
    
    // 创建实例
    var javabook = new Book();
    
    // 从将数据保存到服务器
    javabook.save({
            name: 'Thinking in Java',
            author: 'Bruce Eckel',
            price: 395.70
    });

    我们重载了parse()方法,并返回了模型中能够使用的格式,这样就可以将服务器接口返回的数据与模型中的数据连接起来。虽然本例中使用了最简单的方式解析,但实际上你可能还会做一些格式化、转换和逻辑工作。

    另外值得注意的一点是:我们常常会在数据保存成功后,对界面做一些改变。此时你可以通过许多种方式实现,例如通过save()方法中的success回调函数。

    但我建议success回调函数中只要做一些与业务逻辑和数据无关的、单纯的界面展现即可(就像控制加载动画的显示隐藏),如果数据保存成功之后涉及到业务逻辑或数据显示,你应该通过监听模型的change事件,并在监听函数中实现它们。虽然Backbone并没有这样的要求和约束,但这样更有利于组织你的代码。

    fetch()方法:
    fetch()方法用于从服务器接口获取模型的默认数据,常常用于模型的数据恢复,它的参数和原理与save()方法类似,因此你可以很容易理解它。
    先让我们看一个例子:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service'
    });
    
    // 创建实例
    var javabook = new Book();
    
    // 从服务器获取默认数据
    javabook.fetch({
            success: function() {
                    // 获取数据成功后, 重新读取一次
                    javabook.fetch();
            }
    });

    在这个例子中,我们创建了一个空的(没有初始化数据的)Book模型实例,然后通过fetch()方法从服务器接口获取初始化数据,获取数据成功后再次调用fetch()方法重新获取一次。

    我们将服务器接口返回的数据设置为:

    {
            "id": "1001",
            "name": "Thinking in Java",
            "author": "Bruce Eckel",
            "price": "395.70"
    }

    你需要注意观察两次请求的URL和参数:
    第一次请求地址为http://localhost/service,Request Method参数为GET
    第二次请求地址为http://localhost/service/1001,Request Method参数为GET
    你会发现第二次在请求地址后加上了1001(模型id),这是因为在第一次获取数据成功后,服务器接口返回的数据会覆盖到模型中,因此模型对象具备了唯一标识(id),因此在此后的每次请求中,模型都会将id加载请求地址后面。

    destroy()方法:
    destroy()方法用于将数据从集合(关于集合我们将在下一章中讨论)和服务器中删除,需要注意的是,该方法并不会清除模型本身的数据。(如果需要删除模型中的数据,请手动调用unset()或clear()方法)

    当你的模型对象从集合和服务器端删除时,只要你不再保持任何对模型对象的引用,那么它会自动从内存中移除。(通常的做法是将引用模型对象的变量或属性设置为null值)
    当调用destroy()方法时,模型会触发destroy事件,所有监听该事件的函数将被调用。
    我们还是通过一个例子来详细了解它:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service'
    });
    
    // 创建实例
    var javabook = new Book({
            id: '1001'
    });
    
    // 从服务器删除数据
    javabook.destroy();

    这个例子非常简单,我们创建一个模型后再调用destroy()方法将它销毁。
    请抓包观察请求地址和Request Method:
    我们看到请求地址为:http://localhost/service/1001,Request Method参数为DELETE。它通过Reuqest Method请求参数通知服务器接口将要进行的操作,而请求地址和save()方法及fetch()方法产生的请求地址是相同的,这正体现了我们最开始所说的REST架构。

    在调用destroy()方法时我们同样可以传递一个配置对象,它除了success和error回调函数外,也能像save()方法一样包含一个wait配置,来看下面的例子:

    // 定义Book模型类
    var Book = Backbone.Model.extend({
            urlRoot: '/service'
    });
    
    // 创建实例
    var javabook = new Book({
            id: '1001'
    });
    
    // 监听模型的destroy事件, 在控制台输出字符串
    javabook.on('destroy', function() {
            console.log('destroy');
    });
    
    // 从服务器删除数据
    javabook.destroy({
            wait: true
    });

    如果你的service服务器接口能正常访问,那么你能看到在控制台输出了“destroy”字符串;如果将你的接口设置为响应失败(例如404),那么控制台就不会有输出。

    当我们传递了wait配置后,模型会先请求服务器接口对数据进行删除,当服务器返回状态成功(状态码200)之后,本地才会进行模型的删除操作,最终触发destroy事件。

    如果你想通过Backbone实现数据同步,而不使用RET架构,那么你可以通过重新定义Backbone.sync方法来适配现有的服务器接口。

    在Backbone中,所有与服务器交互的逻辑都定义在Backbone.sync方法中,该方法接收method、model和options三个参数。如果你想重新定义它,可以通过method参数得到需要进行的操作(枚举值为create、read、update和delete),通过model参数得到需要同步的数据,最后根据它们来适配你自己定义的规则即可。

    当然,你也可以将数据同步到本地数据库中,而不是服务器接口,这在开发终端应用时会非常适用。

    7、小结
    至此,Backbone模型中的核心方法和特性我们都已经讨论完了,我们总结一下本节讨论的主要内容:

    模型封装了对象数据,并提供了一系列对数据进行操作的方法
    我们可以在定义模型类、实例化模型对象、和调用set()方法来设置模型中的数据
    当模型中数据发生改变时,会触发change事件和属性事件
    我们可以定义validate方法对模型中的数据进行验证
    通过调用save()、fetch()和destroy()方法可以让模型中的数据与服务器保持同步,但在此之前必须设置url或urlRoot属性

    当然,模型类还包含一些实用的方法帮助我们开发,这里就不一一介绍,通过API文档你能轻易地理解它们。

     

    展开全文
  • ssd模型图示 模型原理 ssd主要的思想是以cnn做为特征提取网络,例如以resnet50做为提取网络,删除掉resnet后面的全连接层,再增添几层...ssd原论文应该是使用的vgg做为backbone,这里我做了一点修改,使用更优秀的resn
  • 完整模型 IntactModel 在很大程度上受到 Henrik Joreteg 的 HumanModel 的,如果不是迫切需要遗留支持(我在... var Model = new Backbone . IntactModel ( { properties : { 'foo' : { type : 'string' } } } ) ; v
  • backbone.js 在花费了数小时甚至数天的时间之后,为您的Web应用程序添加了令人敬畏的新功能,您终于可以开始实际使用它了。 您将新代码添加到JavaScript库中,构建了候选发布版本,并启动了浏览器,以至于感到惊讶。...
  • 用于测试 Backbone.js 应用程序的装置。 目前处于发展初期。 example目录包含一个用于测试框架的小型 Backbone.js 应用程序。 用法 安装咖啡师 使用 NPM 安装 Barista: npm install baristajs 配置咖啡师 配置咖啡...
  • Backbone

    2012-09-06 23:19:00
    简介 Web 应用程序越来越关注于前端,使用客户端脚本与 Ajax 进行交互。由于 JavaScript 应用程序越来越复杂,如果没有合适的工具和模式,那么JavaScript 代码的高效编写、非重复性和可...模型-视图-控制器 (MVC...
  •  Model是Backbone中所有数据模型的基类,用于封装原始数据,并提供对数据进行操作的方法,我们一般通过继承的方式来扩展和使用它。  如果你做过数据库开发,可能对ORM(对象关系映射)不会陌生,而Backbone中的...
  • 假设您有两种Backbone模型,即Post和User var User = Backbone . Model . extend ( { name : null , email : null } ) ; var Post = Backbone . Model . extend ( { defaults : { title : 'Default Title' } }...
  • 在本教程中,我们将游览著名的MV *框架的基本面Backbone.js的 。 我们会看看模型,视图,集合和模板,并看到构建应用程序时每个如何建立彼此的关闭。 我们还将触及的责任和关注点分离,最终铺平了道路为我们构建了...
  • 最近在慢慢深入Backbone,也试着写一些测试,找一些合适的文档来学习。于是就找到了一个系列的文章 : Testing Backbone applications with Jasmine and Sinon – Part 1 概览 这是第一次展示如何测试Backbone.js...
  • Backbone入门

    2014-02-11 16:00:52
    开始学习 Backbone 如何将模型-视图-控制器 (MVC) 架构引入 Ajax Web 应用程序 如何高效管理 Web 应用程序中的数目众多的 JavaScript 代码行是一个挑战。Asynchronous JavaScript and XML (Ajax) 交互大量...
  • 如何安装 下载mongodb: : 下载 nodejs: ://nodejs.org/ 使用项目在控制台中运行: 1 - npm 安装 2 - 凉亭安装 ...每个模型自己检查。 2.开发要花多少时间? -40 小时或 5 天。 3.你的问题?
  • Arcgis backbone操作感悟

    2020-01-08 17:21:04
    框架采用backbone,backbone依赖于jquery和underscore,主要是使用了backbone的view视图,MVC结构,没有使用model模型,和路由功能,单页面。 地图主要是采用arcgis js来经行的二次开发,客户端绘制图形主要是继承...
  • Backbone.js用于根据模型和视图定义应用程序的结构。 HTML骨架 现在我们知道了组成应用程序的组件,我们可以定义支持它的HTML框架。 到目前为止,还没有什么幻想的,只是一个最小的HTML5文档,一些JavaScript文件...
  • 此外,backbone和grunt-bbb、jquery mobile等开发工具的配合使用,以及jasmine、qunit和sinonjs等测试解决方案。, 本书的作者是知名的javascript专家、谷歌chrome团队的工程师addy osmani。本书适合于javascript...
  • 此外,backbone和grunt-bbb、jquery mobile等开发工具的配合使用,以及jasmine、qunit和sinonjs等测试解决方案。, 本书的作者是知名的javascript专家、谷歌chrome团队的工程师addy osmani。本书适合于javascript...
  • Backbone基础笔记

    2019-09-29 07:54:30
    之前做一个iPad的金融项目的时候有用到Backbone,不过当时去的时候项目已经进入UAT测试阶段了,就只是改了改Bug,对Backbone框架算不上深入了解,但要说我一点都不熟悉那倒也不是,我不太喜欢这么不尴不尬地将就着,...
  • Model关于backbone,最基础的一个东西就是model,这个东西就像是后端开发中的数据库映射那个model一样,也是数据对象的模型,并且应该是和后端的model有相同的属性(仅是需要通过前端来操作的属性)。下面就从实例来...
  • 骨干 概要 实际上,这个插件只是由ESB6(由Brian Mann编写的出色的 )移植而成的。 我分叉他的工作主要是为了增加对UMD的支持,因为我需要它支持...将Backbone.Choosy初始化为模型定义: var Model = Backbone . Mod
  • 用咕unt声运行测试: grunt test 如果有问题,请提交问题,拉取请求等! <3 用法 该库使您可以在集合和模型上覆盖sync()方法,以便将它们保存到localForage而不是REST服务器。 只需使用模型的名称空间覆盖对象...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,218
精华内容 2,887
关键字:

backbone测试模型