记录--通过手写,分析axios核心原理
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
一、axios简介
axios是什么?
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
axios有什么特性?(不得不说面试被问到几次)
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
实际上,axios可以用在浏览器和 node.js 中是因为,它会自动判断当前环境是什么,如果是浏览器,就会基于XMLHttpRequests实现axios。如果是node.js环境,就会基于node内置核心模块http实现axios
简单来说,axios的基本原理就是
- axios还是属于 XMLHttpRequest, 因此需要实现一个ajax。或者基于http 。
- 还需要一个promise对象来对结果进行处理。
有什么不理解的或者是建议欢迎评论提出.项目已经放到 github:github.com/Sunny-lucki…
二、基本使用方式
axios基本使用方式主要有
- axios(config)
- axios.method(url, data , config)
// index.html文件
<html>
<script type="text/javascript" src="axios"></script>
<body>
<button class="btn">点我发送请求</button>
<script>
document.querySelector('.btn').onclick = function() {
// 分别使用以下方法调用,查看myaxios的效果
axios.post('/postAxios', {
name: '小美post'
}).then(res => {
console.log('postAxios 成功响应', res);
}) axios({
method: 'post',
url: '/getAxios'
}).then(res => {
console.log('getAxios 成功响应', res);
})
}
</script>
</body>
</html>
</html>
三、实现axios和axios.method
从axios(config)的使用上可以看出导出的axios是一个方法。从axios.method(url, data , config)的使用可以看出导出的axios上或者原型上挂有get,post等方法
实际上导出的axios就是一个Axios类中的一个方法。
如代码所以,核心代码是request。我们把request导出,就可以使用axios(config)这种形式来调用axios了。
class Axios {
constructor() { } request(config) {
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
}
xhr.send(data);
})
}
}
怎么导出呢?十分简单,new Axios,获得axios实例,再获得实例上的request方法就好了。
// 最终导出axios的方法,即实例的request方法
function CreateAxiosFn() {
let axios = new Axios();
let req = axios.request.bind(axios);
return req;
} // 得到最后的全局变量axios
let axios = CreateAxiosFn();
现在axios实际上就是request方法。
你可能会很疑惑,因为我当初看源码的时候也很疑惑:干嘛不直接写个request方法,然后导出呢?非得这样绕这么大的弯子。别急。后面慢慢就会讲到。
现在一个简单的axios就完成了,我们来引入myAxios.js文件并测试一下可以使用不?
简单的搭建服务器:
//server.js
var express = require('express');
var app = express(); //设置允许跨域访问该服务.
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
}); app.get('/getTest', function(request, response){
data = {
'FrontEnd':'前端',
'Sunny':'阳光'
};
response.json(data);
});
var server = app.listen(5000, function(){
console.log("服务器启动");
});
index.html
//index.html
<script type="text/javascript" src="./myAxios.js"></script> <body>
<button class="btn">点我发送请求</button>
<script>
document.querySelector('.btn').onclick = function() {
// 分别使用以下方法调用,查看myaxios的效果
axios({
method: 'get',
url: 'http://localhost:5000/getTest'
}).then(res => {
console.log('getAxios 成功响应', res);
})
}
</script>
</body>
点击按钮,看看是否能成功获得数据。
可喜可贺,成功。
现在我们来实现下axios.method()的形式。
思路。我们可以再Axios.prototype添加这些方法。而这些方法内部调用request方法即可,如代码所示:
// 定义get,post...方法,挂在到Axios原型上
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {
Axios.prototype[met] = function() {
console.log('执行'+met+'方法');
// 处理单个方法
if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])
return this.request({
method: met,
url: arguments[0],
...arguments[1] || {}
})
} else { // 3个参数(url[,data[,config]])
return this.request({
method: met,
url: arguments[0],
data: arguments[1] || {},
...arguments[2] || {}
})
} }
})
我们通过遍历methodsArr数组,依次在Axios.prototype添加对应的方法,注意的是'get
', 'delete
', 'head
', 'options
'这些方法只接受两个参数。而其他的可接受三个参数,想一下也知道,get不把参数放body的。
但是,你有没有发现,我们只是在Axios的prototype上添加对应的方法,我们导出去的可是request方法啊,那怎么办? 简单,把Axios.prototype上的方法搬运到request上即可。
我们先来实现一个工具方法,实现将b的方法混入a;
const utils = {
extend(a,b, context) {
for(let key in b) {
if (b.hasOwnProperty(key)) {
if (typeof b[key] === 'function') {
a[key] = b[key].bind(context);
} else {
a[key] = b[key]
}
} }
}
}
然后我们就可以利用这个方法将Axios.prototype
上的方法搬运到request上啦。
我们修改一下之前的CreateAxiosFn
方法即可
function CreateAxiosFn() {
let axios = new Axios(); let req = axios.request.bind(axios);
增加代码
utils.extend(req, Axios.prototype, axios) return req;
}
现在来测试一下能不能使用axios.get()这种形式调用axios。
<body>
<button class="btn">点我发送请求</button>
<script>
document.querySelector('.btn').onclick = function() { axios.get('http://localhost:5000/getTest')
.then(res => {
console.log('getAxios 成功响应', res);
}) }
</script>
</body>
害,又是意料之中成功。
再完成下一个功能之前,先给上目前myAxios.js的完整代码
class Axios {
constructor() { } request(config) {
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
console.log(config);
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
}
xhr.send(data);
})
}
} // 定义get,post...方法,挂在到Axios原型上
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {
Axios.prototype[met] = function() {
console.log('执行'+met+'方法');
// 处理单个方法
if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])
return this.request({
method: met,
url: arguments[0],
...arguments[1] || {}
})
} else { // 3个参数(url[,data[,config]])
return this.request({
method: met,
url: arguments[0],
data: arguments[1] || {},
...arguments[2] || {}
})
} }
}) // 工具方法,实现b的方法或属性混入a;
// 方法也要混入进去
const utils = {
extend(a,b, context) {
for(let key in b) {
if (b.hasOwnProperty(key)) {
if (typeof b[key] === 'function') {
a[key] = b[key].bind(context);
} else {
a[key] = b[key]
}
} }
}
} // 最终导出axios的方法-》即实例的request方法
function CreateAxiosFn() {
let axios = new Axios(); let req = axios.request.bind(axios);
// 混入方法, 处理axios的request方法,使之拥有get,post...方法
utils.extend(req, Axios.prototype, axios)
return req;
} // 得到最后的全局变量axios
let axios = CreateAxiosFn();
四、请求和响应拦截器
我们先看下拦截器的使用
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}); // 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
拦截器是什么意思呢?其实就是在我们发送一个请求的时候会先执行请求拦截器的代码,然后再真正地执行我们发送的请求,这个过程会对config,也就是我们发送请求时传送的参数进行一些操作。
而当接收响应的时候,会先执行响应拦截器的代码,然后再把响应的数据返回来,这个过程会对response,也就是响应的数据进行一系列操作。
怎么实现呢?需要明确的是拦截器也是一个类,管理响应和请求。因此我们先实现拦截器
class InterceptorsManage {
constructor() {
this.handlers = [];
} use(fullfield, rejected) {
this.handlers.push({
fullfield,
rejected
})
}
}
我们是用这个语句axios.interceptors.response.use
和axios.interceptors.request.use
,来触发拦截器执行use方法的。
说明axios上有一个响应拦截器和一个请求拦截器。那怎么实现Axios呢?看代码
class Axios {
constructor() {
新增代码
this.interceptors = {
request: new InterceptorsManage,
response: new InterceptorsManage
}
} request(config) {
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
console.log(config);
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
};
xhr.send(data);
})
}
}
可见,axios实例上有一个对象interceptors。这个对象有两个拦截器,一个用来处理请求,一个用来处理响应。
所以,我们执行语句axios.interceptors.response.use
和axios.interceptors.request.use
的时候,实现获取axios实例上的interceptors对象,然后再获取response或request拦截器,再执行对应的拦截器的use方法。
而执行use方法,会把我们传入的回调函数push到拦截器的handlers数组里。
到这里你有没有发现一个问题。这个interceptors对象是Axios上的啊,我们导出的是request方法啊(欸?好熟悉的问题,上面提到过哈哈哈~~~额)。处理方法跟上面处理的方式一样,都是把Axios上的方法和属性搬到request过去,也就是遍历Axios实例上的方法,得以将interceptors对象挂载到request上。
所以只要更改下CreateAxiosFn方法即可。
function CreateAxiosFn() {
let axios = new Axios(); let req = axios.request.bind(axios);
// 混入方法, 处理axios的request方法,使之拥有get,post...方法
utils.extend(req, Axios.prototype, axios)
新增代码
utils.extend(req, axios)
return req;
}
好了,现在request也有了interceptors对象,那么什么时候拿interceptors对象中的handler之前保存的回调函数出来执行。
没错,就是我们发送请求的时候,会先获取request拦截器的handlers的方法来执行。再执行我们发送的请求,然后获取response拦截器的handlers的方法来执行。
因此,我们要修改之前所写的request方法 之前是这样的。
request(config) {
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
console.log(config);
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
};
xhr.send(data);
})
}
但是现在request里不仅要执行发送ajax请求,还要执行拦截器handlers中的回调函数。所以,最好下就是将执行ajax的请求封装成一个方法
request(config) {
this.sendAjax(config)
}
sendAjax(config){
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
console.log(config);
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
};
xhr.send(data);
})
}
好了,现在我们要获得handlers中的回调
request(config) {
// 拦截器和请求组装队列
let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理 // 请求拦截
this.interceptors.request.handlers.forEach(interceptor => {
chain.unshift(interceptor.fullfield, interceptor.rejected)
}) // 响应拦截
this.interceptors.response.handlers.forEach(interceptor => {
chain.push(interceptor.fullfield, interceptor.rejected)
}) // 执行队列,每次执行一对,并给promise赋最新的值
let promise = Promise.resolve(config);
while(chain.length > 0) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise;
}
我们先把sendAjax请求和undefined放进了chain数组里,再把请求拦截器的handlers的成对回调放到chain数组头部。再把响应拦截器的handlers的承兑回调反倒chain数组的尾部。
然后再 逐渐取数 chain数组的成对回调执行。
promise = promise.then(chain.shift(), chain.shift())
这一句,实际上就是不断将config从上一个promise传递到下一个promise,期间可能回调config做出一些修改。什么意思?我们结合一个例子来讲解一下
首先拦截器是这样使用的
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}); // 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
然后执行request的时候。chain数组的数据是这样的
chain = [
function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
this.sendAjax.bind(this), undefined, function (response) {
// 对响应数据做点什么
return response;
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error);
}
]
首先
执行第一次promise.then(chain.shift(), chain.shift())
,即
promise.then(
function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
)
一般情况,promise是resolved状态,是执行成功回调的,也就是执行
function (config) {
// 在发送请求之前做些什么
return config;
},
而promise.then是要返回一个新的promise对象的。
为了区分,在这里,我会把这个新的promise对象叫做第一个新的promise对象 这个第一个新的promise对象会把
function (config) {
// 在发送请求之前做些什么
return config;
},
的执行结果传入resolve函数中
resolve(config)
使得这个返回的第一个新的promise对象的状态为resovled,而且第一个新的promise对象的data为config。
这里需要对Promise的原理足够理解。所以我前一篇文章写的是手写Promise核心原理,再也不怕面试官问我Promise原理,你可以去看看 接下来,再执行
promise.then(
sendAjax(config)
,
undefined
)
注意:这里的promise是 上面提到的第一个新的promise对象。
而promise.then这个的执行又会返回第二个新的promise对象。
因为这里promise.then中的promise也就是第一个新的promise对象的状态是resolved的,所以会执行sendAjax()。而且会取出第一个新的promise对象的data 作为config转入sendAjax()。
当sendAjax执行完,就会返回一个response。这个response就会保存在第二个新的promise对象的data中。
接下来,再执行
promise.then(
function (response) {
// 对响应数据做点什么
return response;
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error);
}
)
同样,会把第二个新的promise对象的data取出来作为response参数传入
function (response) {
// 对响应数据做点什么
return response;
},
饭后返回一个promise对象,这个promise对象的data保存了这个函数的执行结果,也就是返回值response。
然后通过return promise;
把这个promise返回了。咦?是怎么取出promise的data的。我们看看我们平常事怎么获得响应数据的
axios.get('http://localhost:5000/getTest')
.then(res => {
console.log('getAxios 成功响应', res);
})
在then里接收响应数据。所以原理跟上面一样,将返回的promise的data作为res参数了。
现在看看我们的myAxios完整代码吧,好有个全面的了解
class InterceptorsManage {
constructor() {
this.handlers = [];
} use(fullfield, rejected) {
this.handlers.push({
fullfield,
rejected
})
}
} class Axios {
constructor() {
this.interceptors = {
request: new InterceptorsManage,
response: new InterceptorsManage
}
} request(config) {
// 拦截器和请求组装队列
let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理 // 请求拦截
this.interceptors.request.handlers.forEach(interceptor => {
chain.unshift(interceptor.fullfield, interceptor.rejected)
}) // 响应拦截
this.interceptors.response.handlers.forEach(interceptor => {
chain.push(interceptor.fullfield, interceptor.rejected)
}) // 执行队列,每次执行一对,并给promise赋最新的值
let promise = Promise.resolve(config);
while(chain.length > 0) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise;
}
sendAjax(){
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
console.log(config);
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
};
xhr.send(data);
})
}
} // 定义get,post...方法,挂在到Axios原型上
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {
Axios.prototype[met] = function() {
console.log('执行'+met+'方法');
// 处理单个方法
if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])
return this.request({
method: met,
url: arguments[0],
...arguments[1] || {}
})
} else { // 3个参数(url[,data[,config]])
return this.request({
method: met,
url: arguments[0],
data: arguments[1] || {},
...arguments[2] || {}
})
} }
}) // 工具方法,实现b的方法混入a;
// 方法也要混入进去
const utils = {
extend(a,b, context) {
for(let key in b) {
if (b.hasOwnProperty(key)) {
if (typeof b[key] === 'function') {
a[key] = b[key].bind(context);
} else {
a[key] = b[key]
}
} }
}
} // 最终导出axios的方法-》即实例的request方法
function CreateAxiosFn() {
let axios = new Axios(); let req = axios.request.bind(axios);
// 混入方法, 处理axios的request方法,使之拥有get,post...方法
utils.extend(req, Axios.prototype, axios)
return req;
} // 得到最后的全局变量axios
let axios = CreateAxiosFn();
来测试下拦截器功能是否正常
<script type="text/javascript" src="./myAxios.js"></script> <body>
<button class="btn">点我发送请求</button>
<script>
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
config.method = "get";
console.log("被我请求拦截器拦截了,哈哈:",config);
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}); // 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
console.log("被我响应拦截拦截了,哈哈 ");
response = {message:"响应数据被我替换了,啊哈哈哈"}
return response;
}, function (error) {
// 对响应错误做点什么
console.log("错了吗");
return Promise.reject(error);
});
document.querySelector('.btn').onclick = function() {
// 分别使用以下方法调用,查看myaxios的效果
axios({
url: 'http://localhost:5000/getTest'
}).then(res => {
console.log('response', res);
})
}
</script>
</body>
拦截成功!!!!!
本文转载于:
https://juejin.cn/post/6856706569263677447
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
记录--通过手写,分析axios核心原理的更多相关文章
- 【面试题】手写async await核心原理,再也不怕面试官问我async await原理
前言 async await 语法是 ES7出现的,是基于ES6的 promise和generator实现的 generator函数 在之前我专门讲个generator的使用与原理实现,大家没了解过的 ...
- 通过源码分析RocketMQ主从复制原理
作者:京东物流 宫丙来 一.主从复制概述 RocketMQ Broker的主从复制主要包括两部分内容:CommitLog的消息复制和Broker元数据的复制. CommitLog的消息复制是发生在消息 ...
- 30个类手写Spring核心原理之环境准备(1)
本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...
- Java Reference核心原理分析
本文转载自Java Reference核心原理分析 导语 带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用Byte ...
- Redis核心原理与实践--事务实践与源码分析
Redis支持事务机制,但Redis的事务机制与传统关系型数据库的事务机制并不相同. Redis事务的本质是一组命令的集合(命令队列).事务可以一次执行多个命令,并提供以下保证: (1)事务中的所有命 ...
- 30个类手写Spring核心原理之依赖注入功能(3)
本文节选自<Spring 5核心原理> 在之前的源码分析中我们已经了解到,依赖注入(DI)的入口是getBean()方法,前面的IoC手写部分基本流程已通.先在GPApplicationC ...
- 抽丝剥茧分析asyncio事件调度的核心原理
先来看一下一个简单的例子 例1: async def foo(): print('enter foo ...') await bar() print('exit foo ...') async def ...
- 手写webpack核心原理,再也不怕面试官问我webpack原理
手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...
- Redis核心原理与实践--Redis启动过程源码分析
Redis服务器负责接收处理用户请求,为用户提供服务. Redis服务器的启动命令格式如下: redis-server [ configfile ] [ options ] configfile参数指 ...
- 30个类手写Spring核心原理之AOP代码织入(5)
本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...
随机推荐
- NC20573 [SDOI2011]染色
题目链接 题目 题目描述 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如 ...
- 关于动态抽样(Dynamic Sampling)
关于动态抽样(Dynamic Sampling) 原文:http://www.oracle.com/technetwork/issue-archive/2009/09-jan/o19asktom-08 ...
- Oracle如何限制非法调用包中过程
原文:http://www.oracle.com/technetwork/issue-archive/2015/15-jan/o15plsql-2398996.html 假如我有一个包P_A,其中封装 ...
- 溯源反制-Mysql蜜罐
东西比较老,类似的文章网上已经很多,原理主要是通过服务端的load data动作可以主动向客户端获取文件. 看过hfish等自带的mysql蜜罐读取/etc/passwd,感觉还差点实用性.这次文章主 ...
- 《系列二》-- 2、bean 的作用域: Scope 有哪些
目录 作用域 Scope 特性概述 常规作用域 web 场景作用域 经典问题 模拟场景 解决办法 方法一 方法二 实现接口 BeanFactoryAware 阅读之前要注意的东西:本文就是主打流水账式 ...
- Sourcetree 如何关联自己的gitlab仓库
现在有些企业自己搭建了gitlab服务器,通过sourcetree从企业服务器拉取代码的时候会提示认证失败.今天搞了大半天才搞懂,给我自己做个笔记. 添加账户 托管服务商 选择 GitLab CE 托 ...
- 矩池云上 git clone --recursive 出错,怎么解决
遇到问题 有时候安装包教程里 git clone 的时候会出现以下错误: git clone --recursive https://github.91chi.fun/https://github.c ...
- iOS日志操作与开发,你真的会重视吗
iOS中常用日志和上报系统浅析 类CocoaLumberjack日志框架架构浅析 Crash的类型介绍和常用收集方案 常用上报技术方案对比和分析
- DataGear 自定义数据可视化看板的图表主题
DataGear 看板的 dg-chart-theme 属性,提供了简单且强大的自定义图表主题功能. 通常,只需要设置其color.backgroundColor.actualBackgroundCo ...
- 【Azure Logic App】添加 Storage Account 来提升 Logic App 的性能
文章原文:https://techcommunity.microsoft.com/t5/azure-integration-services-blog/scaling-logic-app-standa ...