首页

如果使用 JavaScript 原型实现继承

seo达人

在这篇文章中,我们将讨论原型以及如何在 JS 中使用它们进行继承。我们还将会看到原型方法与基于类的继承有何不同。


继承

继承是编程语言的一个显著特征,随着面向对象编程语言的引入而出现。这些语言大多是基于类的语言。在这里,类就像一个蓝图,对象是它的展现形式。就是说,要创建一个对象,首先我们必须创建一个类,然后我们可以从一个类创建任意数量的对象。


想象一下,我们有一个表示智能手机的类。这个类具有像其他智能手机一样的可以拍照、有GPS定位等功能。下面是使用 c++ 来描述这样的一个类:


class SmartPhone {

 public:

 void captureImages() {}

}


SmartPhone x;

x.captureImages()

我们创建了一个名为SmartPhone的类,它有一个名为capturePictures的方法用来拍照。


如果我们需要一个iPhone类,它可以捕捉图像和一些特殊的功能,比如面部ID扫描。下面是两种可能的解决方案:


1.将捕获图像功能与其他常见的智能手机功能,以及iPhone的特定功能一起重写到一个新类中。但是这种方法需要更多的时间和精力,并且会引入更多的bug。


重用SmartPhone类中的功能,这就是继承的作用,继承也是重用其他类/对象中功能的一种方式。

这里是我们如何从SmartPhone类中继承capturePictures方法,使用 c++ 实现如下:


class Iphone: public SmartPhone {

 public:

 void faceIDScan() {}

}


Iphone x


x.faceIDScan()


x.captureImages()

上面是一个简单的继承示例。 但是,它表明继承可以使我们以某种方式重用代码,从而使所生成的程序更不易出错,并且花费更少的时间进行开发。


以下是关于类的一些重要信息:


继承该功能的类称为子类

被继承的类称为父类

一个类可以同时从多个类中继承

我们可以具有多个继承级别。 例如,类C继承自类B,而类B继承自类A

值得注意的是,类本身并没有做任何事情。在从类创建对象之前,实际上没有完成任何工作。我们将看到它为什么不同于JavaScript。


大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】。


原型是什么?

在 JS 中,所有对象都有一个特殊的内部属性,该属性基本上是对另一个对象的引用。 此引用取决于对象的创建方式。 在 ECMAScript/JavaScript规范中,它表示为[[Prototype]]。


由于[[Prototype]]链接到一个对象,所以该对象有自己的[[Prototype]]引用。这就是建立原型链的方式。


这个[[Prototype]]链是 JS 中继承的构建块。


__proto__ 对象

为了访问对象的[[Prototype]],大多数浏览器都提供__proto__属性。访问方式如下:


obj.__proto__

需要注意的是,这个属性不是 ECMAScript 标准的一部分,它实际上是由浏览器实现的。


获取和设置原型方法

除了__proto__属性外,还有一种访问[[Prototype]]的标准方法:


Object.getPrototypeOf(obj);

对应的有个类似的方法来设置对象的[[Prototype]]:


Object.setPrototypeOf(obj, prototype);

[[Prototype]]和.prototype属性

[[Prototype]] 只不过是一种用来表示物体原型的标准符号。 许多开发人员将其与.prototype属性混淆,这是完全不同的事情,接着我们来研究一下.prototype属性。


在 JS 中,有许多创建对象的方法。一种方法是使用构造函数,像这样使用new关键字来调用它:


function SmartPhone(os) {

 this.os = os

}


let phone = new SmartPhone('Android')

在控制台打印 phone 对象:


{

 os: "IPhone"

 __proto__{

   constructor: ƒ SmartPhone(os)

  __proto__: Object

 }

}

现在,如果我们希望在phone对象上有一些方法,我们可以在函数上使用.prototype属性,如下所示:


SmartPhone.prototype.isAndroid = function () {

 return this.os === 'Android' || 'android'

}

再次创建phone对象时,打印 phone 对象如下:


{

 os: "Android"

 __proto__{

   isAndroid: ƒ()

   constructor: ƒ SmartPhone(os)

  __proto__: Object

 }

}

我们可以在对象的[[Prototype]]中看到isAndroid()方法。


简而言之,.prototype属性基本上就像由给定的构造函数创建的[[Prototype]]对象的蓝图。 在.prototype属性/对象中声明的所有内容都会在对象的[[Prototype]]中弹出。


实上,如果将 SmartPhone.prototype 与phone 的[[Prototype]]进行比较,就会发现它们是相同的:


console.log(Object.getPrototypeOf(phone) === SmartPhone.prototype);

// true

值得注意的是,我们还可以在构造函数中创建方法:


function ObjectA() {

 this.methodA = function () {}

}


let firstObj = new ObjectA()

console.log(firstObj)

这种方法的问题是当我们初始化一个新对象时。所有实例都有自己methodA的副本。相反,当我们在函数的原型上创建它时,对象的所有实例只共享方法的一个副本,显然使用原型的方式效率会过高。


大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】。


当我们访问属性时这里发生了什么?

当我们访问一个属性以获取它时,会发生以下情况:


JS 引擎查找对象上的属性,如果找到了该属性,然后返回它。否则,JS 引擎将通过查看[[Prototype]]来检查对象的继承属性,如果找到该属性,则返回它,否则,它会查找 [[Prototype]]的[[Prototype]]。 找到属性或没有[[Prototype]]时,该链结束,这意味着我们已经到达原型链的末端。


当我们设置/创建属性时,JS 总是在对象本身上进行设置。 即使[[Prototype]]链上存在相同的属性,下面是一个例子:


function MyObject() {}

MyObject.prototype.propA = 10; // 在原型上创建属性


let myObject = new MyObject();

console.log(myObject.propA); // [[Prototype]]上的属性

// 10


myObject.propA = 20; // 对象的属性

console.log(myObject.propA);

// 20

在上面的示例中,我们创建了一个构造函数,该函数的[[Prototype]]上具有属性propA。 当我们尝试对其进行读取操作时,会在控制台中看到该值。 但是,当我们尝试在对象本身上设置相同的属性时;JS 使用给定值在对象上创建一个新属性。 现在,如果我们不能直接访问[[Prototype]]上的属性。


值得注意的是,普通对象的[[Prototype]]链的末尾是内置的Object.prototype。 这就是为什么大多数对象共享许多方法(例如toString())的原因。 因为它们实际上是在Object.prototype上定义的。


使用原型继承的各种方法

在 JS 中,无论我们如何创建对象,只有原型继承,但这些方式还有一些区别,来看看:


对象字面量

在JavaScript中创建对象的最简单方法是使用对象字面量:


let obj = {}

如果在浏览器的控制台中打印obj,我们将看到以下内容:


clipboard.png


基本上,所有用文字面量创建的对象都继承了Object.prototype的属性。


需要注意的是__proto__对象引用了创建它的构造函数。 在这种情况下,constructor属性指向Object构造函数。


使用对象构造函数

另一种不太常见的创建对象的方法是使用对象构造函数。JS 提供了一个名为Object的内置构造函数方法来创建对象。


let obj = new Object();

这种方法的结果与对象字面量的方式相同。它从Object.prototype继承属性。因为我们使用Object作为构造函数。


Object.create 方法

使用此辅助方法,我们可以创建一个带有[[Prototype]]的对象,如下所示:


let SmartPhone = {

 captureImages: function() {}

}


let Iphone = Object.create(SmartPhone)


Iphone.captureImages()

这是在 JS 中使用继承的最简单方法之一。猜猜我们如何在没有任何[[Prototype]]引用的情况下创建对象?


