express&koa
面试题目:1.express 和 koa 的对比,两者中间件的原理,koa 捕获异常多种情况说一下
参考:https://blog.csdn.net/shmnh/a…https://blog.csdn.net/K616358…https://blog.csdn.net/wang839…async 函数:http://www.ruanyifeng.com/blo…
初识两者
express:
var express = require(‘express’)
var app = express() // 创建一个 APP 实例
// 建一个项目根目录的 get 请求路由,回调方法中直接输出字符串 Hello World!
app.get(‘/’, function (req, res) {
res.send(‘Hello World!’)
});
// 监听端口,启动服务
app.listen(3000);
koa:
var koa = require(‘koa’);
var route = require(‘koa-route’); //koa 默认没有集成 route 功能,引入中间件
var app = koa(); // 创建一个 APP 实例
// 建一个项目根目录的 get 请求路由,回调方法中直接输出字符串 Hello World!,就是挂载一个中间件
app.use(route.get(‘/’, function *(){
this.body = ‘Hello World’;
}));
// 监听端口,启动服务
app.listen(3000);
启动方式
koa 采用了 new Koa()的方式,而 express 采用传统的函数形式,对比源码如下:
//koa
const Emitter = require(‘events’);
module.exports = class Application extends Emitter {
…
}
//express
exports = module.exports = createApplication;
function createApplication() {
…
}
应用生命周期和上下文
在项目过程中,经常需要用到在整个应用生命周期中共享的配置和数据对象,比如服务 URL、是否启用某个功能特性、接口配置、当前登录用户数据等等。
express:
// 共享配置,express 提供了很多便利的方法
app.set(‘enableCache’, true)
app.get(‘enableCache’)//true
app.disable(‘cache’)
app.disabled(‘cache’)//true
app.enable(‘cache’)
app.enabled(‘cache’)//true
// 应用共享数据:app.locals
app.locals.user = {name:”Samoay”, id:1234};
koa:
// 配置,直接使用 koa context 即可
app.enableCache = true;
app.use(function *(next){
console.log(this.app.enableCache);
//true
this.app.enableCache = false;
//just use this
this.staticPath = ‘static’;
yield *next;
});
// 应用共享数据:ctx.state
this.state.user = {name:”Samoay”, id:1234};
请求 HTTP Request
服务器端需要进行什么处理,怎么处理以及处理的参数都依赖客户端发送的请求,两个框架都封装了 HTTP Request 对象,便于对这一部分进行处理。以下主要举例说明下对请求参数的处理。GET 参数都可以直接通过 Request 对象获取,POST 参数都需要引入中间件先 parse,再取值。
express:
// 获取 QueryString 参数
// GET /shoes?order=desc&shoe[color]=blue
req.query.order
// => “desc”
req.query.shoe.color
// => “blue”
// 通过路由获取 Restful 风格的 URL 参数
app.get(‘/user/:id?’, function userIdHandler(req, res) {
console.log(req.params.id);
res.send(‘GET’);
})
// 获取 POST 数据: 需要 body-parser 中间件
var bodyParser = require(‘body-parser’);
app.use(bodyParser.urlencoded({ extended: true}));
app.post(‘/’, function (req, res) {
console.log(req.body);
res.json(req.body);
koa:
// 获取 QueryString 参数
// GET /?action=delete&id=1234
this.request.query
// => {action: ‘delete’, id: ‘1234’}
// 通过路由获取 Restful 风格的 URL 参数
var route = require(‘koa-route’);
app.use(route.get(‘/post/:id’, function *(id){
console.log(id);
// => 1234
}));
// 获取 POST 数据: 需要 co-body 中间件
// Content-Type: application/x-www-form-urlencoded
// title=Test&content=This+is+a+test+post
var parse = require(‘co-body’);
app.use(route.post(‘/post/new’, function *(){
var post = yield parse(this.request);//this
console.log(post);
// => {title: ‘Test’, content: ‘This is a test post’}
}));
路由 Route
收到客户端的请求,服务需要通过识别请求的方法(HTTP Method: GET, POST, PUT…)和请求的具体路径 (path) 来进行不同的处理。这部分功能就是路由(Route)需要做的事情,说白了就是请求的分发,分发到不同的回调方法去处理。
express
// app.all 表示对所有的路径和请求方式都要经过这些回调方法的处理,可以逗号方式传入多个
app.all(‘*’, authentication, loadUser);
// 也可以多次调用
app.all(‘*’, requireAuthentication)
app.all(‘*’, loadUser);
// 也可以针对某具体路径下面的所有请求
app.all(‘/api/*’, requireAuthentication);
// app.get GET 方式的请求
app.get(‘/user/:id’, function(req, res) {
res.send(‘user ‘ + req.params.id);
});
// app.post POST 方式的请求
app.post(‘/user/create’, function(req, res) {
res.send(‘create new user’);
});
这里需要说明 2 个问题,首先是 app.get,在应用生命周期中也有一个 app.get 方法,用于获取项目配置。Express 内部就是公用的一个方法,如果传入的只有 1 个参数就获取配置,2 个参数就作为路由处理。其次是 app.use(”, cb) 与 app.all(”, cb) 的区别,前者是中间件方式,调用是有顺序的,不一定会执行到;后者是路由方式,肯定会执行到。
koa
// Koa
// 和 Express 不同,koa 需要先引入 route 中间件
var route = require(‘koa-route’);
// 引入中间件之后支持的写法差不多,只是路径传入 route,然后把 route 作为中间件挂载到 app
app.use(route.get(‘/’, list));
app.use(route.get(‘/post/new’, add));
app.use(route.get(‘/post/:id’, show));
app.use(route.post(‘/post’, create));
// 链式写法
var router = require(‘koa-router’)();
router.get(‘/’, list)
.get(‘/post/new’, add)
.get(‘/post/:id’, show)
.post(‘/post’, create);
app.use(router.routes())
.use(router.allowedMethods());
视图 view
Express 框架自身集成了视图功能,提供了 consolidate.js 功能,可以是有几乎所有 Javascript 模板引擎,并提供了视图设置的便利方法。Koa 需要引入 co-views 中间件,co-views 也是基于 consolidate.js,支持能力一样强大。
express
// Express
// 这只模板路径和默认的模板后缀
app.set(‘views’, __dirname + ‘/tpls’);
app.set(‘view engine’, ‘html’);
// 默认,express 根据 template 的后缀自动选择模板
// 引擎渲染,支持 jade 和 ejs。如果不使用默认扩展名
app.engine(ext, callback)
app.engine(‘html’, require(‘ejs’).renderFile);
// 如果模板引擎不支持(path, options, callback)
var engines = require(‘consolidate’);
app.engine(‘html’, engines.handlebars);
app.engine(‘tpl’, engines.underscore);
app.get(‘list’, function(res, req){
res.render(‘list’, {data});
});
koa
// 需要引入 co-views 中间件
var views = require(‘co-views’);
var render = views(‘tpls’, {
map: {html: ‘swig’},//html 后缀使用引擎
default: “jade”//render 不提供后缀名时
});
var userInfo = {
name: ‘tobi’,
species: ‘ferret’
};
var html;
html = render(‘user’, { user: userInfo});
html = render(‘user.jade’, { user: userInfo});
html = render(‘user.ejs’, { user: userInfo});
返回 HTTP Response
获取完请求参数、处理好了具体的请求、视图也准备就绪,下面就该返回给客户端了,那就是 HTTP Response 对象了。这部分也属于框架的基础部分,各种都做了封装实现,显著的区别是 koa 直接将输出绑定到了 ctx.body 属性上,另外输出 JSON 或 JSONP 需要引入中间件。
express
// 输出普通的 html
res.render(‘tplName’, {data});
// 输出 JSON
res.jsonp({user: ‘Samoay’});
// => {“user”: “Samoay”}
// 输出 JSONP ?callback=foo
res.jsonp({user: ‘Samoay’});
// => foo({“user”: “Samoay”});
//res.send([body]);
res.send(new Buffer(‘whoop’));
res.send({some: ‘json’});
res.send(‘<p>some html</p>’);
// 设定 HTTP Status 状态码
res.status(200);
koa
app.use(route.get(‘/post/update/:id’, function *(id){
this.status = 404;
this.body = ‘Page Not Found’;
}));
var views = require(‘co-views’);
var render = views(‘tpls’, {
default: “jade”//render 不提供后缀名时
});
app.use(route.get(‘/post/:id’, function *(id){
var post = getPost(id);
this.status = 200;//by default, optional
this.body = yield render(‘user’, post);
}));
//JSON
var json = require(‘koa-json’);
app.use(route.get(‘/post/:id’, function *(id){
this.body = {id:1234, title:”Test post”, content:”…”};
}));
中间件 Middleware
对比了主要的几个框架功能方面的使用,其实区别最大,使用方式最不同的地方是在中间件的处理上。Express 由于是在 ES6 特性之前的,中间件的基础原理还是 callback 方式的;而 koa 得益于 generator 特性和 co 框架(co 会把所有 generator 的返回封装成为 Promise 对象),使得中间件的编写更加优雅。
express
// req 用于获取请求信息,ServerRequest 的实例
// res 用于响应处理结果,ServerResponse 的实例
// next() 函数用于将当前控制权转交给下一步处理,
// 如果给 next() 传递一个参数时,表示出错信息
var x = function (req, res, next) {
// 对 req 和 res 进行必要的处理
// 进入下一个中间件
return next();
// 传递错误信息到下一个中间件
return next(err);
// 直接输出,不再进入后面的中间件
return res.send(‘show page’);
};
koa
// koa 一切都在 ctx 对象上 +generator
app.use(function *(){
this; // is the Context
this.request; // is a koa Request
this.response; // is a koa Response
this.req;// is node js request
this.res;// is node js response
// 不再进入后面的中间件, 回溯 upstream
return;
});
express 处理多个中间件:
const app = require(“express”)();
app.use((req,res,next)=>{
console.log(“first”);
//next();
});
app.use((req,res,next)=>{
console.log(“second”);
//next();
});
app.use((req,res,next)=>{
console.log(“third”);
res.status(200).send(“<h1>headers …</h1>”);
});
app.listen(3001);
koa 处理多个中间件:
const Koa = require(‘koa’);
const app = new Koa();
app.use((ctx,next) => {
ctx.body = ‘Hello Koa-1’;
next();
});
app.use((ctx,next) => {
ctx.body = ‘Hello Koa-2’;
next();
});
app.use((ctx,next) => {
ctx.body = ‘Hello Koa-3’;
next();
});
app.listen(3000);
/* 与 express 类似,koa 中间件的入参也有两个,
后一个就是 next。next 的功能与 express 一样 */
/* 上面介绍了 koa 的 next()的功能,这里的 next()需要同步调用,千万不要采用异步调用
*/
koa 捕获异常
异常捕获
const http = require(‘http’);
const https = require(‘https’);
const Koa = require(‘koa’);
const app = new Koa();
app.use((ctx)=>{
str=”hello koa2″;// 沒有声明变量
ctx.body=str;
})
app.on(“error”,(err,ctx)=>{// 捕获异常记录错误日志
console.log(new Date(),”:”,err);
});
http.createServer(app.callback()).listen(3000);
上面的代码运行后在浏览器访问返回的结果是“Internal Server error”; 我们发现当错误发生的时候后端程序并没有死掉,只是抛出了异常,前端也同时接收到了错误反馈,对于 KOA 来说,异常发生在中间件的执行过程中,所以只要我们在中间件执行过程中将异常捕获并处理就 OK 了。
添加中间键 use 方法
use(fn) {
if (typeof fn !== ‘function’) throw new TypeError(‘middleware must be a function!’);
if (isGeneratorFunction(fn)) {
deprecate(‘Support for generators will be removed in v3. ‘ +
‘See the documentation for examples of how to convert old middleware ‘ +
‘https://github.com/koajs/koa/blob/master/docs/migration.md’);
fn = convert(fn);
}
debug(‘use %s’, fn._name || fn.name || ‘-‘);
this.middleware.push(fn);
return this;
}
/*fn 可以是三种类型的函数,普通函数,generator 函数,
还有 async 函数。最后 generator 会被转成 async 函数,。
所以最终中间件数组只会有普通函数和 async 函数。*/
异常处理
当异常捕获是有两种处理方式,一种就是响应错误请求,而就是触发注册注册全局错误事件,比如记录错误日志
async 函数
一句话,async 函数就是 Generator 函数的语法糖。
前文有一个 Generator 函数,依次读取两个文件:
var fs = require(‘fs’);
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile(‘/etc/fstab’);
var f2 = yield readFile(‘/etc/shells’);
console.log(f1.toString());
console.log(f2.toString());
};
写成 async 函数,就是下面这样:
var asyncReadFile = async function (){
var f1 = await readFile(‘/etc/fstab’);
var f2 = await readFile(‘/etc/shells’);
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
async 函数的优点(1)内置执行器。Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
var result = asyncReadFile();
(2)更好的语义。async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。(3)更广的适用性。co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
async 函数的实现 async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args){
// …
}
// 等同于
function fn(args){
return spawn(function*() {
// …
});
}
所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。
async 函数的用法同 Generator 函数一样,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
async function getStockPriceByName(name) {
var symbol = await getStockSymbol(name);
var stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName(‘goog’).then(function (result){
console.log(result);
});
上面代码是一个获取股票报价的函数,函数前面的 async 关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个 Promise 对象。
指定多少毫秒后输出一个值:
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value)
}
asyncPrint(‘hello world’, 50);
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise().catch(function (err){
console.log(err);
});
}
await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。但是,如果将 forEach 方法的参数改成 async 函数,也有问题。
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 可能得到错误结果
docs.forEach(async function (doc) {
await db.post(doc);
});
}
// 上面代码可能不会正常工作,原因是这时三个 db.post 操作将是并发执行,
// 也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
如果确实希望多个请求并发执行,可以使用 Promise.all 方法。
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
// 或者使用下面的写法
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}
promise: https://segmentfault.com/n/13…
JS 的继承
面试题目:9.js 的继承
参考:http://www.ruanyifeng.com/blo…
构造函数的继承
例子:
function Animal(){
this.species = “ 动物 ”;
}
function Cat(name,color){
this.name = name;
this.color = color;
}
一、构造函数绑定第一种方法也是最简单的方法,使用 call 或 apply 方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat(“ 大毛 ”,” 黄色 ”);
alert(cat1.species); // 动物
二、prototype 模式如果 ” 猫 ” 的 prototype 对象,指向一个 Animal 的实例,那么所有 ” 猫 ” 的实例,就能继承 Animal 了
// 将 Cat 的 prototype 对象指向一个 Animal 的实例
// 它相当于完全删除了 prototype 对象原先的值,然后赋予一个新值。
Cat.prototype = new Animal();
// 任何一个 prototype 对象都有一个 constructor 属性,指向它的构造函数。
// 如果没有 ”Cat.prototype = new Animal();
//” 这一行,Cat.prototype.constructor 是指向 Cat 的;
// 加了这一行以后,Cat.prototype.constructor 指向 Animal。
Cat.prototype.constructor = Cat;
var cat1 = new Cat(“ 大毛 ”,” 黄色 ”);
alert(cat1.species); // 动物
alert(Cat.prototype.constructor == Animal); //true
// 每一个实例也有一个 constructor 属性,
// 默认调用 prototype 对象的 constructor 属性。
alert(cat1.constructor == Cat.prototype.constructor); // true
// 在运行 ”Cat.prototype = new Animal();” 这一行之后,
//cat1.constructor 也指向 Animal!
alert(cat1.constructor == Animal); // true
// 这显然会导致继承链的紊乱(cat1 明明是用构造函数 Cat 生成的),因此我们必须
// 手动纠正,将 Cat.prototype 对象的 constructor 值改为 Cat。
// 这就是第二行的意思。
这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了 prototype 对象,那么,下一步必然是为新的 prototype 对象加上 constructor 属性,并将这个属性指回原来的构造函数。
o.prototype = {};
o.prototype.constructor = o;
三、直接继承 prototype
由于 Animal 对象中,不变的属性都可以直接写入 Animal.prototype。所以,我们也可以让 Cat()跳过 Animal(),直接继承 Animal.prototype。
先将 Animal 对象改写:
function Animal(){}
Animal.prototype.species = “ 动物 ”;
然后,将 Cat 的 prototype 对象,然后指向 Animal 的 prototype 对象,这样就完成了继承。
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat(“ 大毛 ”,” 黄色 ”);
alert(cat1.species); // 动物
这样做的优点是效率比较高(不用执行和建立 Animal 的实例了),比较省内存。缺点是 Cat.prototype 和 Animal.prototype 现在指向了同一个对象,那么任何对 Cat.prototype 的修改,都会反映到 Animal.prototype。
Cat.prototype.constructor = Cat;
// 这一句实际上把 Animal.prototype 对象的 constructor 属性也改掉了!
alert(Animal.prototype.constructor); // Cat
四、利用空对象作为中介
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
F 是空对象,所以几乎不占内存。这时,修改 Cat 的 prototype 对象,就不会影响到 Animal 的 prototype 对象。
alert(Animal.prototype.constructor); // Animal
将上面的方法,封装成一个函数,便于使用。
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
// 意思是为子对象设一个 uber 属性,这个属性直接指向父对象的 prototype 属性。
//(uber 是一个德语词,意思是 ” 向上 ”、” 上一层 ”。)这等于在子对象上打开一条通道,
// 可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
使用的时候,方法如下
extend(Cat,Animal);
var cat1 = new Cat(“ 大毛 ”,” 黄色 ”);
alert(cat1.species); // 动物
五、拷贝继承
上面是采用 prototype 对象,实现继承。我们也可以换一种思路,纯粹采用 ” 拷贝 ” 方法实现继承。简单说,把父对象的所有属性和方法,拷贝进子对象
function Animal(){}
Animal.prototype.species = “ 动物 ”;
实现属性拷贝的目的:
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
// 这个函数的作用,就是将父对象的 prototype 对象中的属性,一一拷贝给 Child
// 对象的 prototype 对象。
}
使用的时候,这样写:
extend2(Cat, Animal);
var cat1 = new Cat(“ 大毛 ”,” 黄色 ”);
alert(cat1.species); // 动物
非构造函数的继承
例子:
var Chinese = {
nation:’ 中国 ’
};
var Doctor ={
career:’ 医生 ’
}
这两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现 ” 继承 ”。
object()方法
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
// 这个 object()函数,其实只做一件事,就是把子对象的 prototype 属性,
// 指向父对象,从而使得子对象与父对象连在一起。
使用的时候,第一步先在父对象的基础上,生成子对象:
var Doctor = object(Chinese);
然后,再加上子对象本身的属性:
Doctor.career = ‘ 医生 ’;
这时,子对象已经继承了父对象的属性了
alert(Doctor.nation); // 中国
浅拷贝
除了使用 ”prototype 链 ” 以外,还有另一种思路:把父对象的属性,全部拷贝给子对象,也能实现继承。
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
使用的时候,这样写:
var Doctor = extendCopy(Chinese);
Doctor.career = ‘ 医生 ’;
alert(Doctor.nation); // 中国
但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。
// 现在给 Chinese 添加一个 ” 出生地 ” 属性,它的值是一个数组。
Chinese.birthPlaces = [‘ 北京 ’,’ 上海 ’,’ 香港 ’];
// 然后,我们为 Doctor 的 ” 出生地 ” 添加一个城市:
Doctor.birthPlaces.push(‘ 厦门 ’);
//Chinese 的 ” 出生地 ” 也被改掉了
alert(Doctor.birthPlaces); // 北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); // 北京, 上海, 香港, 厦门
extendCopy()只是拷贝基本类型的数据,我们把这种拷贝叫做 ” 浅拷贝 ”。这是早期 jQuery 实现继承的方式。
深拷贝
所谓 ” 深拷贝 ”,就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用 ” 浅拷贝 ” 就行了。
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === ‘object’) {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
使用的时候这样写:
var Doctor = deepCopy(Chinese,Doctor);
现在,给父对象加一个属性,值为数组。然后,在子对象上修改这个属性
Chinese.birthPlaces = [‘ 北京 ’,’ 上海 ’,’ 香港 ’];
Doctor.birthPlaces.push(‘ 厦门 ’);
alert(Doctor.birthPlaces); // 北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); // 北京, 上海, 香港
call 和 apply 的区别
面试题:10.call 和 apply 的区别参考:http://www.ruanyifeng.com/blo…https://www.jianshu.com/p/bc5…
this 用法
this 是 JavaScript 语言的一个关键字。它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。
情况一:纯粹的函数调用
这是函数的最通常用法,属于全局性调用,因此 this 就代表全局对象。
var x = 1;
function test() {
console.log(this.x);
}
test(); // 1
情况二:作为对象方法的调用
函数还可以作为某个对象的方法调用,这时 this 就指这个上级对象。
function test() {
console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m(); // 1
情况三 作为构造函数调用
所谓构造函数,就是通过这个函数,可以生成一个新对象。这时,this 就指这个新对象。
function test() {
this.x = 1;
}
var obj = new test();
obj.x // 1
// 为了表明这时 this 不是全局对象,我们对代码做一些改变
var x = 2;
function test() {
this.x = 1;
}
var obj = new test();
x // 2
// 运行结果为 2,表明全局变量 x 的值根本没变。
情况四 apply 调用
apply()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时 this 指的就是这第一个参数。
var x = 0;
function test() {
console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply() // 0
// 如果把最后一行代码修改为
obj.m.apply(obj); //1
call
call 方法第一个参数是要绑定给 this 的值,后面传入的是一个参数列表。当第一个参数为 null、undefined 的时候,默认指向 window。
var arr = [1, 2, 3, 89, 46]
var max = Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4])//89
例子:
var obj = {
message: ‘My name is: ‘
}
function getName(firstName, lastName) {
console.log(this.message + firstName + ‘ ‘ + lastName)
}
getName.call(obj, ‘Dot’, ‘Dolby’)
apply
apply 接受两个参数,第一个参数是要绑定给 this 的值,第二个参数是一个参数数组。当第一个参数为 null、undefined 的时候,默认指向 window。
var arr = [1,2,3,89,46]
var max = Math.max.apply(null,arr)//89
当函数需要传递多个变量时, apply 可以接受一个数组作为参数输入, call 则是接受一系列的单独变量。
例子:
var obj = {
message: ‘My name is: ‘
}
function getName(firstName, lastName) {
console.log(this.message + firstName + ‘ ‘ + lastName)
}
getName.apply(obj, [‘Dot’, ‘Dolby’])// My name is: Dot Dolby
call 和 apply 可用来借用别的对象的方法,这里以 call()为例:
var Person1 = function () {
this.name = ‘Dot’;
}
var Person2 = function () {
this.getname = function () {
console.log(this.name);
}
Person1.call(this);
}
var person = new Person2();
person.getname(); // Dot
bind
和 call 很相似,第一个参数是 this 的指向,从第二个参数开始是接收的参数列表。区别在于 bind 方法返回值是函数以及 bind 接收的参数列表的使用。
var obj = {
name: ‘Dot’
}
function printName() {
console.log(this.name)
}
var dot = printName.bind(obj)
console.log(dot) // function () { …}
dot() // Dot
//bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。
// 而原函数 printName 中的 this 并没有被改变,依旧指向全局对象 window。
参数的使用
function fn(a, b, c) {
console.log(a, b, c);
}
var fn1 = fn.bind(null, ‘Dot’);
fn(‘A’, ‘B’, ‘C’); // A B C
fn1(‘A’, ‘B’, ‘C’); // Dot A B
fn1(‘B’, ‘C’); // Dot B C
fn.call(null, ‘Dot’); // Dot undefined undefined
//call 是把第二个及以后的参数作为 fn 方法的实参传进去,
// 而 fn1 方法的实参实则是在 bind 中参数的基础上再往后排。
应用场景
求数组中的最大和最小值
var arr = [1,2,3,89,46]
var max = Math.max.apply(null,arr)//89
var min = Math.min.apply(null,arr)//1
将类数组转化为数组
var trueArr = Array.prototype.slice.call(arrayLike)
数组追加
var arr1 = [1,2,3];
var arr2 = [4,5,6];
var total = [].push.apply(arr1, arr2);//6
// arr1 [1, 2, 3, 4, 5, 6]
// arr2 [4,5,6]
判断变量类型
function isArray(obj){
return Object.prototype.toString.call(obj) == ‘[object Array]’;
}
isArray([]) // true
isArray(‘dot’) // false
利用 call 和 apply 做继承
function Person(name,age){
// 这里的 this 都指向实例
this.name = name
this.age = age
this.sayAge = function(){
console.log(this.age)
}
}
function Female(){
Person.apply(this,arguments)// 将父元素所有方法在这里执行一遍就继承了
}
var dot = new Female(‘Dot’,2)
使用 log 代理 console.log
function log(){
console.log.apply(console, arguments);
}
// 当然也有更方便的 var log = console.log()
call、apply 和 bind 函数存在的区别
bind 返回对应函数, 便于稍后调用;apply, call 则是立即调用。
除此外, 在 ES6 的箭头函数下, call 和 apply 将失效, 对于箭头函数来说:
箭头函数体内的 this 对象, 就是定义时所在的对象, 而不是使用时所在的对象; 所以不需要类似于 var _this = this 这种丑陋的写法箭头函数不可以当作构造函数,也就是说不可以使用 new 命令, 否则会抛出一个错误箭头函数不可以使用 arguments 对象,,该对象在函数体内不存在. 如果要用, 可以用 Rest 参数代替不可以使用 yield 命令, 因此箭头函数不能用作 Generator 函数
ajax
面试题:11.ajax 是同步还是异步,怎么样实现同步;12.ajax 实现过程参考:https://blog.csdn.net/qq_2956…https://blog.csdn.net/xxf1597…
Ajax 全称 Asynchronous JavaScript and XML,也就是异步的 js 和 XML 技术。
Ajax 的使用四大步骤详解
第一步,创建 xmlhttprequest 对象
var xmlhttp =new XMLHttpRequest();
//XMLHttpRequest 对象用来和服务器交换数据。
var xhttp;
if(window.XMLHttpRequest) {
// 现代主流浏览器
xhttp= new XMLHttpRequest();
}else{
// 针对浏览器,比如 IE5 或 IE6
xhttp= new ActiveXObject(“Microsoft.XMLHTTP”);
}
第二步,使用 xmlhttprequest 对象的 open()和 send()方法发送资源请求给服务器。xmlhttp.open(method,url,async) method 包括 get 和 post,url 主要是文件或资源的路径,async 参数为 true(代表异步)或者 false(代表同步)
xhttp.send(); 使用 get 方法发送请求到服务器。
xhttp.send(string); 使用 post 方法发送请求到服务器。
post 发送请求什么时候能够使用呢?(1)更新一个文件或者数据库的时候。(2)发送大量数据到服务器,因为 post 请求没有字符限制。(3)发送用户输入的加密数据。
get 例子:
xhttp.open(“GET”,”ajax_info.txt”,true);
xhttp.open(“GET”,”index.html”,true);
xhttp.open(“GET”,”demo_get.asp?t=”+ Math.random(), true);xhttp.send();
post 例子
xhttp.open(“POST”, “demo_post.asp”, true);
xhttp.send();
post 表单例子 post 表单数据需要使用 xmlhttprequest 对象的 setRequestHeader 方法增加一个 HTTP 头。
xhttp.open(“POST”,”ajax_test.aspx”,true);
xhttp.setRequestHeader(“Content-type”,
“application/x-www-form-urlencoded”);
xhttp.send(“fname=Henry&lname=Ford”);
async=true 当服务器准备响应时将执行 onreadystatechange 函数。
xhttp.onreadystatechange= function(){
if(xhttp.readyState == 4 && xhttp.status == 200) {
document.getElementById(“demo”).innerHTML=xhttp.responseText;
}
};
xhttp.open(“GET”, “index.aspx”,true);
xhttp.send();
asyn=false 则将不需要写 onreadystatechange 函数,直接在 send 后面写上执行代码。
xhttp.open(“GET”, “index.aspx”, false);
xhttp.send();
document.getElementById(“demo”).innerHTML = xhttp.responseText;
第三步,使用 xmlhttprequest 对象的 responseText 或 responseXML 属性获得服务器的响应。使用 responseText 属性得到服务器响应的字符串数据,使用 responseXML 属性得到服务器响应的 XML 数据。
例子如下:
document.getElementById(“demo”).innerHTML = xhttp.responseText;
// 服务器响应的 XML 数据需要使用 XML 对象进行转换。
xmlDoc= xhttp.responseXML;
txt= “”;
x= xmlDoc.getElementsByTagName(“ARTIST”);
for(i = 0; i < x.length; i++) {
txt+= x[i].childNodes[0].nodeValue + “<br>”;
}
document.getElementById(“demo”).innerHTML= txt;
第四步,onreadystatechange 函数当发送请求到服务器,我们想要服务器响应执行一些功能就需要使用 onreadystatechange 函数,每次 xmlhttprequest 对象的 readyState 发生改变都会触发 onreadystatechange 函数。onreadystatechange 属性存储一个当 readyState 发生改变时自动被调用的函数。
readyState 属性,XMLHttpRequest 对象的状态,改变从 0 到 4,0 代表请求未被初始化,1 代表服务器连接成功,2 请求被服务器接收,3 处理请求,4 请求完成并且响应准备。status 属性,200 表示成功响应,404 表示页面不存在。
在 onreadystatechange 事件中,服务器响应准备的时候发生,当 readyState== 4 和 status==200 的时候服务器响应准备。
步骤总结
创建 XMLHttpRequest 对象, 也就是创建一个异步调用对象. 创建一个新的 HTTP 请求, 并指定该 HTTP 请求的方法、URL 及验证信息. 设置响应 HTTP 请求状态变化的函数. 发送 HTTP 请求. 获取异步调用返回的数据. 使用 JavaScript 和 DOM 实现局部刷新.
同步 & 异步
AJAX 中根据 async 的值不同分为同步(async = false)和异步(async = true)两种执行方式
$.ajax({
type: “post”,
url: “path”,
cache:false,
async:false,
dataType: ($.browser.msie) ? “text” : “xml”,
success: function(xmlobj){
function1(){};
}
});
function2(){};
一. 什么是同步请求:(false)同步请求即是当前发出请求后,浏览器什么都不能做,必须得等到请求完成返回数据之后,才会执行后续的代码,相当于是排队,前一个人办理完自己的事务,下一个人才能接着办。也就是说,当 JS 代码加载到当前 AJAX 的时候会把页面里所有的代码停止加载,页面处于一个假死状态,当这个 AJAX 执行完毕后才会继续运行其他代码页面解除假死状态(即当 ajax 返回数据后,才执行后面的 function2)。二. 什么是异步请求:(true) 异步请求就当发出请求的同时,浏览器可以继续做任何事,Ajax 发送请求并不会影响页面的加载与用户的操作,相当于是在两条线上,各走各的,互不影响。一般默认值为 true,异步。异步请求可以完全不影响用户的体验效果,无论请求的时间长或者短,用户都在专心的操作页面的其他内容,并不会有等待的感觉。
同步适用于一些什么情况呢?我们可以想一下,同步是一步一步来操作,等待请求返回的数据,再执行下一步,那么一定会有一些情况,只有这一步执行完,拿到数据,通过获取到这一步的数据来执行下一步的操作。这是异步没有办法实现的,因此同步的存在一定有他存在的道理。我们在发送 AJAX 请求后,还需要继续处理服务器的响应结果,如果这时我们使用异步请求模式同时未将结果的处理交由另一个 JS 函数进行处理。这时就有可能发生这种情况:异步请求的响应还没有到达,函数已经执行完了 return 语句了,这时将导致 return 的结果为空字符串。
闭包
面试题:13. 闭包的作用理解,以及那些地方用过闭包,以及闭包的缺点,如何实现闭包
标题文字