深入理解vue

栏目: 编程语言 · 发布时间: 6年前

内容简介:一 理解vue的核心理念使用vue会让人感到身心愉悦,它同时具备angular和react的优点,轻量级,api简单,文档齐全,简单强大,麻雀虽小五脏俱全.倘若用一句话来概括vue,那么我首先想到的便是官方文档中的一句话:
编辑推荐:
本文来自cnblogs,本文主要介绍vue的核心理念以及vue的双向绑定原理及实现方法等,希望对您的学习有所帮助。

一 理解vue的核心理念

使用vue会让人感到身心愉悦,它同时具备angular和react的优点,轻量级,api简单,文档齐全,简单强大,麻雀虽小五脏俱全.

倘若用一句话来概括vue,那么我首先想到的便是官方文档中的一句话:

Vue.js(读音 /vju:/,类似于 view) 是一套构建用户界面的渐进式框架。

这句话可能大家并不陌生,但是真正理解这句话的可能并不多,其实,读懂了这句话,也就明白了vue的核心理念.

那么,怎样理解什么是渐进式框架?在这之前,我们首先要理解什么是框架.在最初的前端开发中,为了完成某个功能,我们需要通过js在HTML页面中获得dom节点,随后获得dom节点中的文本内容或者在dom节点上添加事件,进行一系列的程序操作,但是,如果任务量很大的情况下,代码会随着业务的增加而变得臃肿和混乱,在现实的开发中,负责的逻辑和巨大的开发量,是原生js无法完成的.

这个时候,开发人员将js代码分为了三个板块,数据(Model),逻辑控制(*),视图(View),数据板块只负责数据部分,视图板块负责更改样式,逻辑控制负责联系视图板块和数据板块,这样子有很大的好处,当需求发生变动时,只需要修改对应的板块就好

深入理解vue

这种开发模式,就是所谓的MV*结构,我们现在了解的MVC,MVP,MVVM都是MV*的衍生物,对比这几种框架模式,我们会总结出来一个本质的特点,那就是这些开发模式都是让视图和数据间不会发生直接联系.对比用原生JS获得dom的操作,你会发现原生dom流其实是将dom作为数据,从dom中获得Model,随后又更改dom来实现更新视图,视图和模型其实混在一起,所以代码自然混乱,不易维护.

在具有响应式系统的Vue实例中,DOM状态只是数据状态的一个映射 即 UI=VM(State) ,当等式右边State改变了,页面展示部分UI就会发生相应改变。很多人初次上手Vue时,觉得很好用,原因就是这个.不过,Vue的核心定位并不是一个框架,设计上也没有完全遵循MVVM模式,可以看到在图中只有State和View两部分, Vue的核心功能强调的是状态到界面的映射,对于代码的结构组织并不重视, 所以单纯只使用其核心功能时,它并不是一个框架,而更像一个视图模板引擎,这也是为什么Vue开发者把其命名成读音类似于view的原因。

上文提到,Vue的核心的功能,是一个视图模板引擎,但这不是说Vue就不能成为一个框架。如下图所示,这里包含了Vue的所有部件,在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。更重要的是,这些功能相互独立,你可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念.

深入理解vue

二探讨vue的双向绑定原理及实现

成果图

深入理解vue

深入理解vue

下面介绍两个内容

1.vue双向绑定的原理

2.实现简易版vue的过程,包括声明式数据渲染及一些简单的指令

vue双向绑定原理

vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情.我们可以看一下通过控制台梳齿一个定义在vue初始化数据上的对象是什么.

 var vm = new Vue({
 data: {
 test : {
 a: 1
 }
 },
 created: function () {
 console.log(this.test);
 }
 });

打印结果:

深入理解vue

在打印结果中我们可以看到属性a有两个方法:get和set.为什么会有这两个方法呢,这正是vue通过Object.defineProperty()进行数据劫持的.

Object.defineProperty()这个方法是做什么的呢?文档上是这样说的

深入理解vue

简单的说,他可以控制一个对象属性的一些特有操作,比如读写权,是否可枚举,这里我们主要研究它的get和set方法,如果想清楚更多用法,可以参考:https://developer.mozilla.org/zh-CN/docs/Web/