构造方法

与 JS 运行时提供的对象构造函数相似。 我们还可以创建自己的构造函数,以创建适合我们需求的对象,如下所示:


function SmartPhone(os) {

 this.os = os;

}


SmartPhone.prototype.isAndroid = function() {

 return this.os === 'Android';

};


SmartPhone.prototype.isIOS = function() {

 return this.os === 'iOS';

};

现在,我们想创建一个iPhone类,它应该有'iOS'作为它 os 属性的值。它还应该有faceIDScan方法。


首先,我们必须创建一个Iphone构造函数,在其中,我们应该调用SmartPhone构造函数,如下所示:


function Iphone() {

  SmartPhone.call(this, 'iOS');

}

这会将Iphone构造函数中的this.os属性设置为’iOS‘。


之所以调用SmartPhone.call方法,是因为我们需要更改 this 值以引用Iphone。 这类似于在面向对象的世界中调用父级的构造函数。


接下来的事情是,我们必须从SmartPhone构造函数继承方法。 我们可以在此处使用Object.create朋友,如下所示:


Iphone.prototype = Object.create(SmartPhone.prototype);

现在,我们可以使用.prototype为Iphone添加方法,如下所示:


Iphone.prototype.faceIDScan = function() {};

最后,我们可以使用Iphone创建一个对象,如下所示:


let x = new Iphone();


// calling inherited method

console.log(x.isIOS()):

// true


ES6 class

使用ES6,整个过程非常简单。 我们可以创建类(它们与C ++或其他任何基于类的语言中的类不同,只是在原型继承之上的语法糖),然后从其他类派生新的类。


下面是我们如何在ES6中创建类:


class SmartPhone {

 constructor(os) {

   this.os = os;

 }

 isAndroid() {

   return this.os === 'Android';

 }

 isIos() {

   return this.os === 'iOS';

 }

};

现在,我们可以创建一个派生自SmartPhone的新类,如下所示:


class Iphone extends SmartPhone {

  constructor() {

    super.call('iOS');

  }

  faceIDScan() {}

}

我们不是调用SmartPhone.call,而是调用super.call。 在内部,JavaScript引擎会自动为我们执行此操作。


最后,我们可以使用Iphone创建一个对象,如下所示


let x = new Iphone();


x.faceIDScan();


// calling inherited method

console.log(x.isIos()):

// true

该ES6示例与先前的构造方法示例相同。 但是阅读和理解起来要干净得多。


原文:https://javascript.info/proto...


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

Emscripten教程之emcc编译命令

seo达人

语法


emcc [options] file ...

这个输入文件file,既可以是clang可以编译的C/C++语言,也可以是二进制形式的llvm bitcode或者人类可读形式的llvm assembly文件。

参数

大部分clang或者gcc的选项(option)都是可以工作的,比如:

# 显示信息 emcc --help # 显示编译器版本信息 emcc --version

如果想看当前Emscripten中clang版本支持的全部选项列表,可以直接使用命令:
clang --help.

emcc修改的或者emcc中新的选项列在下面:

首先是一些编译优化flag,它们-O0,-O1,-O2,-Os,-Oz,-O3。

-O0:
不进行编译优化(这是默认情况)。当你刚开始移植项目是推荐使用它,因为它会包含许多断言。

-O1:
简单优化。推荐你在既想缩短编译时间又想编译优化时使用。它毕竟比-O2级别的优化编译起来快多了。它会进行asm.js和llvm的-O1进行优化,它会relooping,会删除运行时断言和C++异常捕获,它也会使得-s ALIASING_FUNCTION_POINTERS=1。

想要C++异常捕获重新可用,请设置:-s DISABLE_EXCEPTION_CATCHING=0。

-O2:
和-O1类似,不过多了JavaScript级别的优化以及一些llvm -O3的优化项。当你想发布项目的时候,推荐使用本级别优化。

-O3:
和-O2类似,不过比-O2又多了一些JavaScript优化,而且编译时间明显比-O2长。这个也推荐在发布版本的时候使用。

-Os:
和-O3类似,不过增加了额外的优化以减小生成的代码体积,代价是比-O3性能差一点。-Os优化会同时影响llvm bitcode 和JavaScript文件的生成。

-Oz:
和-Os类似,不过进一步减小了代码体积。

-s OPTION=VALUE
传给编译器的所有涉及到JavaScript代码生成的选项。选项列表,请见settings.js

对于某个选项的值,不仅可以直接在emcc命令行里面设定,也可以把他们写成json文件。比如下面,就是将DEAD_FUNCTIONS选项的值放到了path/to/file文件里,emcc里面传这个文件的路径。

-s DEAD_FUNCTIONS=@/path/to/file
note: 1、文件内容可以是:["_func1","_func2"]; 2、文件路径必须是绝对的,不能是相对的。

-g:
这是保留调试信息flag。

  • 如果只是编译到bitcode,那就和clang和gcc中的-g一样。
  • 如果是要编译到JavaScript,-g就等于-g3。

-g<level>
控制打印的调试信息数量,每一个level都是在前一个level的基础上编译的:

  • -g0:不保留调试信息。
  • -g1:保留空格,不压缩。
  • -g2:保留函数名。
  • -g3:保留变量名,与-g同。变量名一般不是必须编译后保留的,但是如果保留了,可以推断变量的目的,对吧。
  • -g4:保留llvm 调试信息,这是能调试的最高级别。
note:
优化级别越高,编译时间越长

--profiling:

--profiling-funcs:

--tracing:
启用Emscripten的tracing API。

--emit-symbol-map:

--js-opts<level>:
允许JavaScript优化,有两个值:
0:不允许JavaScript优化器允许;
1:使用JavaScript优化器。
通常用不到我们设置这一项, 因为设置-O后面的level的时候,这个项就能顺便取到一个合适的值。

note:
有些选项会重写这个flag的值,比如EMTERPRETIFY, DEAD_FUNCTIONS, OUTLINING_LIMIT, SAFE_HEAP 和 SPLIT_MEMORY会将js-opts=1,因为他们依赖js优化器。

--llvm-opts<level>:
启用llvm优化。它的取值有有:

  • 0:不使用llvm优化
  • 1:llvm -O1优化
  • 2:llvm -O2优化
  • 3:llvm -O3优化

和--js-opts<level>一样,通常用不到我们设置这一项, 因为设置-O后面的level的时候,这个项就能顺便取到一个合适的值。

--llvm-lto<level>:
启用llvm 连接时 优化。可以取值0,1,2,3。

--closure <on>:
运行压缩编译器(Closure Compiler),可能的取值有,0,1,2:

  • 0:是不启用压缩编译器。
  • 1:启用。
  • 2:启用。

--pre-js <file>
生成代码前,指定一个要把内容添加进来的文件。

--post-js <file>
生成代码后,指定一个要把内容添加进来的文件。

--embed-file <file>
指定一个带路径的文件嵌入到编译生成的js代码里。路径是相对于编译时的当前路径。如果传的是一个目录,则目录下所有文件的内容都会被嵌入到将来生成的js代码中。

--preload-file <name>
异步运行编译代码前,指定一个预加载的文件。路径是相对于编译时的当前路径。如果传的是一个目录,则目录下所有文件的内容都会被预加载到一个.data文件中。

--exclude-file <name>
从 –embed-file and –preload-file后面的目录中排除一些文件,支持使用通配符*。

--use-preload-plugins
告诉文件打包器当文件加载时,运行预加载插件。它用来执行诸如使用浏览器解码器解码图片和音频等。

--shell-file <path>
指定要生成HTML的模板文件。

--source-map-base <base-url>

