方法 手写promise_JS探索-手写Promise

无意间在知乎上刷到Monad这个概念,去了解了一下,前端的Promise就是一种Monad模式,所以试着学习一下手写一个Promise.

本文内容主要参考于

只会用?一起来手写一个合乎规范的Promise​www.jianshu.com
b2cc9056b372c3f86512b8c6a44055ba.png

Promise是什么

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise是处理异步编码的一个解决方案,在Promise出现以前,异步代码的编写都是通过回调函数来处理的,回调函数本身没有任何问题,只是当多次异步回调有逻辑关系时就会变得复杂:

const fs = require('fs');
fs.readFile('1.txt', (err,data) => {fs.readFile('2.txt', (err,data) => {fs.readFile('3.txt', (err,data) => {//可能还有后续代码});});
});

上面读取了3个文件,它们是层层递进的关系,可以看到多个异步代码套在一起不是纵向发展的,而是横向,不论是从语法上还是从排错上都不好,于是Promise的出现可以解决这一痛点。
上述代码如果改写成Promise版是这样:

const util = require('util');
const fs = require('fs');
const readFile = util.promisify(fs.readFile);readFile('1.txt').then(data => {return readFile('2.txt');}).then(data => {return readFile('3.txt');}).then(data => {//...});

可以看到,代码是从上至下纵向发展了,更加符合人们的逻辑。

下面手写一个Promise,按照Promises/A+规范,可以参照规范原文:

Promises/A+​promisesaplus.com

手写实现Promise是一道前端经典的面试题,比如美团的面试就是必考题,Promise的逻辑还是比较复杂的,考虑的逻辑也比较多,下面总结手写Promise的关键点,和怎样使用代码来实现它。

Promise代码基本结构

实例化Promise对象时传入一个函数作为执行器,有两个参数(resolve和reject)分别将结果变为成功态和失败态。我们可以写出基本结构

function Promise(executor) {this.state = 'pending'; //状态this.value = undefined; //成功结果this.reason = undefined; //失败原因function resolve(value) {}function reject(reason) {}
}module.exports = Promise;

其中state属性保存了Promise对象的状态,规范中指明,一个Promise对象只有三种状态:等待态(pending)成功态(resolved)和失败态(rejected)
当一个Promise对象执行成功了要有一个结果,它使用value属性保存;也有可能由于某种原因失败了,这个失败原因放在reason属性中保存。

then方法定义在原型上

每一个Promise实例都有一个then方法,它用来处理异步返回的结果,它是定义在原型上的方法,我们先写一个空方法做好准备:

Promise.prototype.then = function (onFulfilled, onRejected) {
};

当实例化Promise时会立即执行

当我们自己实例化一个Promise时,其执行器函数(executor)会立即执行,这是一定的:

let p = new Promise((resolve, reject) => {console.log('执行了');
});
//运行结果:执行了

因此,当实例化Promise时,构造函数中就要马上调用传入的executor函数执行

function Promise(executor) {var _this = this;this.state = 'pending';this.value = undefined;this.reason = undefined;executor(resolve, reject); //马上执行function resolve(value) {}function reject(reason) {}
}

已经是成功态或是失败态不可再更新状态

规范中规定,当Promise对象已经由pending状态改变为了成功态(resolved)或是失败态(rejected)就不能再次更改状态了。因此我们在更新状态时要判断,如果当前状态是pending(等待态)才可更新:

function resolve(value) {//当状态为pending时再做更新if (_this.state === 'pending') {_this.value = value;//保存成功结果_this.state = 'resolved';}}function reject(reason) {//当状态为pending时再做更新if (_this.state === 'pending') {_this.reason = reason;//保存失败原因_this.state = 'rejected';}}

以上可以看到,在resolve和reject函数中分别加入了判断,只有当前状态是pending才可进行操作,同时将成功的结果和失败的原因都保存到对应的属性上。之后将state属性置为更新后的状态。

then方法的基本实现

当Promise的状态发生了改变,不论是成功或是失败都会调用then方法,所以,then方法的实现也很简单,根据state状态来调用不同的回调函数即可:

Promise.prototype.then = function (onFulfilled, onRejected) {if (this.state === 'resolved') {//判断参数类型,是函数执行之if (typeof onFulfilled === 'function') {onFulfilled(this.value);}}if (this.state === 'rejected') {if (typeof onRejected === 'function') {onRejected(this.reason);}}
};

需要一点注意,规范中说明了,onFulfilled 和 onRejected 都是可选参数,也就是说可以传也可以不传。传入的回调函数也不是一个函数类型,那怎么办?规范中说忽略它就好了。因此需要判断一下回调函数的类型,如果明确是个函数再执行它。

让Promise支持异步

代码写到这里似乎基本功能都实现了,可是还有一个很大的问题,目前此Promise还不支持异步代码,如果Promise中封装的是异步操作,then方法无能为力:

let p = new Promise((resolve, reject) => {setTimeout(() => {resolve(1);}, 500);
});p.then(data => console.log(data)); //没有任何结果

运行以上代码发现没有任何结果,本意是等500毫秒后执行then方法,哪里有问题呢?原因是setTimeout函数使得resolve是异步执行的,有延迟,当调用then方法的时候,此时此刻的状态还是等待态(pending),因此then方法即没有调用onFulfilled也没有调用onRejected。
这个问题如何解决?我们可以参照发布订阅模式,在执行then方法时如果还在等待态(pending),就把回调函数临时寄存到一个数组里,当状态发生改变时依次从数组中取出执行就好了,清楚这个思路我们实现它,首先在类上新增两个Array类型的数组,用于存放回调函数:

function Promise(executor) {var _this = this;this.state = 'pending';this.value = undefined;this.reason = undefined;this.onFulfilledFunc = [];//保存成功回调this.onRejectedFunc = [];//保存失败回调//其它代码略...
}

这样当then方法执行时,若状态还在等待态(pending),将回调函数依次放入数组中:

Promise.prototype.then = function (onFulfilled, onRejected) {//等待态,此时异步代码还没有走完if (this.state === 'pending') {if (typeof onFulfilled === 'function') {this.onFulfilledFunc.push(onFulfilled);//保存回调}if (typeof onRejected === 'function') {this.onRejectedFunc.push(onRejected);//保存回调}}//其它代码略...
};

寄存好了回调,接下来就是当状态改变时执行就好了:

    function resolve(value) {if (_this.state === 'pending') {_this.value = value;//依次执行成功回调_this.onFulfilledFunc.forEach(fn => fn(value));_this.state = 'resolved';}}function reject(reason) {if (_this.state === 'pending') {_this.reason = reason;//依次执行失败回调_this.onRejectedFunc.forEach(fn => fn(reason));_this.state = 'rejected';}}

至此,Promise已经支持了异步操作,setTimeout延迟后也可正确执行then方法返回结果。

链式调用

Promise处理异步代码最强大的地方就是支持链式调用,这块也是最复杂的,我们先梳理一下规范中是怎么定义的:

  1. 每个then方法都返回一个新的Promise对象(原理的核心
  2. 如果then方法中显示地返回了一个Promise对象就以此对象为准,返回它的结果
  3. 如果then方法中返回的是一个普通值(如Number、String等)就使用此值包装成一个新的Promise对象返回。
  4. 如果then方法中没有return语句,就视为返回一个用Undefined包装的Promise对象
  5. 若then方法中出现异常,则调用失败态方法(reject)跳转到下一个then的onRejected
  6. 如果then方法没有传入任何回调,则继续向下传递(值的传递特性)。

规范中说的很抽像,我们可以把不好理解的点使用代码演示一下。
其中第3项,如果返回是个普通值就使用它包装成Promise,我们用代码来演示:

let p =new Promise((resolve,reject)=>{resolve(1);
});p.then(data=>{return 2; //返回一个普通值
}).then(data=>{console.log(data); //输出2
});

可见,当then返回了一个普通的值时,下一个then的成功态回调中即可取到上一个then的返回结果,说明了上一个then正是使用2来包装成的Promise,这符合规范中说的。
第4项,如果then方法中没有return语句,就视为返回一个用Undefined包装的Promise对象

let p = new Promise((resolve, reject) => {resolve(1);
});p.then(data => {//没有return语句
}).then(data => {console.log(data); //undefined
});

可以看到,当没有返回任何值时不会报错,没有任何语句时实际上就是return undefined;即将undefined包装成Promise对象传给下一个then的成功态。
第6项,如果then方法没有传入任何回调,则继续向下传递,这是什么意思呢?这就是Promise中值的穿透,还是用代码演示一下:

let p = new Promise((resolve, reject) => {resolve(1);
});p.then(data => 2)
.then()
.then()
.then(data => {console.log(data); //2
});

以上代码,在第一个then方法之后连续调用了两个空的then方法 ,没有传入任何回调函数,也没有返回值,此时Promise会将值一值向下传递,直到你接收处理它,这就是所谓的值的穿透。
现在可以明白链式调用的原理,不论是何种情况then方法都会返回一个Promise对象,这样才会有下个then方法。
搞清楚了这些点,我们就可以动手实现then方法的链式调用,一起来完善它:

Promise.prototype.then = function (onFulfilled, onRejected) {var promise2 = new Promise((resolve, reject) => {//代码略...})return promise2;
};

首先,不论何种情况then都返回Promise对象,我们就实例化一个新promise2并返回。
接下来就处理根据上一个then方法的返回值来生成新Promise对象,由于这块逻辑较复杂且有很多处调用,我们抽离出一个方法来操作,这也是规范中说明的:

/*** 解析then返回值与新Promise对象* @param {Object} promise2 新的Promise对象 * @param {*} x 上一个then的返回值* @param {Function} resolve promise2的resolve* @param {Function} reject promise2的reject*/
function resolvePromise(promise2, x, resolve, reject) {//...
}

resolvePromise方法用来封装链式调用产生的结果,下面我们分别一个个情况的写出它的逻辑,首先规范中说明,如果promise2x 指向同一对象,就使用TypeError作为原因转为失败。原文如下:

If promise and x refer to the same object, reject promise with a TypeError as the reason.

这是什么意思?其实就是循环引用,当then的返回值与新生成的Promise对象为同一个(引用地址相同),则会抛出TypeError错误:

let promise2 = p.then(data => {return promise2;
});

运行结果:

TypeError: Chaining cycle detected for promise #<Promise>

很显然,如果返回了自己的Promise对象,状态永远为等待态(pending),再也无法成为resolved或是rejected,程序会死掉,因此首先要处理它:

function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) {reject(new TypeError('Promise发生了循环引用'));}
}

接下来就是分各种情况处理。当x就是一个Promise,那么就执行它,成功即成功,失败即失败。若x是一个对象或是函数,再进一步处理它,否则就是一个普通值:

function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) {reject(new TypeError('Promise发生了循环引用'));}if (x !== null && (typeof x === 'object' || typeof x === 'function')) {//可能是个对象或是函数} else {//否则是个普通值resolve(x);}
}

此时规范中说明,若是个对象,则尝试将对象上的then方法取出来,此时如果报错,那就将promise2转为失败态。原文:

If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
    //代码略...if (x !== null && (typeof x === 'object' || typeof x === 'function')) {//可能是个对象或是函数try {let then = x.then;//取出then方法引用} catch (e) {reject(e);}} else {//否则是个普通值resolve(x);}
}

多说几句,为什么取对象上的属性有报错的可能?Promise有很多实现(bluebird,Q等),Promises/A+只是一个规范,大家都按此规范来实现Promise才有可能通用,因此所有出错的可能都要考虑到,假设另一个人实现的Promise对象使用Object.defineProperty()恶意的在取值时抛错,我们可以防止代码出现Bug。
此时,如果对象中有then,且then是函数类型,就可以认为是一个Promise对象,之后,使用x作为this来调用then方法。

//其他代码略...
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {//可能是个对象或是函数try {let then = x.then; if (typeof then === 'function') {//then是function,那么执行Promisethen.call(x, (y) => {resolve(y);}, (r) => {reject(r);});} else {resolve(x);}} catch (e) {reject(e);}} else {//否则是个普通值resolve(x);
}

这样链式写法就基本完成了。但是还有一种极端的情况,如果Promise对象转为成功态或是失败时传入的还是一个Promise对象,此时应该继续执行,直到最后的Promise执行完。

p.then(data => {return new Promise((resolve,reject)=>{//resolve传入的还是Promiseresolve(new Promise((resolve,reject)=>{resolve(2);}));});
})

此时就要使用递归操作了

很简单,把调用resolve改写成递归执行resolvePromise方法即可,这样直到解析Promise成一个普通值才会终止,即完成此规范:

//其他代码略...
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {//可能是个对象或是函数try {let then = x.then; if (typeof then === 'function') {let y = then.call(x, (y) => {//递归调用,传入y若是Promise对象,继续循环resolvePromise(promise2, y, resolve, reject);}, (r) => {reject(r);});} else {resolve(x);}} catch (e) {reject(e);}} else {//是个普通值,最终结束递归resolve(x);
}

到此,链式调用的代码已全部完毕。在相应的地方调用resolvePromise方法即可。

最后的最后

其实,写到此处Promise的真正源码已经写完了,但是距离100分还差一分,是什么呢?

规范中说明,Promise的then方法是异步执行的。

ES6的原生Promise对象已经实现了这一点,但是我们自己的代码是同步执行,不相信可以试一下,那么如何将同步代码变成异步执行呢?可以使用setTimeout函数来模拟一下:

setTimeout(()=>{//此入的代码会异步执行
},0);

利用此技巧,将代码then执行处的所有地方使用setTimeout变为异步即可,举个栗子:

setTimeout(() => {try {let x = onFulfilled(value);resolvePromise(promise2, x, resolve, reject);} catch (e) {reject(e);}
},0);

好了,现在已经是满分的Promise源码了。

附上完整代码

function Promise(executor) {
let self = this
this.status = 'pending' //当前状态
this.value = undefined  //存储成功的值
this.reason = undefined //存储失败的原因
this.onResolvedCallbacks = []//存储成功的回调
this.onRejectedCallbacks = []//存储失败的回调
function resolve(value) {if (self.status == 'pending') {self.status = 'resolved'self.value = valueself.onResolvedCallbacks.forEach(fn => fn());}
}
function reject(error) {if (self.status == 'pending') {self.status = 'rejected'self.reason = errorself.onRejectedCallbacks.forEach(fn => fn())}
}
try {executor(resolve, reject)
} catch (error) {reject(error)
}
}
Promise.prototype.then = function (infulfilled, inrejected) {
let self = this
let promise2
infulfilled = typeof infulfilled === 'function' ? infulfilled : function (val) {return val
}
inrejected = typeof inrejected === 'function' ? inrejected : function (err) {throw err
}
if (this.status == 'resolved') {promise2 = new Promise(function (resolve, reject) {//x可能是一个promise,也可能是个普通值setTimeout(function () {try {let x = infulfilled(self.value)resolvePromise(promise2, x, resolve, reject)} catch (err) {reject(err)}});})
}
if (this.status == 'rejected') {promise2 = new Promise(function (resolve, reject) {//x可能是一个promise,也可能是个普通值setTimeout(function () {try {let x = inrejected(self.reason)resolvePromise(promise2, x, resolve, reject)} catch (err) {reject(err)}});})
}
if (this.status == 'pending') {promise2 = new Promise(function (resolve, reject) {self.onResolvedCallbacks.push(function () {//x可能是一个promise,也可能是个普通值setTimeout(function () {try {let x = infulfilled(self.value)resolvePromise(promise2, x, resolve, reject)} catch (err) {reject(err)}});})self.onRejectedCallbacks.push(function () {//x可能是一个promise,也可能是个普通值setTimeout(function () {try {let x = inrejected(self.reason)resolvePromise(promise2, x, resolve, reject)} catch (err) {reject(err)}});})})
}
return promise2
}
function resolvePromise(p2, x, resolve, reject) {
if (p2 === x && x != undefined) {reject(new TypeError('类型错误'))
}
//可能是promise,看下对象中是否有then方法,如果有~那就是个promise
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {try {//为了防止出现 {then:11}这种情况,需要判断then是不是一个函数let then = x.thenif (typeof then === 'function') {then.call(x, function (y) {//y 可能还是一个promise,那就再去解析,知道返回一个普通值为止resolvePromise(p2, y, resolve, reject)}, function (err) {reject(err)})} else {//如果then不是function 那可能是对象或常量resolve(x)}} catch (e) {reject(e)}
} else {//说明是一个普通值resolve(x)
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/527045.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

12如何隐藏dock栏_一键隐藏 iPhone 刘海和底部 Dock 栏,简洁又好看

技能&#xff1a; 隐藏刘海和底部dock 栏难度系数&#xff1a;2颗星适用系统&#xff1a;iOS 13(部分非iOS13也适用)最近&#xff0c;小雷打开手机&#xff0c;看到最多的关键词&#xff0c;就是&#xff1a;iOS13又双叒叕更新了。。。而且也看到不止一位网友晒这个有趣的新功能…

宝塔php安装那个合_使用宝塔面板安装nextcloud | 启用本地存储 | 安装smbclient

宝塔面板安装nextcloud | 启用本地存储使用宝塔面板搭建nextcloud服务后&#xff0c;在设置外部存储时总是无法启用本地存储。问题1&#xff1a;提示&#xff1a;“smbclient” 未安装。无法挂载 "SMB / CIFS", "SMB / CIFS 使用 OC 登录信息"。请联系管理…

mysql groupby 拼接_mysql groupby 字段合并问题(group_concat)

在我们的日常mysql查询中&#xff0c;我们可能会遇到这样的情况&#xff1a;对表中的所有记录进行分类&#xff0c;并且我需要得到每个分类中某个字段的全部成员。上面的话&#xff0c;大家看起来可能不太好懂&#xff0c;下面举一个例子来给大家说明。现在我们有一张表&#x…

mysql聚集索引 myisam_一句话说清聚集索引和非聚集索引以及MySQL的InnoDB和MyISAM

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。https://blog.csdn.net/21aspnet/article/details/89303988聚集索引和非聚集索引以及MySQL的InnoDB和MyISAM经常遇到有人向我咨询这个问题&#xff0c;其实呢&#xff0c;网上帖子很多&#xff0c;也…

ue4插件导入导出_Blender到UE4的无缝衔接

Hello . 大家好本文给大家介绍一下Send To Unreal插件我是Vee1简介Send To Unreal是Epic官方开发的用于Blender和UE4快速同步的插件&#xff0c;支持静态物体、骨骼物体、动画等等。省去了Blender导出-选择目录文件-UE4导入这个中间步骤&#xff0c;效率提升不是一般得多。插件…

mysql点击计数器_MySql计数器,如网站点击数,如何实现高性能高并发的计数器功能...

MySql计数器&#xff0c;如网站点击数&#xff0c;如何实现高性能高并发的计数器功能Clicks: 5338 Date: 2014-03-29 23:30:42 Power By 李轩LaneTagMysql计数器高性能现在有很多的项目&#xff0c;对计数器的实现甚是随意&#xff0c;比如在实现网站文章点击数的时候&#xff…

python 微服务架构_微服务架构(Python)

在后端开发方面&#xff0c;Java的使用呢要远比Python广泛&#xff0c;所以Java的微服务框架非常流行&#xff0c;但Python的微服务框架却很少有人问津。在大多数需要微服务的场合下直接用Java的各种工具就可以解决问题&#xff0c;但如果业务代码使用Python写的&#xff0c;那…

vue修改入口文件名字_webpack打包vue项目,可修改配置文件

问题&#xff1a;vue项目打包完成后&#xff0c;如需改变配置文件中的信息&#xff0c;比如域名修改(如下图config.js)&#xff0c;是不可能在配置文件中直接更改的&#xff0c;因为配置文件是前端写死的&#xff0c;这时只能手动更改项目中的配置&#xff0c;然后重新打包npm …

java判断正整数正则_Java正则验证正整数的方法分析【测试可用】

本文实例讲述了Java正则验证正整数的方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;package des;import java.util.regex.Matcher;import java.util.regex.Pattern;public class Num {/*** param args*/public static void main(String[] args) {// TODO Auto-ge…

java 官网下载jdk源码_openJDK之如何下载各个版本的openJDK源码

如果我们需要阅读openJDK的源码&#xff0c;那么需要下载&#xff0c;那么该去哪下载呢?现在JDK已经发展到版本10了&#xff0c;11已经处于计划中&#xff0c;如果需要特定版本的openJDK&#xff0c;它们的下载链接在哪呢?1.openJDK的项目图1 可以看到有openJDK6、openJDK7、…

java 最大分词算法_Java实现的最大匹配分词算法详解

本文实例讲述了java实现的最大匹配分词算法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;全文检索有两个重要的过程&#xff1a;1分词2倒排索引我们先看分词算法目前对中文分词有两个方向&#xff0c;其中一个是利用概率的思想对文章分词。 也就是如果两个字&#x…

java jnotify_Jnotify文件监控的用法以及Jar文件导入的方法

简介Jnotiy, 支持动态监控(支持级联监控)文件夹和文件的jar包。在linux中&#xff0c;调用linux底层的jnotify服务。在windows中&#xff0c;需要添加附件的dll文件。因为通用的Maven仓库中没有此Jar文件&#xff0c;pom.xml文件需要如下配置&#xff1a;net.contentobjects.jn…

java 异常练习题_Java 异常(习题)

异常Key Point* 异常的概念和分类* 异常的产生和传递* 异常的处理* 自定义异常练习1. 填空Java 中所有的错误都继承自throwable类&#xff1b;在该类的子类中&#xff0c;Error类表示严重的底层错误&#xff0c;对于这类错误一般处理的方式是不要求我们对其处理Exception类表示…

mysql 半同步 主主_MySQL主从,半同步,主主复制

MySQL Replication我们知道&#xff0c;MySQL数据库的二进制日志记录着每一个明确或者潜在可能导致数据库发生改变的sql语句&#xff0c;因此我们可以基于二进制日志来实现mysql的主从一致。而我们在此提到的mysql的复制的简单过程就是&#xff1a;首先mysql的主服务器(Master)…

java 数据队列_Java 数据结构 - 队列

Java 数据结构 - 队列我们今天要讲的数据结构是队列&#xff0c;比如 Java 线程池任务就是队列实现的。1. 什么是队列和栈一样&#xff0c;队列也是一种操作受限的线性结构。使用队列时&#xff0c;在一端插入元素&#xff0c;而在另一端删除元素。1.1 队列的主要特性队列中的数…

java+set+split_阿里资深工程师教你如何优化 Java 代码!

原标题&#xff1a;阿里资深工程师教你如何优化 Java 代码&#xff01;作者 | 王超责编 | 伍杏玲明代王阳明先生在《传习录》谈为学之道时说&#xff1a;私欲日生&#xff0c;如地上尘&#xff0c;一日不扫&#xff0c;便又有一层。着实用功&#xff0c;便见道无终穷&#xff0…

myVariable是java标识符吗_java 标识符与变量

一、Java 标识符三要素1.标识符由字母、下划线(_)、美元符号($)或者字母组成。2.标识符应以字母、下划线(_)、美元符开头。3.标识符字符大小写敏感&#xff0c;长度无限制。标识符最重要的就是 见名知意并且不能与java关键字重名!二、Java 变量1.java变量是程序中最基本的单元。…

友盟统计java代码_SFAnalytics 分析友盟统计源码,反编译 SDK,还有部分没有 出来 android 259万源代码下载- www.pudn.com...

文件名称: SFAnalytics下载 收藏√ [5 4 3 2 1 ]开发工具: Java文件大小: 8023 KB上传时间: 2016-06-05下载次数: 0提 供 者: 花心大萝卜详细说明&#xff1a;分析友盟统计源码&#xff0c;反编译友盟统计SDK&#xff0c;还有部分没有反编译出来-Analysis their Allies s…

java mvc数据库 封装_关于SpringMvc参数封装_JavaEE框架(Maven+SpringMvc+Spring+MyBatis)全程实战教程_Java视频-51CTO学院...

SpringMVCSpring MVC属于SpringFrameWork的后续产品&#xff0c;已经融合在Spring Web Flow里面。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色&#xff0c;这种分离让它们更容易进行定制。SpringSpring是一个开源框架&#xff0c;Spring是于2003 年兴起的…

20199计算机二级java答案_计算机二级Java练习题-2019.9

是不是急于做大量的计算机等级考试题库&#xff0c;却因测试结果不尽人意而心慌不安&#xff1f;不要急&#xff01;考无忧小编为大家准备了一些二级Java练习题&#xff0c;希望能帮助大家高效复习&#xff0c;轻松通关&#xff01;1.下列叙述中正确的是()。A.栈是“先进先出”…