JavaScript/Reference/Global_Objects/Object

/defineProperty

我们可以很轻松的打印出一个对象的属性数据:

var Book = {

name: '人性的弱点'

};

console.log(Book.name); // 人性的弱点

但是如果在执行console.log(Book.name)的同时,给书的书名增加一个书名号呢,这个时候应该怎么做,这时候我们就需要用到Object.defineProperty( )了:

 //在console.log(book.name)同时,直接给书加一个书号
 var Book = {};
 var name = '';
 Object.defineProperty(Book,'name',{
 set:function(value) {
 name = value;
 console.log('你取了一个书名叫:'+value);
 },
 get:function() {
 console.log('get方法被监听到');
 return '<'+name+'>';
 }
 });
 Book.name = '人性的弱点'; //你取了一个书名叫:人性的弱点
 console.log(Book.name); //<人性的弱点>

通过Object.defineProperty( )这个方法设置了Book对象的name属性,对其get和set方法进行重写操作,get方法在获得name属性时被调用,set方法在设置name属性时被触发.所以在执行Book.name='人性的弱点'这个语句时调用set方法,输出你取了一个书名叫:人性的弱点.当调用console.log(Book.name)时触发get方法,输出<人性的弱点>,如果在代码中加入这句话时,会打印出什么呢?

console.log(Book)

结果如下:

深入理解vue

与上面vue打印数据进行对比非常类似,说明vue确实是通过这种方式进行数据劫持的.那么什么是发布者-订阅者模式呢??

订阅者和发布者模式,通常用于消息队列中.一般有两种形式来实现消息队列,一是使用生产者和消费者来实现,二是使用订阅者-发布者模式来实现,其中订阅者和发布者实现消息队列的方式,就会用订阅者模式.

打个比方,所谓的订阅者,就像我们在日常生活中订阅报纸一样,在订阅报纸的时候,通常都得需要在报社或者一些中介机构进行注册,当有新版的报纸发刊的时候,邮递员就需要向订阅该报纸的人,依次发放报纸.

所谓的订阅者,就像我们在日常生活中,订阅报纸一样。我们订阅报纸的时候,通常都得需要在报社或者一些中介机构进行注册。当有新版的报纸发刊的时候,邮递员就需要向订阅该报纸的人,依次发放报纸。

所有如果用代码实现该模式,需要进行两个步骤:

1、初始化发布者、订阅者。

2、订阅者需要注册到发布者,发布者发布消息时,依次向订阅者发布消息。

订阅者注册

深入理解vue

发布者发布消息

深入理解vue

那么接下来我们将通过vue原理实现一个简单的mvvm双向绑定的demo

思路分析

要想实现mvvm,主要包含两个方面,视图变化更新数据,数据变化更新视图.

view变化更新data其实可以通过事件监听实现,比如input标签监听input事件,所有我们着重分析data变化更新view.

data变化更新view的重点是如何知道view什么时候变化了,只要知道什么时候view变化了,那么接下来的就好处理了.这个时候我们上文提到的Object.defineProperty( )就起作用了.通过Object.defineProperty( )对属性设置一个set函数,当属性变化时就会触发这个函数,所以我们只需要将一些更新的方法放在set函数中就可以实现data变化更新view了.

深入理解vue

实现过程

我们已经知道如何实现数据的双向绑定了,那么首先要对数据进行劫持监听,所以我们首先要设置一个监听器Observer,用来监听所有的属性,当属性变化时,就需要通知订阅者Watcher,看是否需要更新.因为属性可能是多个,所以会有多个订阅者,故我们需要一个消息订阅器Dep来专门收集这些订阅者,并在监听器Observer和订阅者Watcher之间进行统一的管理.以为在节点元素上可能存在一些指令,所以我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令初始化成一个订阅者Watcher,并替换模板数据并绑定相应的函数,这时候当订阅者Watcher接受到相应属性的变化,就会执行相对应的更新函数,从而更新视图.