--minify 0
等于-g1。

--js-transform <cmd>
优化之前,生成代码之后,设定这一条命令。这条命令可以让你修改JavaScript代码。之后,编译器会将修改的和未修改的一起进行编译优化。

--bind
启用bingdings编译源代码。bingings是Emscripten中连接C++和JavaScript代码的一类API。

--ignore-dynamic-linking
告诉编译器忽视动态链接,之后用户就得手动链接到共享库。

--js-library <lib>
定义除了核心库(src/library_*)以外的js库。

-v
打开详细输出。
这个设置为把-v传给clang,并且启用EMCC_DEBUG生成编译阶段的中间文件。它也会运行Emscripten关于工具链的内部的完整性检查。

tip: emcc -v是诊断错误的有用工具,不管你是否附加其他参数。

--cache

--clear-cache

--clear-ports

--show-ports

--save-bc PATH

--memory-init-file <on>
规定是否单独生成一个内存初始化文件。取值包括0和1.

  • 0:不单独生成.mem文件。
  • 1:单独生成.mem文件。

-Wwarn-absolute-paths
启用在-I和-L命令行指令中使用绝对路径的警告。这是用来警告无意中使用了绝对路径的。在引用非可移植的本地系统头文件时,使用绝对路径有时是很危险的。

--proxy-to-worker

--emrun
使生成的代码能够感知emrun命令行工具。当运行emran生成的应用程序时,这样设置就允许stdout、stderr和exit(returncode)被捕获。

--cpuprofiler
在生成的页面上嵌入一个简单的CPU分析器。使用这个来执行粗略的交互式性能分析。

--memoryprofiler
在生成的页面上嵌入内存分配跟踪器,使用它来分析应用程序Emscripten堆的使用情况。

--threadprofiler
在生成的页面上嵌入一个线程活动分析器。当进行多线程编译时,使用它来分析多线程应用程序。
--em-config

--default-obj-ext .ext

--valid-abspath path
设置一个绝对路径的白名单,以防止关于绝对路径的警告。

-o <target>
编译输出的文件格式。target可以取值为:

  • name.js:JavaScript文件;
  • name.html:HTML+js文件。把js单独生成是为了减小页面加载时间。
  • name.bc:llvm bitcode。这是默认值。
  • name.o:和上面一样。
note:
如果你用了--memory-init-file,则还会从js文件中再单独分出一部分代码为.mem文件。

-c
生成llvm bitcode代码,而不是JavaScript。

--separate-asm
把asm.js文件单独生成到一个文件中。这样可以减少启动时的内存加载。

--output_eol windows|linux
规定生成的文本文件的行尾,如果是–output_eol windows,就是windows rn行尾,如果是–output_eol linux,则生成Linux行尾的文本文件。

--cflags

环境变量


emcc会受到几个环境变量的影响,如下:

  • EMMAKEN_JUST_CONFIGURE
  • EMMAKEN_JUST_CONFIGURE_RECURSE
  • EMCONFIGURE_JS
  • EMCONFIGURE_CC
  • EMMAKEN_CXX
  • EMMAKEN_COMPILER
  • EMMAKEN_CFLAGS
  • EMCC_DEBUG

这几个里面比较有意思的是EMCC_DEBUG。比如,如果你在编译之前设置set EMCC_DEBUG=1,那么编译的时候会把编译过程的调试信息和编译各个阶段的中间文件输出到一个临时目录,这算是给开发者提供一些编译期间的帮助或者说调试信息吧。


Emscripten主题系列文章是emscripten中文站点的一部分内容。
第一个主题介绍代码可移植性与限制
第二个主题介绍Emscripten的运行时环境
第三个主题第一篇文章介绍连接C++和JavaScript
第三个主题第二篇文章介绍embind
第四个主题介绍文件和文件系统
第六个主题介绍Emscripten如何调试代码

vue + vuex + koa2开发环境搭建及示例开发

seo达人

写在前面

这篇文章的主要目的是学会使用koa框架搭建web服务,从而提供一些后端接口,供前端调用。
搭建这个环境的目的是: 前端工程师在跟后台工程师商定了接口但还未联调之前,涉及到向后端请求数据的功能能够走前端工程师自己搭建的http路径,而不是直接在前端写几个死数据。即,模拟后端接口。

当然在这整个过程(搭建环境 + 开发示例demo)中,涉及到以下几点知识点。
包括:

  • koa2的知识点
  • node的知识点
  • 跨域问题
  • fetch的使用
  • axios的使用
  • promise的涉及
  • vuex -> state、mutations、actions的使用

第一部分:环境搭建

vue + vuex环境

首先是vue + vue-router + vuex的环境。我们用vue-cli脚手架生成项目,会用vue的同学对这块应该很熟了。

// 全局安装脚手架工具 npm i vue-cli -g // 验证脚手架工具安装成功与否 vue --version // 构建项目 vue init webpack 项目名 // 测试vue项目是否运行成功 npm run dev

因为脚手架生成的vue项目不包含vuex,所以再安装vuex。

// 安装vuex npm i vuex --save

koa2环境

前端项目构建好了,就开始构建我们的后端服务。

首先在你的开发工具(不管是webstorm还是sublime)里新建一个目录,用来搭建基于koa的web服务。

在这里,我们不妨给这个目录起名为koa-demo。

然后执行:

// 进入目录 cd koa-demo // 生成package.json npm init -y // 安装以下依赖项 npm i koa npm i koa-router npm i koa-cors

安装好koa和两个中间件,环境就算搭建完成了。

第二部分:示例开发

搭建环境是为了使用,所以我们立马来写一个demo出来。
demo开发既是一个练习如何在开发环境中写代码的过程,反过来,也是一个验证环境搭建的对不对、好不好用的过程。

后端接口开发

本例中,后端我们只提供一个服务,就是给前端提供一个返回json数据的接口。代码中包含注释,所以直接上代码。

server.js文件

 // server.js文件 let Koa = require('koa'); let Router = require('koa-router'); let cors = require('koa-cors'); // 引入modejs的文件系统API let fs = require('fs'); const app = new Koa(); const router = new Router(); // 提供一个/getJson接口 router
    .get('/getJson', async ctx => { // 后端允许cors跨域请求 await cors(); // 返回给前端的数据 ctx.body = JSON.parse(fs.readFileSync( './static/material.json'));

    }); // 将koa和两个中间件连起来 app.use(router.routes()).use(router.allowedMethods()); // 监听3000端口 app.listen(3000);

这里面用到了一个json文件,在'./static/material.json'路径,该json文件的代码是:

// material.json文件 [{ "id": 1, "date": "2016-05-02", "name": "张三", "address": "北京 清华大学",
}, { "id": 2, "date": "2016-05-04", "name": "李四", "address": "上海 复旦大学",
}, { "id": 3, "date": "2016-05-01", "name": "王五", "address": "广东 中山大学",
}, { "id": 4, "date": "2016-05-03", "name": "赵六", "address": "广东 深圳大学",
}, { "id": 5, "date": "2016-05-05", "name": "韩梅梅", "address": "四川 四川大学",
}, { "id": 6, "date": "2016-05-11", "name": "刘小律", "address": "湖南 中南大学",
}, { "id": 7, "date": "2016-04-13", "name": "曾坦", "address": "江苏 南京大学",
}] 

然后我们是用以下命令将服务启动

node server.js

测试接口是否良好

打开浏览器,输入http://127.0.0.1:3000/getJson。看一看页面上是否将json文件中的json数据显示出来,如果能够显示出来,则说明这个提供json数据的服务,我们已经搭建好了。

