资讯专栏INFORMATION COLUMN

大厂的532道面试题知识点笔记

Tony / 608人阅读

摘要:在运行这一行之后,也指向这显然会导致继承链的紊乱明明是用构造函数生成的,因此我们必须手动纠正,将对象的值改为。下文都遵循这一点,即如果替换了对象,那么,下一步必然是为新的对象加上属性,并将这个属性指回原来的构造函数。

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("

some html

"); //设定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("

headers ...

"); }); 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 + "
"; } 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.闭包的作用理解,以及那些地方用过闭包,以及闭包的缺点,如何实现闭包
参考:https://segmentfault.com/a/11...

闭包的作用理解

变量的作用域

变量的作用域无非就是两种:全局变量和局部变量。Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

 var n=999;
  function f1(){
    alert(n);
  }
  f1(); // 999

//另一方面,在函数外部自然无法读取函数内的局部变量。
  function f1(){
    var n=999;
  }

  alert(n); // error
//这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

从外部读取局部变量

 function f1(){

    var n=999;

    function f2(){
      alert(n); // 999
    }

  }
/*在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,
对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。*/
/*这就是Javascript语言特有的"链式作用域"结构(chain scope),
子对象会一级一级地向上寻找所有父对象的变量。
所以,父对象的所有变量,对子对象都是可见的,反之则不成立。*/

闭包的概念

闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。


闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。


使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包的使用场景

应用场景一:setTimeout
原生的setTimeout有一个缺陷,你传递的第一个函数不能带参数。即

setTimeout(func(parma),1000);

我们就可以用闭包来实现这个效果了

function func(param) {
    return function() {
        alert(param);
    }
}
var f = func(1)
setTimeout(f, 1000);

应用场景二:用闭包模拟私有方法

// 可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行毕,就可以立即销毁其作用域链了
(function(){
    function createFunc() {
        var name = "wheeler";
        return function () {
            return name;
        }
    }

    var nameFunc = createFunc();

    var name = nameFunc();

    console.log(name);
})();
闭包的应用场景
用闭包模拟私有方法
var returnNum = (function () {
    var num = 0;

    function changeNum(value) {
        num = value;
    }

    return {
        add: function () {
            changeNum(10);
        },
        delete: function () {
            changeNum(-10);
        },
        getNum: function () {
            return num;
        }
    }
})();

// 闭包
console.log(returnNum.getNum());
returnNum.add();
console.log(returnNum.getNum());
returnNum.delete();
console.log(returnNum.getNum());

应用场景三:缓存

var CacheCount = (function () {
    var cache = {};
    return {
        getCache: function (key) {
            if (key in cache) {// 如果结果在缓存中
                return cache[key];// 直接返回缓存中的对象
            }
            var newValue = getNewValue(key); // 外部方法,获取缓存
            cache[key] = newValue;// 更新缓存
            return newValue;
        }
    };
})();

console.log(CacheCount.getCache("key1"));

应用场景四:封装

var person = function(){
    var name = "default";//变量作用域为函数内部,外部无法访问
    return {
        getName : function(){
            return name;
        },
        setName : function(newName){
            name = newName;
        }
    }
}();

console.log(person.name);// undefined
console.log(person.getName());
person.setName("wheeler");
console.log(person.getName());
跨域

面试题:14.跨域方法以及怎么样实现的与原理
参考:http://www.ruanyifeng.com/blo...
https://blog.csdn.net/qq_3409...
https://blog.csdn.net/qq_2860...
http://www.ruanyifeng.com/blo...

同源政策

所谓"同源"指的是"三个相同"。

协议相同

域名相同

端口相同

举例

http://www.example.com/dir/page.html

这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。它的同源情况如下:

http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)

限制范围
目前,如果非同源,共有三种行为受到限制:

Cookie、LocalStorage 和 IndexDB 无法读取。

DOM 无法获得。

AJAX 请求不能发送。

Cookie
Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。

但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。

举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie。

document.domain = "example.com";
//现在,A网页通过脚本设置一个 Cookie。
document.cookie = "test1=hello";

//B网页就可以读到这个 Cookie。
var allCookie = document.cookie;
注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法
另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。
Set-Cookie: key=value; domain=.example.com; path=/

iframe
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。

//比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame

//子窗口获取主窗口的DOM也会报错。
window.parent.document.body
// 报错

如果两个窗口一级域名相同,只是二级域名不同,那么设置上一节介绍的document.domain属性,就可以规避同源政策,拿到DOM。

例子:

/* 1. 在页面 http://a.example.com/a.html 设置document.domain */




/* 2. 在页面http:// b.example.com/b.html 中设置document.domain */

对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题:

片段识别符(fragment identifier)

window.name

跨文档通信API(Cross-document messaging)

片段识别符

片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#fra...。如果只是改变片段标识符,页面不会重新刷新。
父窗口可以把信息,写入子窗口的片段标识符。

var src = originURL + "#" + data;
document.getElementById("myIFrame").src = src;