整理上面的思路,我们需要实现三个步骤,来完成双向绑定:

1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

流程图如下:

深入理解vue

1.实现一个监听器Observer

数据监听器的核心方法就是Object.defineProperty( ),通过遍历循环对所有属性值进行监听,并对其进行Object.defineProperty( )处理,那么代码可以这样写:

 //对所有属性都要蒋婷,递归遍历所有属性
 function defineReactive(data,key,val) {
 observe(val); //递归遍历所有的属性
 Object.defineProperty(data,key,{
 enumerable:true, //当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。
 configurable:true, //当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中
 get:function() {
 return val;
 },
 set:function(newVal) {
 val = newVal;
 console.log('属性'+key+'已经被监听,现在值为:"'+newVal.toString()+'"');
 }
 })
 } 

function observe(data) {
 if(!data || typeof data !== 'object') {
 return;
 }
 Object.keys(data).forEach(function(key){
 defineReactive(data,key,data[key]);
 });
 }
 
var library = {
 book1: {
 name: ''
 },
 book2: ''
 };
 observe(library);
 library.book1.name = 'vue权威指南'; // 属性name已经被监听了,现在值为:“vue权威指南”
 library.book2 = '没有此书籍'; // 属性book2已经被监听了,现在值为:“没有此书籍”

通过observe()方法进行遍历向下找到所有的属性,并通过defineReactive()方法进行数据劫持监听.

在上面的思路中,我们需要一个可以容纳消息订阅者的消息订阅器Dep,订阅器主要收集消息订阅者,然后在属性变化时执行相应订阅者的更新函数,那么消息订阅器Dep需要有一个容器,用来存放消息订阅者.我们将上面的监听器Observer稍微修改一下:

 function defineReactive(data,key,val) {
 observe(val);
 var dep = new Dep();
 Object.defineProperty(data, key, {
 enumerable: true,
 configurable: true,
 get: function() {
 if (是否需要添加订阅者) { //Watcher初始化触发
 dep.addSub(watcher); // 在这里添加一个订阅者
 }
 return val;
 },
 set: function(newVal) {
 if (val === newVal) {
 return;
 }
 val = newVal;
 console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
 dep.notify(); // 如果数据变化,通知所有订阅者
 }
 });
 }
 
function observe(data) {
 if(!data || typeof data !== 'object') {
 return;
 }
 Object.keys(data).forEach(function(key){
 defineReactive(data,key,data[key]);
 });
 }
 
function Dep() {
 this.subs = [];
 }
 
//prototype 属性使您有能力向对象添加属性和方法
 //prototype这个属性只有函数对象才有,具体的说就是构造函数具有.只要你声明定义了一个函数对象,这个prototype就会存在
 //对象实例是没有这个属性
 Dep.prototype = { 
 addSub:function(sub) {
 this.subs.push(sub);
 },
 notify:function() {
 this.subs.forEach(function(sub) {
 sub.update(); //通知每个订阅者检查更新
 })
 }
 }
 Dep.target = null;

在代码中,我们将订阅器Dep添加一个订阅者设计在get里面,这是为了让Watcher在初始化时触发,因此判断是否需要需要添加订阅者,至于具体实现的方法,我们在下文中深究.在set方法中,如果函数变化,就会通知所有的订阅者,订阅者们将会执行相对应的更新函数,到目前为止,一个比较完善的Observer已经成型了,下面我们要写订阅者Watcher.

2.实现订阅者Watcher

根据我们的思路,订阅者Wahcher在初始化时要将自己添加到订阅器Dep中,那么如何进行添加呢?

我们已经知道监听器Observer是在get函数中执行了添加订阅者的操作的,所以我们只需要在订阅者Watcher在初始化时触发相对应的get函数来执行添加订阅者的操作即可.那么怎么触发对应的get函数呢?我们只需要获取对应的属性值,就可以通过Object.defineProperty( )触发对应的get了.