前端调用后端接口示例

为突出重点,排除干扰,方便理解。我们的前端就写一个组件,组件有两部分:首先是一个按钮,用来调用web服务的getJson接口;然后是一个内容展示区域,拿到后端返回的数据以后,将其在组件的这块区域显示出来

首先我们看组件文件

<template> <div class="test"> <button type="button" @click="getJson">从后端取json</button> <div class="showJson">{{json}}</div> </div> </template> <script> import {store} from '../vuex' export default { computed: {
          json(){ return store.state.json;
          }
        }, methods: {
          getJson(){
              store.dispatch("getJson");
          }
        }
    } </script> <style scoped> .showJson{ width:500px; margin:10px auto; min-height:500px; background-color: palegreen;
  } </style> 

非常简单,就不多解释了。
然后看我们的vuex文件

import Vue from 'vue' import Vuex from 'vuex';

Vue.use(Vuex) const state = { json: [],
}; const mutations = {
  setJson(state, db){
    state.json = db;
  }
} const actions = {
  getJson(context){ // 调用我们的后端getJson接口 fetch('http://127.0.0.1:3000/json', { method: 'GET', // mode:'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json',
      },
    }).then(function (res) { if(res.status === 200){ return res.json()
      }
    }).then(function (json) { //console.log(typeof Array.from(json), Array.from(json)); context.commit('setJson', Array.from(json));
    })
  }
}; export const store = new Vuex.Store({ state: state, mutations: mutations, actions: actions,
})

ok, 代码撸完了,获取后端数据之前是这样的。

获取后端数据之后是这样的。

说说axios

想要把本demo的fetch改为axios方式,要做的工作有以下几处:
1、安装axios、在vuex文件引用axios

npm i axios import axios from 'axios'

2、将fetch部分代码替换为:

const actions = {
  getJson(context){
    axios.get('/json', { method: 'GET', // mode:'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json',
      },
    }).then(function (res) { if(res.status === 200){ return res.data
      }
    }).then(function (json) { //console.log(typeof Array.from(json), Array.from(json)); context.commit('setJson', Array.from(json));
    })
  }
};

3、又会遇到跨域,在webpack中修改,路径config/index.js文件中添加proxyTable项的配置:

proxyTable: { '/json': { target: 'http://127.0.0.1:3000', changeOrigin: true, pathRewrite: { '^/json': '/json' }
      }
    },

后记

基于vue脚手架搭建的项目,模拟异步取数据,也可以直接在脚手架生成的static文件夹下放置数据,假装是后台拿过来的数据。

不过搭建一个基于express或者koa的web服务,确实也该是一个前端工程师应该掌握的。

OK,以上就是全文了。
如果这篇文章使你有所收获,不胜荣幸。
欢迎点赞,以期能帮助更多同学!

深入理解vue中的slot与slot-scope

seo达人

写在前面

vue中关于插槽的文档说明很短,语言又写的很凝练,再加上其和methods,data,computed等常用选项使用频率、使用先后上的差别,这就有可能造成初次接触插槽的开发者容易产生“算了吧,回头再学,反正已经可以写基础组件了”,于是就关闭了vue说明文档。

实际上,插槽的概念很简单,下面通过分三部分来讲。这个部分也是按照vue说明文档的顺序来写的。

进入三部分之前,先让还没接触过插槽的同学对什么是插槽有一个简单的概念:插槽,也就是slot,是组件的一块HTML模板,这块模板显示不显示、以及怎样显示由父组件来决定。 实际上,一个slot最核心的两个问题这里就点出来了,是显示不显示怎样显示

由于插槽是一块模板,所以,对于任何一个组件,从模板种类的角度来分,其实都可以分为非插槽模板插槽模板两大类。
非插槽模板指的是html模板,指的是‘div、span、ul、table’这些,非插槽模板的显示与隐藏以及怎样显示由插件自身控制;插槽模板是slot,它是一个空壳子,因为它显示与隐藏以及最后用什么样的html模板显示由父组件控制。但是插槽显示的位置确由子组件自身决定,slot写在组件template的哪块,父组件传过来的模板将来就显示在哪块

单个插槽 | 默认插槽 | 匿名插槽

首先是单个插槽,单个插槽是vue的官方叫法,但是其实也可以叫它默认插槽,或者与具名插槽相对,我们可以叫它匿名插槽。因为它不用设置name属性。

单个插槽可以放置在组件的任意位置,但是就像它的名字一样,一个组件中只能有一个该类插槽。相对应的,具名插槽就可以有很多个,只要名字(name属性)不同就可以了。

下面通过一个例子来展示。

父组件:


    
  1. <template>
  2. <div class="father">
  3. <h3>这里是父组件</h3>
  4. <child>
  5. <div class="tmpl">
  6. <span>菜单1</span>
  7. <span>菜单2</span>
  8. <span>菜单3</span>
  9. <span>菜单4</span>
  10. <span>菜单5</span>
  11. <span>菜单6</span>
  12. </div>
  13. </child>
  14. </div>
  15. </template>

子组件:


    
  1. <template>
  2. <div class="child">
  3. <h3>这里是子组件</h3>
  4. <slot></slot>
  5. </div>
  6. </template>

在这个例子里,因为父组件在<child></child>里面写了html模板,那么子组件的匿名插槽这块模板就是下面这样。也就是说,子组件的匿名插槽被使用了,是被下面这块模板使用了。


    
  1. <div class="tmpl">
  2. <span>菜单1</span>
  3. <span>菜单2</span>
  4. <span>菜单3</span>
  5. <span>菜单4</span>
  6. <span>菜单5</span>
  7. <span>菜单6</span>
  8. </div>

最终的渲染结果如图所示:



    
  1. 注:所有demo都加了样式,以方便观察。其中,父组件以灰色背景填充,子组件都以浅蓝色填充。

具名插槽

匿名插槽没有name属性,所以是匿名插槽,那么,插槽加了name属性,就变成了具名插槽。具名插槽可以在一个组件中出现N次。出现在不同的位置。下面的例子,就是一个有两个具名插槽单个插槽的组件,这三个插槽被父组件用同一套css样式显示了出来,不同的是内容上略有区别。

父组件:


    
  1. <template>
  2. <div class="father">
  3. <h3>这里是父组件</h3>
  4. <child>
  5. <div class="tmpl" slot="up">
  6. <span>菜单1</span>
  7. <span>菜单2</span>
  8. <span>菜单3</span>
  9. <span>菜单4</span>
  10. <span>菜单5</span>
  11. <span>菜单6</span>
  12. </div>
  13. <div class="tmpl" slot="down">
  14. <span>菜单-1</span>
  15. <span>菜单-2</span>
  16. <span>菜单-3</span>
  17. <span>菜单-4</span>
  18. <span>菜单-5</span>
  19. <span>菜单-6</span>
  20. </div>
  21. <div class="tmpl">
  22. <span>菜单->1</span>
  23. <span>菜单->2</span>
  24. <span>菜单->3</span>
  25. <span>菜单->4</span>
  26. <span>菜单->5</span>
  27. <span>菜单->6</span>
  28. </div>
  29. </child>
  30. </div>
  31. </template>

子组件:


    
  1. <template>
  2. <div class="child">
  3. // 具名插槽
  4. <slot name="up"></slot>
  5. <h3>这里是子组件</h3>
  6. // 具名插槽
  7. <slot name="down"></slot>
  8. // 匿名插槽
  9. <slot></slot>
  10. </div>
  11. </template>

显示结果如图:


可以看到,父组件通过html模板上的slot属性关联具名插槽。没有slot属性的html模板默认关联匿名插槽。