//子窗口通过监听hashchange事件得到通知。
window.onhashchange = checkMessage;

function checkMessage() {
  var message = window.location.hash;
  // ...
}
//同样的,子窗口也可以改变父窗口的片段标识符。
parent.location.href= target + "#" + hash;

window.name

浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。

//父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。
window.name = data;

//接着,子窗口跳回一个与主窗口同域的网址。
location = "http://parent.url.com/xxx.html";

//主窗口就可以读取子窗口的window.name了。
var data = document.getElementById("myFrame").contentWindow.name;
这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。

例子:
www.test.com下a.html页:



    
        
        
    
    

        

        
    

www.domain.com下b.html页:



    
        
        
    
    
        2
        
        
    

window.postMessage

这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。

var popup = window.open("http://bbb.com", "title");
popup.postMessage("Hello World!", "http://bbb.com");
postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。

子窗口向父窗口发送消息的写法类似。

window.opener.postMessage("Nice to see you", "http://aaa.com");

//父窗口和子窗口都可以通过message事件,监听对方的消息。
window.addEventListener("message", function(e) {
  console.log(e.data);
},false);

message事件的事件对象event,提供以下三个属性:

event.source:发送消息的窗口

event.origin: 消息发向的网址

event.data: 消息内容

子窗口通过event.source属性引用父窗口,然后发送消息:

window.addEventListener("message", receiveMessage);
function receiveMessage(event) {
  event.source.postMessage("Nice to see you!", "*");
}

event.origin属性可以过滤不是发给本窗口的消息。

window.addEventListener("message", receiveMessage);
function receiveMessage(event) {
  if (event.origin !== "http://aaa.com") return;
  if (event.data === "Hello World") {
      event.source.postMessage("Hello", event.origin);
  } else {
    console.log(event.data);
  }
}

postMessage的使用方法: otherWindow.postMessage(message, targetOrigin);

otherWindow: 指目标窗口,也就是给哪个window发消息,是
window.frames 属性的成员或者由 window.open 方法创建的窗口
message: 是要发送的消息,类型为 String、Object (IE8、9 不支持)
targetOrigin: 是限定消息接收范围,不限制请使用 ‘*’


例子:

//本地代码index.html

  
      
      
//server.com上remote.html,监听message事件,并检查来源是否是要通信的域。


    
    

LocalStorage

主窗口写入iframe子窗口的localStorage

//子窗口将父窗口发来的消息,写入自己的LocalStorage。
window.onmessage = function(e) {
  if (e.origin !== "http://bbb.com") {
    return;
  }
  var payload = JSON.parse(e.data);
  localStorage.setItem(payload.key, JSON.stringify(payload.data));
};

//父窗口发送消息
var win = document.getElementsByTagName("iframe")[0].contentWindow;
var obj = { name: "Jack" };
win.postMessage(JSON.stringify({key: "storage", data: obj}), "http://bbb.com");

加强版的子窗口接收消息:

window.onmessage = function(e) {
  if (e.origin !== "http://bbb.com") return;
  var payload = JSON.parse(e.data);
  switch (payload.method) {
    case "set":
      localStorage.setItem(payload.key, JSON.stringify(payload.data));
      break;
    case "get":
      var parent = window.parent;
      var data = localStorage.getItem(payload.key);
      parent.postMessage(data, "http://aaa.com");
      break;
    case "remove":
      localStorage.removeItem(payload.key);
      break;
  }
};

加强版的父窗口发送消息代码:

var win = document.getElementsByTagName("iframe")[0].contentWindow;
var obj = { name: "Jack" };
// 存入对象
win.postMessage(JSON.stringify({key: "storage", method: "set", data: obj}), "http://bbb.com");
// 读取对象
win.postMessage(JSON.stringify({key: "storage", method: "get"}), "*");
window.onmessage = function(e) {
  if (e.origin != "http://aaa.com") return;
  // "Jack"
  console.log(JSON.parse(e.data).name);
};

location.hash

这个办法比较绕,但是可以解决完全跨域情况下的脚步置换问题。原理是利用location.hash来进行传值。www.a.com下的a.html想和www.b.com下的b.html通信(在a.html中动态创建一个b.html的iframe来发送请求)
但是由于“同源策略”的限制他们无法进行交流(b.html无法返回数据),于是就找个中间人:www.a.com下的c.html(注意是www.a.com下的)。
b.html将数据传给c.html(b.html中创建c.html的iframe),由于c.html和a.html同源,于是可通过c.html将返回的数据传回给a.html,从而达到跨域的效果。

//a.html

//b.html

//由于两个页面不在同一个域下,IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe,这里有一个a.com下的代理文件c.html。Firefox可以修改。
//c.html

直接访问a.html,a.html向b.html发送的消息为”sayHi”;b.html通过消息判断返回了”HiWorld”,并通过c.html改变了location.hash的值

AJAX
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。

除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。

JSONP

WebSocket

CORS

JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。

它的基本思想是,网页通过添加一个

阅读需要支付1元查看
<