在这里需要注意一个细节,我们只需要在订阅者初始化时才执行添加订阅者,所以我们需要一个判断,在Dep.target上缓存一下订阅者,添加成功后去除就行了,代码如下:

 function Watcher(vm,exp,cb) {
 this.vm = vm; //指向SelfVue的作用域
 this.exp = exp; //绑定属性的key值
 this.cb = cb; //闭包
 this.value = this.get();
 } 
 
Watcher.prototype = {
 update:function() {
 this.run();
 },
 run:function() {
 var value = this.vm.data[this.exp];
 var oldVal = this.value;
 if(value !== oldVal) {
 this.value = value;
 this.cb.call(this.vm,value,oldVal);
 }
 },
 get:function() {
 Dep.target = this; // 缓存自己
 var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数
 Dep.target = null; // 释放自己
 return value;
 }
 }

这个时候我们需要对监听器Observer中的defineReactive()做稍微的调整:

 function defineReactive(data,key,val) {
 observe(val);
 var dep = new Dep();
 Object.defineProperty(data, key, {
 enumerable: true,
 configurable: true,
 get: function() {
 if(Dep.target) { //判断是否需要添加订阅者
 dep.addSub(Dep.target);
 }
 return val;
 },
 set: function(newVal) {
 if (val === newVal) {
 return;
 }
 val = newVal;
 console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
 dep.notify(); // 如果数据变化,通知所有订阅者
 }
 });
 }

到目前为止,一个简易版的Watcher已经成型了,我们只需要将订阅者Watcher和监听器Observer关联起来,就可以实现一个简单的双向绑定.因为这里还没有设计指令解析器,所以对于模板数据我们都进行写死处理,假设模板上有一个节点元素,且id为'name',并且双向绑定的绑定变量也是'name',且是通过两个大双括号包起来(暂时没有什么用处),模板代码如下:

<body>

<h1 id="name">{{name}}</h1>

</body>

我们需要定义一个SelfVue类,来实现observer和watcher的关联,代码如下:

 //将Observer和Watcher关联起来
 function SelfVue(data,el,exp) {
 this.data = data;
 observe(data);
 el.innerHTML = this.data[exp];
 new Watcher(this,exp,function(value) {
 el.innerHTML = value;
 });
 return this;
 }

然后在页面上new一个SelfVue,就可以实现双向绑定了:

 <body>
 <h1 id="name"{{name}}></h1>
 </body>
 
<script src="../js/observer.js"></script>
 <script src="../js/Watcher.js"></script>
 <script src="../js/SelfVue.js"></script>
 
<script>
 var ele = document.querySelector('#name');
 var selfVue = new SelfVue({
 name:'hello world'
 },ele,'name');
 
 window.setTimeout(function() {
 console.log('name值改变了');
 selfVue.name = 'byebye world';
 },2000);
 </script>>

这时我们打开页面,显示的是'hello world',2s后变成了'byebye world',一个简单的双向绑定实现了.

对比vue,我们发现了有一个问题,我们在为属性赋值的时候形式是: ' selfVue.data.name = 'byebye world' ',而我们理想的形式是:' selfVue.name = 'byebye world' ',那么怎么实现这种形式呢,只需要在new SelfVue时做一个代理处理,让访问SelfVue的属性代理为访问selfVue.data的属性,原理还是使用Object.defineProperty( )对属性在包装一层.代码如下:

 function SelfVue(data,el,exp) {
 var self = this;
 this.data = data;
 //Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组
 Object.keys(data).forEach(function(key) {
 self.proxyKeys(key); //绑定代理属性
 });
 observe(data);
 el.innerHTML = this.data[exp]; // 初始化模板数据的值
 new Watcher(this,exp,function(value) {
 el.innerHTML = value;
 });
 return this;
 }
 
SelfVue.prototype = {
 proxyKeys:function(key) {
 var self = this;
 Object.defineProperty(this,key,{
 enumerable:false,
 configurable:true,
 get:function proxyGetter() {
 return self.data[key];
 },
 set:function proxySetter(newVal) {
 self.data[key] = newVal;
 } 
 });
 }
 }

这样我们就可以用理想的形式改变模板数据了.

3.实现指令解析器Compile