作用域插槽 | 带数据的插槽

最后,就是我们的作用域插槽。这个稍微难理解一点。官方叫它作用域插槽,实际上,对比前面两种插槽,我们可以叫它带数据的插槽。什么意思呢,就是前面两种,都是在组件的template里面写


    
  1. 匿名插槽
  2. <slot></slot>
  3. 具名插槽
  4. <slot name="up"></slot>

但是作用域插槽要求,在slot上面绑定数据。也就是你得写成大概下面这个样子。


    
  1. <slot name="up" :data="data"></slot>
  2. export default {
  3. data: function(){
  4. return {
  5. data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
  6. }
  7. },
  8. }

我们前面说了,插槽最后显示不显示是看父组件有没有在child下面写模板,像下面那样。


    
  1. <child>
  2. html模板
  3. </child>

写了,插槽就总得在浏览器上显示点东西,东西就是html该有的模样,没写,插槽就是空壳子,啥都没有。
OK,我们说有html模板的情况,就是父组件会往子组件插模板的情况,那到底插一套什么样的样式呢,这由父组件的html+css共同决定,但是这套样式里面的内容呢?

正因为作用域插槽绑定了一套数据,父组件可以拿来用。于是,情况就变成了这样:样式父组件说了算,但内容可以显示子组件插槽绑定的。

我们再来对比,作用域插槽和单个插槽和具名插槽的区别,因为单个插槽和具名插槽不绑定数据,所以父组件是提供的模板要既包括样式由包括内容的,上面的例子中,你看到的文字,“菜单1”,“菜单2”都是父组件自己提供的内容;而作用域插槽,父组件只需要提供一套样式(在确实用作用域插槽绑定的数据的前提下)。

下面的例子,你就能看到,父组件提供了三种样式(分别是flex、ul、直接显示),都没有提供数据,数据使用的都是子组件插槽自己绑定的那个人名数组。

父组件:


    
  1. <template>
  2. <div class="father">
  3. <h3>这里是父组件</h3>
  4. <!--第一次使用:用flex展示数据-->
  5. <child>
  6. <template slot-scope="user">
  7. <div class="tmpl">
  8. <span v-for="item in user.data">{{item}}</span>
  9. </div>
  10. </template>
  11. </child>
  12. <!--第二次使用:用列表展示数据-->
  13. <child>
  14. <template slot-scope="user">
  15. <ul>
  16. <li v-for="item in user.data">{{item}}</li>
  17. </ul>
  18. </template>
  19. </child>
  20. <!--第三次使用:直接显示数据-->
  21. <child>
  22. <template slot-scope="user">
  23. {{user.data}}
  24. </template>
  25. </child>
  26. <!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
  27. <child>
  28. 我就是模板
  29. </child>
  30. </div>
  31. </template>

子组件:


    
  1. <template>
  2. <div class="child">
  3. <h3>这里是子组件</h3>
  4. // 作用域插槽
  5. <slot :data="data"></slot>
  6. </div>
  7. </template>
  8. export default {
  9. data: function(){
  10. return {
  11. data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
  12. }
  13. }
  14. }

结果如图所示:

github

以上三个demo就放在GitHub了,有需要的可以去取。使用非常方便,是基于vue-cli搭建工程。

https://github.com/cunzaizhuyi/vue-slot-demo

uni-app uni.request接口封装

seo达人

uni-app uni.request接口封装

今天在做uni-app项目时,发现在uni-app中 调取后台接口需要大量的重复编辑,就在想能不能封装一个如同Vue项目中的this.$axios.get(url,data).then();格式,这样就减少了很多代码重复!!

封装为如同this.$axios.get(url,data).then();格式

第一步、

我们先在index首页中的组件部分,创建一个js文件;





第二步、

我们在uni-app的入口文件中引入request.js文件;

在入口文件中挂载到uni-app实例上;





第三步、

开始接口封装:

(以下为js文件代码)



//先把接口暴露出去

export default{

//我们先定一个uni-app方法 以便于以下操作使用uni-app调取接口时便利

request(options){

///我们使用Promise方法来实现调用接口时后面多个.then()的方法

//只有Promise能实现如同$axios后面连续多个.then()的方法

return new Promise((reslove,reject)=>{

uni.request({

...options,

success:res=>{

//判断我们在使用封装的自定义时第三个参数是否为native

//当native为true时 我们返回原数据

if(options.native){

reslove(res)

}

//当native为false时 我们直接返回data中的数据

if(res.statusCode === 200){

reslove(res.data)

}else{

//加入接口参数错误或接口地址错误时 我们返回原错误提示

reject(res)

}

}

})

})

},

//在方法中 第二个参数和第三个参数用ES6新语法来添加默认值

//接口调取get方法

get(url,data={},options={}){

//我们把传过来的参数赋给options,这样我们在使用uni-app

//this.request()方法时 传递一个参数就可以

options.url = url;

options.data = data;

options.method = 'get';

//调用上面自己定义的this.request()方法传递参数

return this.request(options)

},

//接口调取post方法

post(url,data={},options={}){

options.url = url;

options.data = data;

options.method = 'post';

return this.request(options)

}

}



这样我们就已经封装完成啦,接下来就是 在页面内使用!

第四步、

我们可以在页面中来调取已经封装好的自定义事件啦



例一:

个人建议使用ES6新语法 箭头函数 不然使用this还要重新在外面声明定义,太麻烦了,使用箭头函数就会方便很多



// 已封装好的接口方法

//本案例调取接口时 没有参数上传 直接调用的

//这样使用方法时只传递了一个参数,也就是接口地址

//第二个参数没有写,默认为空;假如有参数的话 可以直接填写

//后面的参数都为接口内已经定义好的默认值:{}空对象

//里面的res为接口返回数据中的data里面的内容

this.$H.get('/api/getIndexCarousel.jsp').then(res=>{

//res打印出来是接口返回数据中data里面的数据

console.log(res)

//赋给数据区的变量,方便本页面使用

this.swiperData = res

});



例二、



// 已封装好的接口方法

//本案例使用时 传递了三个参数

//第一个为:接口地址

//第二个为:调取接口传递的参数,方法使用时不用传参,写空对象就好

//第三个为:自定义事件中 native 的属性 若为true 则返回原数据

//若想返回原数据,必须要填写第二个参数,若没有参数,也要写空对象

//因为方法调用时 是按照传参顺序调用的,若不写 参数传递就会出错

this.$H.get('/api/getIndexCarousel.jsp',{},{

native:true

}).then(res=>{

//res打印出来的数据是接口返回来的原数据

console.log(res)

//赋给数据区的变量,方便本页面使用

this.swiperData = res

});




每天学习一个Android中的常用框架——1.Litepal

seo达人

文章目录

1.简介

2.特性

3.演示

3.1 集成

3.2 配置

3.3 创建数据库

3.4 升级数据库

3.5 插入数据

3.6 查询数据

3.7 更新数据

3.8 删除数据

4.版本异同

5.源码地址

1.简介

Litepal——作为带我入行的第一本教学书籍《Android第一行代码》的作者郭霖老师所写出来的持久化框架,几乎算是我接触Android世界之后第一个遇到的框架,故将该框架列为一系列学习框架博客的首位。

根据Litepal的GitHub主页:Litepal,可以看到该框架的一些简介:



LitePal is an open source Android library that allows developers to use SQLite database extremely easy. You can finish most of the database operations without writing even a SQL statement, including create or upgrade tables, crud operations, aggregate functions, etc. The setup of LitePal is quite simple as well, you can integrate it into your project in less than 5 minutes.



事实上,正如这段简介所说,集成Litepal相当简单,不需要超过五分钟时间。使用Litepal,也适合对sql语言还不熟悉的开发者快速上手。



2.特性

让我们继续浏览Litepal的GitHub主页,可以发掘Litepal的一些特性:



Using object-relational mapping (ORM) pattern.

Almost zero-configuration(only one configuration file with few properties).

Maintains all tables automatically(e.g. create, alter or drop tables).

Multi databases supported.

Encapsulated APIs for avoiding writing SQL statements.

Awesome fluent query API.

Alternative choice to use SQL still, but easier and better APIs than the originals.

More for you to explore.

用大白话来描述的话,可以列举如下:



Litepal使用了ORM(对象关系映射)模型

Litepal几乎是无配置的,仅需极少的配置文件

Litepal几乎包括所有的CRUD操作,也支持多张表格的操作

Litepal可以仅调用api进行CRUD操作而避免编写sql语句

总之,看到Litepal具有这么多良好的特性,读者是否心动了呢。理论的话不多说,我们现在就开始正式地使用Litepal进行数据库的相关操作

PS:如果有曾经学习过Java的ORM框架——Mybatis的读者,应该不会对Litepal的使用太陌生,因为它们都使用了xml文件进行相应的配置



3.演示

3.1 集成

现在Android框架的集成相比于IDE还为ADT的时代,要方便了许多。原因是现在的主流IDE是Android Studio,而AS默认使用了Gradle进行版本的配置管理,这让集成框架变得简单了许多。

在build.gradle下,添加以下语句,然后重新sync,即可将Litepal集成到你的项目中:



implementation 'org.litepal.android:java:3.0.0'

1

当然,目前Android的主流开发语言,除了Java之外,还有Kotlin,Litepal同样具有Kotlin版本的(这里的演示仅针对Java,Kotlin版本的异曲同工)依赖:



implementation 'org.litepal.android:kotlin:3.0.0'

1

可以根据个人需求进行配置。



3.2 配置

集成了Litepal之后,要想正式使用它还需要进行一些配置



在assets目录下新建litepal.xml,作为Litepal的全局配置文件,相应的条目信息已作出注释,代码如下:

<?xml version="1.0" encoding="utf-8"?>

<litepal>

    <!--  数据库名  -->

    <dbname value="androidframelearn"/>



    <!--  数据库版本号  -->

    <version value="1"/>



    <!--  指定映射模型  -->

    <list>

       

    </list>



    <!--  指定文件的存储方式  -->

    <!--  <storage value="external" />-->

</litepal>



在你的应用下配置Litepal,有两种方式可以实现:

修改清单文件,将你的应用名修改为:android:name="org.litepal.LitePalApplication"

新建一个自己写的MyOwnApplication类,然后将清单文件中的应用名定位到该类,即:android:name="com.example.MyOwnApplication",然后再编写MyOwnApplication类,代码如下:

public class MyOwnApplication extends Application {



@Override

public void onCreate() {

    super.onCreate();

    LitePal.initialize(this);

}

...

}



两种方式亦可,Litepal的作者建议若使用第二种方式,需要尽快地调用LitePal.initialize(this);所以将其放在onCreate()方法是最好的。



3.3 创建数据库

刚才在介绍的时候已经说过,Litepal采取的是对象关系映射(ORM)的模式,那么什么是对象关系映射呢?简单点说,我们使用的编程语言是面向对象语言,而使用的数据库则是关系型数据库,那么将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是对象关系映射了。

不过你可千万不要小看对象关系映射模式,它赋予了我们一个强大的功能,就是可以用面向对象的思维来操作数据库,而不用再和SQL语句打交道了,不信的话我们现在就来体验一下。像往常使用SQLiteOpenHelper类,为了创建一张Book表需要先分析表中应该包含哪些列,然后再编写出一条建表语句,最后在自定义的SQLiteOpenHelper中去执行这条建表语句。但是使用LitePal,你就可以用面向对象的思维来实现同样的功能了,定义一个Book类,代码如下所示:



package com.androidframelearn.dao_litapal;



import org.litepal.crud.LitePalSupport;



public class Book extends LitePalSupport {

    private int id;

    private String author;

    private double price;

    private int pages;

    private String name;

    public int getId(){

        return id;

    }

    public void setId(int id){

        this.id = id;

    }



    public String getAuthor(){

        return author;

    }

    public void setauthor(String author){

        this.author = author;

    }



    public double getPrice(){

        return price;

    }

    public void setPrice(double price){

        this.price = price;

    }



    public int getPages(){

        return pages;

    }

    public void setPages(int pages){

        this.pages = pages;

    }



    public String getName(){

        return name;

    }

    public void setName(String name){

        this.name = name;

    }

}



这里使用标签来声明我们要配置的映射模型类,注意一定要使用完整的类名。不管有多少模型类需要映射,都使用同样的方式配置在标签下即可。

没错,这样就已经把所有工作都完成了,现在只要进行任意一次数据库的操作,BookStore.db数据库应该就会自动创建出来。为了更好地演示代码,我们将布局文件所需要的功能一次性编写好,activity_main.xml代码如下:



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".MainActivity"

    android:orientation="vertical">



    <Button

        android:id="@+id/btn_db_create"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="创建数据库"/>



    <Button

        android:id="@+id/btn_db_query"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="查询数据"/>



    <Button

        android:id="@+id/btn_db_insert"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="插入数据"/>



    <Button

        android:id="@+id/btn_db_update"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="更新数据"/>



    <Button

        android:id="@+id/btn_db_delete"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="删除数据"/>



</LinearLayout>





接下来,修改MainActivity,除了给按钮注册点击事件,还需要编写不同的方法代表不同的逻辑,其中,创建数据库的方法代码如下:



private void createDBbyLitePal() {

        btn_db_create.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Log.i(TAG,"创建数据库成功");

                LitePal.getDatabase();

            }

        });

    }



仅仅通过点击按钮,调用LitePal.getDatabase();这句api,就可以创建出数据库,让我们实际进入项目中尝试一下吧!点击该按钮,然后查看控制台,如图所示:



出现该句日记,说明数据库创建成功,接下来我们看看这个数据库是否按照我们所设置好的格式创建出来了,进入data/data/你的项目包名/databases,即可查看到该数据库已经放置到该目录下,如图所示:





3.4 升级数据库

事实上,若想对现有数据库进行升级,也是可以实现的。以前我们使用SQLiteOpenHelper来升级数据库的方式,虽说功能是实现了,但你有没有发现一个问题,,就是升级数据库的时候我们需要先把之前的表drop掉,然后再重新创建才行。这其实是一个非常严重的问题,因为这样会造成数据丢失,每当升级一次数据库,之前表中的数据就全没了。

而使用Litepal,就可以很好地避免这个问题。假设我们现在有一张新的表Category要加进去,同样编写它的实体类,代码如下:



package com.androidframelearn.dao_litapal;



public class Category {

    private int id;

    private String categoryName;

    private int categoryCode;

    public int getId(){

        return id;

    }

    public void setId(int id){

        this.id = id;

    }



    public String getCategoryName(){

        return categoryName;

    }

    public void setCategoryName(String categoryName){

        this.categoryName = categoryName;

    }



    public int getCategoryCode(){

        return categoryCode;

    }

    public void setCategoryCode(int categoryCode){

        this.categoryCode = categoryCode;

    }

}



改完了所有我们想改的东西,只需要记得在litepal.xml将版本号加1就行了。当然由于这里还添加了一个新的模型类,因此也需要将它添加到映射模型列表中。修改litepal.xml中的代码,如下所示:



<?xml version="1.0" encoding="utf-8"?>

<litepal>

    <!--  数据库名  -->

    <dbname value="androidframelearn"/>



    <!--  数据库版本号  -->

    <version value="2"/>



    <!--  指定映射模型  -->

    <list>

        <mapping class="com.androidframelearn.dao_litapal.Book"/>

        <mapping class="com.androidframelearn.dao_litapal.Category"/>

    </list>



    <!--  指定文件的存储方式  -->

    <!--  <storage value="external" />-->

</litepal>



重新运行一下程序,再次创建数据库,就可以完美地完成数据库的升级了。这里的调试可以使用sqlite工具,这里不再赘述。



3.5 插入数据

在讲述本节时,首先回顾一下之前添加数据的方法,我们需要创建出一个Contentvalues对象,然后将所有要添加的数据put到这个Contentvalues对象当中,最后再调用SQLiteDatabase的insert() 方法将数据添加到数据库表当中,步骤相当繁琐。

而使用LitePal来添加数据,这些操作可以简单到让你惊叹!我们只需要创建出模型类的实例,再将所有要存储的数据设置好,最后调用一下save()方法就可以了。

同样地,修改MainActivity,增加插入数据的事件方法,代码如下:



private void insertDatabyLitePal() {

        btn_db_insert.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Book book = new Book();

                book.setName("The Da Vinci Code");

                book.setauthor("Dan Brown");

                book.setPages(454);

                book.setPrice(16.96);

                book.save();

                Log.i(TAG,"插入数据成功");

            }

        });

    }



同样运行程序,查看控制台,如图所示:



当点击查询数据(下一节将介绍该逻辑)时,控制台打印刚刚插入的数据,如图所示:





3.6 查询数据

使用Litepal同样可以很轻易地查询数据,当然了,由于篇幅限制,这里仅仅贴出最简单的查询方式,至于关联查询等稍复杂的查询方式,可以去GItHub上参考Litepal的官方文档进行相关调用即可。

同样地,修改MainActivity,增加查看数据的事件方法,代码如下:



private void queryDatabyLitePal() {

        btn_db_query.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                List<Book> books = LitePal.findAll(Book.class);

                for (Book book : books){

                    Log.i(TAG,"查询数据成功");

                    Log.d("MainActivity","书名是"+book.getName());

                    Log.d("MainActivity","书的作者是"+book.getAuthor());

                    Log.d("MainActivity","书的页数是"+book.getPages());

                    Log.d("MainActivity","书的价格是"+book.getPrice());

                }

            }

        });

    }



相关的运行结果上一小节以贴出,这里不再重复。



3.7 更新数据

更新数据要比添加数据稍微复杂一点,因为它的API接口比较多,这里我们只介绍最常用的几种更新方式。

首先,最简单的一种更新方式就是对已存储的对象重新设值,然后重新调用save()方法即可。那么这里我们就要了解一个概念,什么是已存储的对象?

对于LitePal来说,对象是否已存储就是根据调用model.isSaved()方法的结果来判断的, 返回true就表示已存储,返回false就表示未存储。那么接下来的问题就是,什么情况下会返回true,什么情况下会返回false呢?

实际上只有在两种情况下model.isSave()方法才会返回true, 一种情况是已经调用过model. save()方法去添加数据了,此时model会被认为是已存储的对象。另一种情况是model对象是通过LitePal提供的查询API查岀来的,由于是从数据库中查到的对象,因此也会被认为是已存储的对象。

由于查询API相对复杂,因此只能先通过第一种情况来进行验证。修改MainActivity中的代码,如下所示:



private void updateDatabyLitePal() {

        btn_db_update.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Book book = new Book();

                book.setName("The Lost Symbol");

                book.setauthor("Dan Brown");

                book.setPages(510);

                book.setPrice(19.95); // 第一次设置商品价格

                book.save();

                book.setPrice(10.99); // 第二次设置商品价格

                book.save();

                Log.i(TAG,"更新数据成功");

            }

        });

    }



可以看到,我们做了跟插入数据类似的事情,但是我们对数据的价格进行了设置,运行程序,如图所示:



可以看到,除了刚刚插入的数据,还有第二条刚刚更新过后的数据。然而这种更新方式只能对已存储的对象进行操作,限制性比较大,接下来我们学习另外一种更加灵巧的更新方式,可以调用以下api:



book.updateAll("name = ? and author = ?","The Lost Symbol","Dan Brown");

1

这里仅贴出其中一条api,其他的可以参考官方文档,这里不再赘述。



3.8 删除数据

使用Litepal删除数据的方式主要有两种,第一种比较简单,就是直接调用已存储对象的delete()方法就可以了,对于已存储对象的概念,我们在之前已经学习过了。也就是说,调用过save()方法的对象,或者是通过LitePal提供的查询API查出来的对象,都是可以直接使用delete()方法来删除数据的。这种方式比较简单,我们就不进行代码演示了,下面直接来看另外一种删除数据的方式。

代码如下:



private void deleteDatabyLitePal() {

        btn_db_delete.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                LitePal.deleteAll(Book.class,"price < ?","15");

                Log.i(TAG,"删除成功");

            }

        });

    }



运行程序,删除过后,按照代码逻辑,已经删除掉了所有price小于15的条目,如图所示:





4.版本异同

之前阅读了郭霖老师所著《Android第一行代码 第二版》时,所记载的Litepal版本为:



compile 'org.litepal.android:core:1.4.1'

1

而的Litepal版本(Java版本,另有Kotlin版本,导入的依赖稍有不同)为:



implementation 'org.litepal.android:java:3.0.0'

1

新旧版本的主要区别是一些类名的划分,例如老板本的DataSupport变成了LitePalSupport,除此之外,一些api的名称也稍有变动,读者在使用时最好可以参考GitHub上的官方文档,及时更新代码,做到与时俱进。



5.源码地址

AFL——Android框架学习


TinyUI-TUIListView最简单的使用

seo达人

      在TinyUI简介的博客中提到其特点中包含一条,即多数大控件的使用方法和android一直,除了语言差异之外,本篇我们就介绍列表控件TUIListView最简单的使用方法。



        列表组件/控件作为目前Android/iOS的APP中最常用的控件,该控件的设计同时参考Android、windows、Qt等使用的经验进行筛选,最终选择了Android的ListView设计,其他平台的列表中使用难以程度或设计上略逊于Android,因为Android给与了开发者最大的发挥控件,你可以在列表中可以显示任何控件。



        TUIListView中的每一行每一列你可以放置任何UI组件,使用TUIListView需要配合TUIAdapter进行使用,而TinyUI和Android一样提供了内置的简单使用的TUISimpleAdapter。TUISimpleAdapter主要用于显示文本(即每一行都是只能显示文字),如果需要在列表中显示其他UI组件,则需要自定义一个Adapter,关于自定义Adapter将在后续进行详细讲解。



        本篇既然是TUIListView最简单的使用,我们就使用TUISimpleAdapter来显示文本你列表,TUISimpleAdapter最好只用于数据步发生变化的情况,因为其存放的数据使用了C++标准库的vector容器,而非使用list容器,vector容器的特点是访问速度快,但其缺点是vector的内存是连续的,因此内容发生变化可能会造成内存申请和拷贝的动作;而list容器使用的双向链表,其特点是插入数据快,但访问速度慢。



        本篇我们仍然使用上一篇中自定义的MyWindow来显示TUIListView。