再上面的双向绑定demo中,我们发现整个过程都没有解析dom节点,而是固定某个节点进行替换数据,所以接下来我们要实现一个解析器Compile来解析和绑定工作,分析解析器的作用,实现步骤如下:

1.解析模板指令,并替换模板数据,初始化视图

2.将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器

为了解析模板,首先要获得dom元素,然后对含有dom元素上含有指令的节点进行处理,这个过程对dom元素的操作比较繁琐,所以我们可以先建一个fragment片段,将需要解析的dom元素存到fragment片段中在做处理:

 nodeToFragment:function(el) {
 var fragment = document.createDocumentFragment(); //createdocumentfragment()方法创建了一虚拟的节点对象,节点对象包含所有属性和方法。
 var child = el.firstChild;
 while(child) {
 // 将Dom元素移入fragment中
 fragment.appendChild(child);
 child = el.firstChild;
 }
 return fragment;
 }

接下来需要遍历所有节点,对含有指令的节点进行特殊的处理,这里我们先处理最简单的情况,只对带有 '{{变量}}' 这种形式的指令进行处理,代码如下:

 //遍历各个节点,对含有相关指定的节点进行特殊处理
 compileElement:function(el) {
 var childNodes = el.childNodes; //childNodes属性返回节点的子节点集合,以 NodeList 对象。
 var self = this;
 //slice() 方法可从已有的数组中返回选定的元素。
 [].slice.call(childNodes).forEach(function(node) {
 var reg = /\{\{(.*)\}\}/;
 var text = node.textContent; //textContent 属性设置或返回指定节点的文本内容
 if(self.isTextNode(node) && reg.test(text)) { //判断是否符合{{}}的指令
 //exec() 方法用于检索字符串中的正则表达式的匹配。
 //返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
 self.compileText(node,reg.exec(text)[1]);
 }
 if(node.childNodes && node.childNodes.length) {
 self.compileElement(node); //继续递归遍历子节点
 }
 });
 },
 compileText:function(node,exp) {
 var self = this;
 var initText = this.vm[exp];
 this.updateText(node,initText); // 将初始化的数据初始化到视图中
 new Watcher(this.vm,exp,function(value) {
 self.updateText(node,value);
 });
 
 },
 updateText:function(node,value) {
 node.textContent = typeof value == 'undefined' ? '': value;
 },

获取到最外层节点后,调用compileElement函数,对所有子节点进行判断,如果节点是文本节点且匹配{{}}这种形式指令的节点就开始进行编译处理,编译处理首先需要初始化视图数据,对应上面所说的步骤1,接下去需要生成一个并绑定更新函数的订阅器,对应上面所说的步骤2。这样就完成指令的解析、初始化、编译三个过程,一个解析器Compile也就可以正常的工作了。

为了将解析器Compile与监听器Observer和订阅者Watcher关联起来,我们需要再修改一下类SelfVue函数:

 function SelfVue(options) {
 var self = this;
 this.vm = this;
 this.data = options.data;
 Object.keys(this.data).forEach(function(key) {
 self.proxyKeys(key); //绑定代理属性
 });
 observe(options.data);
 new Compile(options.el,this.vm);
 return this;
 }

更改后,我们就不要像之前通过传入固定的元素值进行双向绑定了,可以随便命名各种变量进行双向绑定了:

 <body>
 <div id="app">
 <h1>{{title}}</h1>
 <h2>{{name}}</h2>
 <h3>{{content}}</h3>
 </div>
 </body>
 <script src="../js/observer2.js"></script>
 <script src="../js/Watcher1.js"></script>
 <script src="../js/compile1.js"></script>
 <script src="../js/index3.js"></script> 

 <script>
 var selfVue = new SelfVue({
 el:'#app',
 data:{
 title:'aaa',
 name:'bbb',
 content:'ccc'
 }
 });
 window.setTimeout(function() {
 selfVue.title = 'ddd';
 selfVue.name = 'eee';
 selfVue.content = 'fff'
 },2000);
 </script>

到这里,一个数据双向绑定功能已经基本完成了,接下去就是需要完善更多指令的解析编译,在哪里进行更多指令的处理呢?答案很明显,只要在上文说的compileElement函数加上对其他指令节点进行判断,然后遍历其所有属性,看是否有匹配的指令的属性,如果有的话,就对其进行解析编译。这里我们再添加一个v-model指令和事件指令的解析编译,对于这些节点我们使用函数compile进行解析处理:

 compile:function(node) {
 var nodeAttrs = node.attributes; //attributes 属性返回指定节点的属性集合,即 NamedNodeMap。
 var self = this;
 //Array.prototype属性表示Array构造函数的原型,并允许为所有Array对象添加新的属性和方法。
 //Array.prototype本身就是一个Array
 Array.prototype.forEach.call(nodeAttrs,function(attr) {
 var attrName = attr.name; //添加事件的方法名和前缀:v-on:click="onClick" ,则attrName = 'v-on:click' id="app" attrname= 'id'
 if(self.isDirective(attrName)) { 
 var exp = attr.value; //添加事件的方法名和前缀:v-on:click="onClick" ,exp = 'onClick'
 
//substring() 方法用于提取字符串中介于两个指定下标之间的字符。返回值为一个新的字符串
 //dir = 'on:click'
 var dir = attrName.substring(2); 
 if(self.isEventDirective(dir)) { //事件指令
 self.compileEvent(node,self.vm,exp,dir);
 }else { //v-model指令
 self.compileModel(node,self.vm,exp,dir);
 }
 
 node.removeAttribute(attrName);
 }
 });
 }

上面的compile函数是挂载Compile原型上的,它首先遍历所有节点属性,然后再判断属性是否是指令属性,如果是的话再区分是哪种指令,再进行相应的处理.

最后我们再次改造一下SelfVue,是它的格式看上去更像vue:

 function SelfVue(options) {
 var self = this;
 this.data = options.data;
 this.methods = options.methods;
 Object.keys(this.data).forEach(function(key) {
 self.proxyKeys(key); 
 });
 observe(options.data);
 new Compile(options.el,this);
 options.mounted.call(this);
 }

测试一下:

 <body>
 <div id="app">
 <h2>{{title}}</h2>
 <input v-model="name">
 <h1>{{name}}</h1>
 <button v-on:click="clickMe">click me!</button>
 </div>
 </body> 
<script src="../js/observer3.js"></script>
 <script src="../js/Watcher1.js"></script>
 <script src="../js/compile2.js"></script>
 <script src="../js/index4.js"></script>
 <script>
 new SelfVue({
 el: '#app',
 data: {
 title: 'hello world',
 name: 'canfoo'
 },
 methods: {
 clickMe: function () {
 this.title = 'hello world';
 }
 },
 mounted: function () {
 window.setTimeout(() => {
 this.title = '你好';
 }, 1000);
 }
 });
 </script>

效果如下:

深入理解vue

到目前为止,我们简易版的demo已经成功了,通过上面这个例子,我们可以更加深刻的理解vue的一些机制,比如双向绑定,声明式渲染等.

写在后面

因为代码量比较多,所以对于一些不重要的没有一一展示,我把代码放在我的github上了,github Clone with HTTPS:

https://github.com/2686685661/SelfVue.git

相关文章
深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档
重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程
基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Kotlin实战

Kotlin实战

【美】Dmitry Jemerov(德米特里·詹莫瑞福)、【美】 Svetlana Isakova(斯维特拉娜·伊凡诺沃) / 覃宇、罗丽、李思阳、蒋扬海 / 电子工业出版社 / 2017-8 / 89.00

《Kotlin 实战》将从语言的基本特性开始,逐渐覆盖其更多的高级特性,尤其注重讲解如何将 Koltin 集成到已有 Java 工程实践及其背后的原理。本书分为两个部分。第一部分讲解如何开始使用 Kotlin 现有的库和API,包括基本语法、扩展函数和扩展属性、数据类和伴生对象、lambda 表达式,以及数据类型系统(着重讲解了可空性和集合的概念)。第二部分教你如何使用 Kotlin 构建自己的 ......一起来看看 《Kotlin实战》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换