使用方法/步骤

  1. 定义listView和andapter



            MyWindow中包含TUISimpleAdapter.h的头文件,并定义listView和adapter



    MyWindow.h


    ifndef MY_WINDOW_H

    define MY_WINDOW_H

    include <TUIWindow.h>

    include <TUISimpleAdapter.h>

     

     

     

    class MyWindow : public TUIWindow

    {

    public:

        MyWindow(TUIWindow* parent = nullptr);

        virtual ~MyWindow();

     

        void onShow();

        void onClose();

     

    private:

        TUIListView listView;

        TUISimpleAdapter adapter;

    };

     

    endif // !MY_WINDOW_H

     


  2. 填充数据,并把adapter设置到listView中



    MyWindow.cpp


    include "MyWindow.h"

     

     

     

    MyWindow::MyWindow(TUIWindow* parent)

        : TUIWindow(parent)

    {

        setContentView(&this->listView); // 把listView作为当前窗口的内容视图

     

     

        vector<string> data; // 使用vector<string>类型的data存放数据

     

        for (int32_t i = 0; i < 20; i++)

        {

            data.push_back(to_string(i)); // 生成0~20的数值-转换成字符串,放到data中

        }

     

        this->adapter.setData(data); // 把data设置到adapter中

     

        this->listView.setAdapter(&this->adapter); // 把adapter设置到listView,作为listView数据来源和操作对象

    }

     

    MyWindow::~MyWindow()

    {

    }

     

    void MyWindow::onShow()

    {

    }

     

    void MyWindow::onClose()

    {

    }

    到目前为止窗口显示列表控件已全部完成,接下来和上一篇一样调用MyWindow的show()方法即可显示,最终结果如下图所示:


call、apply、bind 原理实现

seo达人

目录

  1. call 的模拟实现
  2. apply 的模拟实现
  3. bind 的模拟实现
  4. 三者异同



    学习并参考于:



    JavaScript深入之call和apply的模拟实现



    JS的call,apply与bind详解,及其模拟实现





    (一)call的模拟实现

    call 用法 : MDN Function.prototype.call()



    call() 方法使用一个指定的 this 值和可选的参数列表来调用一个函数。



    call() 提供新的 this 值给当前调用的函数/方法。


  5. call 实现主要思路:

    将函数设为对象的属性



    执行该函数



    删除该函数



    另外还有考虑:



    call 函数还能给定参数执行函数

    this 参数不传,或者传null,undefined, this指向window对象

    函数是可以有返回值的
  6. 实现:

    Function.prototype.myCall = function () {

      if (typeof this !== 'function') {

        throw new TypeError('error!')

      }

      let context = arguments[0] || window   //this 参数可以传 null,当为 null 的时候,视为指向 window

      context.fn = this  // 首先要获取调用call的函数,用this可以获取

      let args = [...arguments].slice(1) //从 Arguments 对象中取值,取出第二个到最后一个参数   

      let result = context.fn(...args)  //函数是可以有返回值的

      delete context.fn

      return result

    }


  7. 测试:

    // 测试一下上面实现的myCall

    var value = 2;



    var obj = {

        value: 1

    }



    function bar(name, age) {

        console.log(this.value);

        return {

            value: this.value,

            name: name,

            age: age

        }

    }



    bar.call(null); // 2



    console.log(bar.myCall(obj, 'kevin', 18));

    // 1

    // Object {

    //    value: 1,

    //    name: 'kevin',

    //    age: 18

    // }



    (二)apply 的模拟实现

    apply 用法:MDN Function.prototype.apply()



    apply() 方法使用一个指定的 this 值和可选的参数数组 来调用一个函数。



    apply 的实现跟 call 类似。


  8. 实现:

    Function.prototype.myApply = function () {

      if (typeof this !== 'function') {

        throw new TypeError('error!')

      }

      let context = arguments[0] || window

      context.fn = this

      let result = arguments[1] ? context.fn(...arguments[1]) : context.fn()

      delete context.fn

      return result

    }


  9. 测试:

    var foo = {

        value: 1

    }

    function bar(name, age) {

        console.log(name)

        console.log(age)

        console.log(this.value);

    }

    bar.myApply(foo, ['black', '18']) // black 18 1



    (三)bind 的模拟实现

    bind 用法:MDN Function.prototype.bind()



    bind()方法会创建一个新函数,称为绑定函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。



    bind是ES5新增的一个方法,不会执行对应的函数,而是返回对绑定函数的引用。


  10. 实现:

    Function.prototype.customBind = function () {

      if (typeof this !== 'function') {

        throw new TypeError('error!')

      }

      const that = this   // 首先要获取调用bind的函数,用this获取并存放在that中

      let context = arguments[0] || window

      const args = [...arguments].slice(1)

      return function() {

        return that.apply(context, args.concat([...arguments]))

      }

    }



    (四)三者异同
  11. 相同:

    改变函数体内 this 的指向
  12. 不同:

    call、apply的区别:call方法接受的是参数列表,而apply方法接受的是一个参数数组。

    bind不立即执行。而call或apply会自动执行对应的函数。


关于HTTP请求出现 405状态码 not allowed的解决办法

seo达人

发现httppost请求目标网站会出现405 状态码,原因为 Apache、IIS、Nginx等绝大多数web服务器,都不允许静态文件响应POST请求

所以将post请求改为get请求即可

跨域,请求按要求配置完毕之后,options预请求老是报错。原因是webapi 默认的web.config有配置

有这么个配置,导致不行。要把他删掉,还要加上

浅谈JavaScript实现可视化展示冒泡排序过程

seo达人



<!DOCTYPE html>

<html>

<head>

<title>JavaScript实现可视化展示冒泡排序过程</title>

<style>

#boxes{

border:1px solid grey;

width:1320px;

height:300px;

margin-top:10px;

position:relative;

}

.box{

background:red;

width:20px;

line-height:30px;

text-align:center;

font-family:Microsoft Yahei;

font-size:15px;

color:white;

margin:0 1px;

position:absolute;

}

</style>

</head>

<body>

<div id="boxes"></div>

<script>

function random(){

var numbers = [];

for (var i = 0; i < 60; i++) {

var number = Math.floor(Math.random() 90 + 10);

numbers.push(number);

var divElement = document.createElement("div");

var parentElement = document.getElementById("boxes");

divElement.style.left = i
20 + i 2 + "px";

divElement.style.top = 300 - 3
number + "px";

divElement.style.height = 3 number + "px";

divElement.setAttribute("class","box");

parentElement.appendChild(divElement);

}

return numbers;

}

function sort(){

var numbers = random();

var parentElement = document.getElementById("boxes");

var i = 0, j = 0;

var time = setInterval(function() {

if (i < numbers.length) {

if (j < numbers.length - i) {

if (numbers[j] > numbers[j + 1]) {

var temp = numbers[j];

numbers[j] = numbers[j + 1];

numbers[j + 1] = temp;

parentElement.innerHTML = "";

for (var k = 0; k < numbers.length; k++) {

var textNode = document.createTextNode(numbers[k]);

var divElement = document.createElement("div");

divElement.appendChild(textNode);

divElement.style.left = k
20 + k 2 + "px";

divElement.style.top = 300 - 3
numbers[k] + "px";

divElement.style.height = 3 * numbers[k] + "px";

divElement.setAttribute("class","box");

parentElement.appendChild(divElement);

}

}

j++;

}

else{

i++;

j = 0;

}

}

else {

clearInterval(time); 

return;

}

}, 100);  

}

sort();

</script>

</body>

</html>

————————————————

版权声明:本文为CSDN博主「筱葭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zhouziyu2011/java/article/details/53899692

日历

链接

个人资料

蓝蓝设计的小编 http://www.lanlanwork.com

存档