成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

大廠的532道面試題知識(shí)點(diǎn)筆記

cgspine / 3287人閱讀

摘要:在運(yùn)行這一行之后,也指向這顯然會(huì)導(dǎo)致繼承鏈的紊亂明明是用構(gòu)造函數(shù)生成的,因此我們必須手動(dòng)糾正,將對象的值改為。下文都遵循這一點(diǎn),即如果替換了對象,那么,下一步必然是為新的對象加上屬性,并將這個(gè)屬性指回原來的構(gòu)造函數(shù)。

express&koa

面試題目:1.express和koa的對比,兩者中間件的原理,koa捕獲異常多種情況說一下

參考:https://blog.csdn.net/shmnh/a...
https://blog.csdn.net/K616358...
https://blog.csdn.net/wang839...
async 函數(shù):http://www.ruanyifeng.com/blo...

初識(shí)兩者

express:

var express = require("express")
var app = express()  //創(chuàng)建一個(gè)APP實(shí)例
 
//建一個(gè)項(xiàng)目根目錄的get請求路由,回調(diào)方法中直接輸出字符串Hello World!
app.get("/", function (req, res) {
    res.send("Hello World!")
});
 
//監(jiān)聽端口,啟動(dòng)服務(wù)
app.listen(3000);

koa:

var koa = require("koa");
var route = require("koa-route");  //koa默認(rèn)沒有集成route功能,引入中間件
 
var app = koa();  //創(chuàng)建一個(gè)APP實(shí)例
 
//建一個(gè)項(xiàng)目根目錄的get請求路由,回調(diào)方法中直接輸出字符串Hello World!,就是掛載一個(gè)中間件
app.use(route.get("/", function *(){
    this.body = "Hello World";
}));
 
//監(jiān)聽端口,啟動(dòng)服務(wù)

app.listen(3000);
啟動(dòng)方式

koa采用了new Koa()的方式,而express采用傳統(tǒng)的函數(shù)形式,對比源碼如下:

//koa
const Emitter = require("events");
module.exports = class Application extends Emitter {
...
}
//express
exports = module.exports = createApplication;
function createApplication() {
...
}
應(yīng)用生命周期和上下文

在項(xiàng)目過程中,經(jīng)常需要用到在整個(gè)應(yīng)用生命周期中共享的配置和數(shù)據(jù)對象,比如服務(wù)URL、是否啟用某個(gè)功能特性、接口配置、當(dāng)前登錄用戶數(shù)據(jù)等等。

express:

//共享配置,express提供了很多便利的方法
app.set("enableCache", true)
app.get("enableCache")//true
 
app.disable("cache")
app.disabled("cache")//true
 
app.enable("cache")
app.enabled("cache")//true
 
//應(yīng)用共享數(shù)據(jù):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;
});
 
//應(yīng)用共享數(shù)據(jù):ctx.state
this.state.user = {name:"Samoay", id:1234}; 
請求HTTP Request

服務(wù)器端需要進(jìn)行什么處理,怎么處理以及處理的參數(shù)都依賴客戶端發(fā)送的請求,兩個(gè)框架都封裝了HTTP Request對象,便于對這一部分進(jìn)行處理。以下主要舉例說明下對請求參數(shù)的處理。GET參數(shù)都可以直接通過Request對象獲取,POST參數(shù)都需要引入中間件先parse,再取值。

express:

// 獲取QueryString參數(shù)
// GET /shoes?order=desc&shoe[color]=blue
req.query.order
// => "desc"
 
req.query.shoe.color
// => "blue"
 
// 通過路由獲取Restful風(fēng)格的URL參數(shù)
app.get("/user/:id?", function userIdHandler(req, res) {
    console.log(req.params.id);
    res.send("GET");
})
 
//獲取POST數(shù)據(jù):需要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參數(shù)
// GET /?action=delete&id=1234
this.request.query
// => { action: "delete", id: "1234" }
 
// 通過路由獲取Restful風(fēng)格的URL參數(shù)
var route = require("koa-route");
app.use(route.get("/post/:id", function *(id){
    console.log(id);
    // => 1234
}));
 
// 獲取POST數(shù)據(jù):需要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

收到客戶端的請求,服務(wù)需要通過識(shí)別請求的方法(HTTP Method: GET, POST, PUT...)和請求的具體路徑(path)來進(jìn)行不同的處理。這部分功能就是路由(Route)需要做的事情,說白了就是請求的分發(fā),分發(fā)到不同的回調(diào)方法去處理。

express

// app.all表示對所有的路徑和請求方式都要經(jīng)過這些回調(diào)方法的處理,可以逗號(hào)方式傳入多個(gè)
app.all("*", authentication, loadUser);
// 也可以多次調(diào)用
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個(gè)問題,首先是app.get,在應(yīng)用生命周期中也有一個(gè)app.get方法,用于獲取項(xiàng)目配置。Express內(nèi)部就是公用的一個(gè)方法,如果傳入的只有1個(gè)參數(shù)就獲取配置,2個(gè)參數(shù)就作為路由處理。其次是app.use("", cb)?與app.all("", cb)?的區(qū)別,前者是中間件方式,調(diào)用是有順序的,不一定會(huì)執(zhí)行到;后者是路由方式,肯定會(huì)執(zhí)行到。

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));
 
//鏈?zhǔn)綄懛?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模板引擎,并提供了視圖設(shè)置的便利方法。Koa需要引入co-views中間件,co-views也是基于consolidate.js,支持能力一樣強(qiáng)大。

express

// Express
// 這只模板路徑和默認(rèn)的模板后綴
app.set("views", __dirname + "/tpls");
app.set("view engine", "html");
 
//默認(rèn),express根據(jù)template的后綴自動(dòng)選擇模板
//引擎渲染,支持jade和ejs。如果不使用默認(rèn)擴(kuò)展名
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"http://render不提供后綴名時(shí)
});
 
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

獲取完請求參數(shù)、處理好了具體的請求、視圖也準(zhǔn)備就緒,下面就該返回給客戶端了,那就是HTTP Response對象了。這部分也屬于框架的基礎(chǔ)部分,各種都做了封裝實(shí)現(xiàn),顯著的區(qū)別是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

"); //設(shè)定HTTP Status狀態(tài)碼 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"http://render不提供后綴名時(shí)
});
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

對比了主要的幾個(gè)框架功能方面的使用,其實(shí)區(qū)別最大,使用方式最不同的地方是在中間件的處理上。Express由于是在ES6特性之前的,中間件的基礎(chǔ)原理還是callback方式的;而koa得益于generator特性和co框架(co會(huì)把所有g(shù)enerator的返回封裝成為Promise對象),使得中間件的編寫更加優(yōu)雅。

express

// req 用于獲取請求信息, ServerRequest 的實(shí)例
// res 用于響應(yīng)處理結(jié)果, ServerResponse 的實(shí)例
// next() 函數(shù)用于將當(dāng)前控制權(quán)轉(zhuǎn)交給下一步處理,
//        如果給 next() 傳遞一個(gè)參數(shù)時(shí),表示出錯(cuò)信息
var x = function (req, res, next) {
 
    // 對req和res進(jìn)行必要的處理
 
    // 進(jìn)入下一個(gè)中間件
    return next();
 
    // 傳遞錯(cuò)誤信息到下一個(gè)中間件
    return next(err);
 
    // 直接輸出,不再進(jìn)入后面的中間件
    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
 
    //不再進(jìn)入后面的中間件, 回溯upstream
    return;
});

express處理多個(gè)中間件:

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處理多個(gè)中間件:

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中間件的入?yún)⒁灿袃蓚€(gè),
后一個(gè)就是next。next的功能與express一樣*/

/*上面介紹了koa的next()的功能,這里的next()需要同步調(diào)用,千萬不要采用異步調(diào)用
*/
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)=>{//捕獲異常記錄錯(cuò)誤日志
   console.log(new Date(),":",err);
});
http.createServer(app.callback()).listen(3000);

上面的代碼運(yùn)行后在瀏覽器訪問返回的結(jié)果是“Internal Server error”;我們發(fā)現(xiàn)當(dāng)錯(cuò)誤發(fā)生的時(shí)候后端程序并沒有死掉,只是拋出了異常,前端也同時(shí)接收到了錯(cuò)誤反饋,對于KOA來說,異常發(fā)生在中間件的執(zhí)行過程中,所以只要我們在中間件執(zhí)行過程中將異常捕獲并處理就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可以是三種類型的函數(shù),普通函數(shù),generator函數(shù),
還有async函數(shù)。最后generator會(huì)被轉(zhuǎn)成async函數(shù),。
所以最終中間件數(shù)組只會(huì)有普通函數(shù)和async函數(shù)。*/

異常處理

當(dāng)異常捕獲是有兩種處理方式,一種就是響應(yīng)錯(cuò)誤請求,而就是觸發(fā)注冊注冊全局錯(cuò)誤事件,比如記錄錯(cuò)誤日志

async 函數(shù)

一句話,async 函數(shù)就是 Generator 函數(shù)的語法糖。

前文有一個(gè) Generator 函數(shù),依次讀取兩個(gè)文件:

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 函數(shù),就是下面這樣:

var asyncReadFile = async function (){
  var f1 = await readFile("/etc/fstab");
  var f2 = await readFile("/etc/shells");
  console.log(f1.toString());
  console.log(f2.toString());
};

一比較就會(huì)發(fā)現(xiàn),async 函數(shù)就是將 Generator 函數(shù)的星號(hào)(*)替換成 async,將 yield 替換成 await,僅此而已。

async 函數(shù)的優(yōu)點(diǎn)
(1)內(nèi)置執(zhí)行器。 Generator 函數(shù)的執(zhí)行必須靠執(zhí)行器,所以才有了 co 函數(shù)庫,而 async 函數(shù)自帶執(zhí)行器。也就是說,async 函數(shù)的執(zhí)行,與普通函數(shù)一模一樣,只要一行。

var result = asyncReadFile();

(2)更好的語義。 async 和 await,比起星號(hào)和 yield,語義更清楚了。async 表示函數(shù)里有異步操作,await 表示緊跟在后面的表達(dá)式需要等待結(jié)果。
(3)更廣的適用性。 co 函數(shù)庫約定,yield 命令后面只能是 Thunk 函數(shù)或 Promise 對象,而 async 函數(shù)的 await 命令后面,可以跟 Promise 對象和原始類型的值(數(shù)值、字符串和布爾值,但這時(shí)等同于同步操作)。

async 函數(shù)的實(shí)現(xiàn)
async 函數(shù)的實(shí)現(xiàn),就是將 Generator 函數(shù)和自動(dòng)執(zhí)行器,包裝在一個(gè)函數(shù)里。

async function fn(args){
  // ...
}

// 等同于

function fn(args){ 
  return spawn(function*() {
    // ...
  }); 
}

所有的 async 函數(shù)都可以寫成上面的第二種形式,其中的 spawn 函數(shù)就是自動(dòng)執(zhí)行器。

async 函數(shù)的用法
同 Generator 函數(shù)一樣,async 函數(shù)返回一個(gè) Promise 對象,可以使用 then 方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到 await 就會(huì)先返回,等到觸發(fā)的異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語句。

async function getStockPriceByName(name) {
  var symbol = await getStockSymbol(name);
  var stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName("goog").then(function (result){
  console.log(result);
});

上面代碼是一個(gè)獲取股票報(bào)價(jià)的函數(shù),函數(shù)前面的async關(guān)鍵字,表明該函數(shù)內(nèi)部有異步操作。調(diào)用該函數(shù)時(shí),會(huì)立即返回一個(gè)Promise對象。

指定多少毫秒后輸出一個(gè)值:

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 對象,運(yùn)行結(jié)果可能是 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 函數(shù)之中,如果用在普通函數(shù),就會(huì)報(bào)錯(cuò)。但是,如果將 forEach 方法的參數(shù)改成 async 函數(shù),也有問題。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 可能得到錯(cuò)誤結(jié)果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}
//上面代碼可能不會(huì)正常工作,原因是這時(shí)三個(gè) db.post 操作將是并發(fā)執(zhí)行,
//也就是同時(shí)執(zhí)行,而不是繼發(fā)執(zhí)行。正確的寫法是采用 for 循環(huán)。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}

如果確實(shí)希望多個(gè)請求并發(fā)執(zhí)行,可以使用 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...

構(gòu)造函數(shù)的繼承

例子:

function Animal(){

    this.species = "動(dòng)物";

  }

function Cat(name,color){

    this.name = name;

    this.color = color;

  }

一、 構(gòu)造函數(shù)綁定
第一種方法也是最簡單的方法,使用call或apply方法,將父對象的構(gòu)造函數(shù)綁定在子對象上,即在子對象構(gòu)造函數(shù)中加一行:

function Cat(name,color){

    Animal.apply(this, arguments);

    this.name = name;

    this.color = color;

  }

  var cat1 = new Cat("大毛","黃色");

  alert(cat1.species); // 動(dòng)物

二、 prototype模式
如果"貓"的prototype對象,指向一個(gè)Animal的實(shí)例,那么所有"貓"的實(shí)例,就能繼承Animal了

//將Cat的prototype對象指向一個(gè)Animal的實(shí)例
//它相當(dāng)于完全刪除了prototype 對象原先的值,然后賦予一個(gè)新值。
    Cat.prototype = new Animal();
    
//任何一個(gè)prototype對象都有一個(gè)constructor屬性,指向它的構(gòu)造函數(shù)。
//如果沒有"Cat.prototype = new Animal();
//"這一行,Cat.prototype.constructor是指向Cat的;
//加了這一行以后,Cat.prototype.constructor指向Animal。
  Cat.prototype.constructor = Cat;

  var cat1 = new Cat("大毛","黃色");

  alert(cat1.species); // 動(dòng)物

    alert(Cat.prototype.constructor == Animal); //true
    
    //每一個(gè)實(shí)例也有一個(gè)constructor屬性,
    //默認(rèn)調(diào)用prototype對象的constructor屬性。
     alert(cat1.constructor == Cat.prototype.constructor); // true
     
     //在運(yùn)行"Cat.prototype = new Animal();"這一行之后, 
      //cat1.constructor也指向Animal!
      alert(cat1.constructor == Animal); // true
      
      //這顯然會(huì)導(dǎo)致繼承鏈的紊亂(cat1明明是用構(gòu)造函數(shù)Cat生成的),因此我們必須    
     //手動(dòng)糾正,將Cat.prototype對象的constructor值改為Cat。
     //這就是第二行的意思。

這是很重要的一點(diǎn),編程時(shí)務(wù)必要遵守。下文都遵循這一點(diǎn),即如果替換了prototype對象,那么,下一步必然是為新的prototype對象加上constructor屬性,并將這個(gè)屬性指回原來的構(gòu)造函數(shù)。

o.prototype = {};
o.prototype.constructor = o;

三、 直接繼承prototype

由于Animal對象中,不變的屬性都可以直接寫入Animal.prototype。所以,我們也可以讓Cat()跳過 Animal(),直接繼承Animal.prototype。

先將Animal對象改寫:

function Animal(){ }

Animal.prototype.species = "動(dòng)物";

然后,將Cat的prototype對象,然后指向Animal的prototype對象,這樣就完成了繼承。

Cat.prototype = Animal.prototype;

  Cat.prototype.constructor = Cat;

  var cat1 = new Cat("大毛","黃色");

  alert(cat1.species); // 動(dòng)物

這樣做的優(yōu)點(diǎn)是效率比較高(不用執(zhí)行和建立Animal的實(shí)例了),比較省內(nèi)存。缺點(diǎn)是 Cat.prototype和Animal.prototype現(xiàn)在指向了同一個(gè)對象,那么任何對Cat.prototype的修改,都會(huì)反映到Animal.prototype。

Cat.prototype.constructor = Cat;

// 這一句實(shí)際上把Animal.prototype對象的constructor屬性也改掉了!

alert(Animal.prototype.constructor); // Cat

四、 利用空對象作為中介

var F = function(){};

  F.prototype = Animal.prototype;

  Cat.prototype = new F();

  Cat.prototype.constructor = Cat;

F是空對象,所以幾乎不占內(nèi)存。這時(shí),修改Cat的prototype對象,就不會(huì)影響到Animal的prototype對象。

alert(Animal.prototype.constructor); // Animal

將上面的方法,封裝成一個(gè)函數(shù),便于使用。

function extend(Child, Parent) {

    var F = function(){};

    F.prototype = Parent.prototype;

    Child.prototype = new F();

    Child.prototype.constructor = Child;

    Child.uber = Parent.prototype;

  }

//意思是為子對象設(shè)一個(gè)uber屬性,這個(gè)屬性直接指向父對象的prototype屬性。
//(uber是一個(gè)德語詞,意思是"向上"、"上一層"。)這等于在子對象上打開一條通道,
//可以直接調(diào)用父對象的方法。這一行放在這里,只是為了實(shí)現(xiàn)繼承的完備性,純屬備用性質(zhì)。

使用的時(shí)候,方法如下

extend(Cat,Animal);

var cat1 = new Cat("大毛","黃色");

alert(cat1.species); // 動(dòng)物

五、 拷貝繼承

上面是采用prototype對象,實(shí)現(xiàn)繼承。我們也可以換一種思路,純粹采用"拷貝"方法實(shí)現(xiàn)繼承。簡單說,把父對象的所有屬性和方法,拷貝進(jìn)子對象

  function Animal(){}

  Animal.prototype.species = "動(dòng)物";

實(shí)現(xiàn)屬性拷貝的目的:

function extend2(Child, Parent) {

    var p = Parent.prototype;

    var c = Child.prototype;

    for (var i in p) {

      c[i] = p[i];

      }

    c.uber = p;
    //這個(gè)函數(shù)的作用,就是將父對象的prototype對象中的屬性,一一拷貝給Child    
      //對象的prototype對象。

  }

使用的時(shí)候,這樣寫:

extend2(Cat, Animal);

  var cat1 = new Cat("大毛","黃色");

  alert(cat1.species); // 動(dòng)物
非構(gòu)造函數(shù)的繼承

例子:

var Chinese = {
    nation:"中國"
  };
  var Doctor ={
    career:"醫(yī)生"
  }

這兩個(gè)對象都是普通對象,不是構(gòu)造函數(shù),無法使用構(gòu)造函數(shù)方法實(shí)現(xiàn)"繼承"。

object()方法

 function object(o) {

    function F() {}

    F.prototype = o;

    return new F();

  }
//這個(gè)object()函數(shù),其實(shí)只做一件事,就是把子對象的prototype屬性,
//指向父對象,從而使得子對象與父對象連在一起。

使用的時(shí)候,第一步先在父對象的基礎(chǔ)上,生成子對象:

var Doctor = object(Chinese);

然后,再加上子對象本身的屬性:

Doctor.career = "醫(yī)生";

這時(shí),子對象已經(jīng)繼承了父對象的屬性了

 alert(Doctor.nation); //中國

淺拷貝

除了使用"prototype鏈"以外,還有另一種思路:把父對象的屬性,全部拷貝給子對象,也能實(shí)現(xiàn)繼承。

function extendCopy(p) {

    var c = {};

    for (var i in p) { 
      c[i] = p[i];
    }

    c.uber = p;

    return c;
  }

使用的時(shí)候,這樣寫:

var Doctor = extendCopy(Chinese);

Doctor.career = "醫(yī)生";

alert(Doctor.nation); // 中國

但是,這樣的拷貝有一個(gè)問題。那就是,如果父對象的屬性等于數(shù)組或另一個(gè)對象,那么實(shí)際上,子對象獲得的只是一個(gè)內(nèi)存地址,而不是真正拷貝,因此存在父對象被篡改的可能。

//現(xiàn)在給Chinese添加一個(gè)"出生地"屬性,它的值是一個(gè)數(shù)組。
 Chinese.birthPlaces = ["北京","上海","香港"];

//然后,我們?yōu)镈octor的"出生地"添加一個(gè)城市:
 Doctor.birthPlaces.push("廈門");

//Chinese的"出生地"也被改掉了
alert(Doctor.birthPlaces); //北京, 上海, 香港, 廈門
alert(Chinese.birthPlaces); //北京, 上海, 香港, 廈門

extendCopy()只是拷貝基本類型的數(shù)據(jù),我們把這種拷貝叫做"淺拷貝"。這是早期jQuery實(shí)現(xiàn)繼承的方式。

深拷貝

所謂"深拷貝",就是能夠?qū)崿F(xiàn)真正意義上的數(shù)組和對象的拷貝。它的實(shí)現(xiàn)并不難,只要遞歸調(diào)用"淺拷貝"就行了。

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;
  }

使用的時(shí)候這樣寫:

var Doctor = deepCopy(Chinese,Doctor);

現(xiàn)在,給父對象加一個(gè)屬性,值為數(shù)組。然后,在子對象上修改這個(gè)屬性

  Chinese.birthPlaces = ["北京","上海","香港"];

  Doctor.birthPlaces.push("廈門");

   alert(Doctor.birthPlaces); //北京, 上海, 香港, 廈門

  alert(Chinese.birthPlaces); //北京, 上海, 香港
call和apply的區(qū)別

面試題:10.call和apply的區(qū)別
參考: http://www.ruanyifeng.com/blo...
https://www.jianshu.com/p/bc5...

this 用法

this是 JavaScript 語言的一個(gè)關(guān)鍵字。
它是函數(shù)運(yùn)行時(shí),在函數(shù)體內(nèi)部自動(dòng)生成的一個(gè)對象,只能在函數(shù)體內(nèi)部使用。

情況一:純粹的函數(shù)調(diào)用

這是函數(shù)的最通常用法,屬于全局性調(diào)用,因此this就代表全局對象。

var x = 1;
function test() {
   console.log(this.x);
}
test();  // 1

情況二:作為對象方法的調(diào)用

函數(shù)還可以作為某個(gè)對象的方法調(diào)用,這時(shí)this就指這個(gè)上級對象。

function test() {
  console.log(this.x);
}

var obj = {};
obj.x = 1;
obj.m = test;

obj.m(); // 1

情況三 作為構(gòu)造函數(shù)調(diào)用

所謂構(gòu)造函數(shù),就是通過這個(gè)函數(shù),可以生成一個(gè)新對象。這時(shí),this就指這個(gè)新對象。

function test() {
 this.x = 1;
}

var obj = new test();
obj.x // 1

//為了表明這時(shí)this不是全局對象,我們對代碼做一些改變
var x = 2;
function test() {
  this.x = 1;
}

var obj = new test();
x  // 2
//運(yùn)行結(jié)果為2,表明全局變量x的值根本沒變。

情況四 apply 調(diào)用

apply()是函數(shù)的一個(gè)方法,作用是改變函數(shù)的調(diào)用對象。它的第一個(gè)參數(shù)就表示改變后的調(diào)用這個(gè)函數(shù)的對象。因此,這時(shí)this指的就是這第一個(gè)參數(shù)。

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 方法第一個(gè)參數(shù)是要綁定給this的值,后面?zhèn)魅氲氖且粋€(gè)參數(shù)列表。當(dāng)?shù)谝粋€(gè)參數(shù)為null、undefined的時(shí)候,默認(rèn)指向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接受兩個(gè)參數(shù),第一個(gè)參數(shù)是要綁定給this的值,第二個(gè)參數(shù)是一個(gè)參數(shù)數(shù)組。當(dāng)?shù)谝粋€(gè)參數(shù)為null、undefined的時(shí)候,默認(rèn)指向window。

var arr = [1,2,3,89,46]
var max = Math.max.apply(null,arr)//89

當(dāng)函數(shù)需要傳遞多個(gè)變量時(shí), apply 可以接受一個(gè)數(shù)組作為參數(shù)輸入, 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很相似,第一個(gè)參數(shù)是this的指向,從第二個(gè)參數(shù)開始是接收的參數(shù)列表。區(qū)別在于bind方法返回值是函數(shù)以及bind接收的參數(shù)列表的使用。

var obj = {
    name: "Dot"
}

function printName() {
    console.log(this.name)
}

var dot = printName.bind(obj)
console.log(dot) // function () { … }
dot()  // Dot

//bind 方法不會(huì)立即執(zhí)行,而是返回一個(gè)改變了上下文 this 后的函數(shù)。
 //而原函數(shù)printName 中的 this 并沒有被改變,依舊指向全局對象 window。

參數(shù)的使用

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 是把第二個(gè)及以后的參數(shù)作為 fn 方法的實(shí)參傳進(jìn)去,
//而 fn1 方法的實(shí)參實(shí)則是在 bind 中參數(shù)的基礎(chǔ)上再往后排。
應(yīng)用場景

求數(shù)組中的最大和最小值

var arr = [1,2,3,89,46]
var max = Math.max.apply(null,arr)//89
var min = Math.min.apply(null,arr)//1

將類數(shù)組轉(zhuǎn)化為數(shù)組

var trueArr = Array.prototype.slice.call(arrayLike)

數(shù)組追加

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都指向?qū)嵗?    this.name = name
    this.age = age
    this.sayAge = function(){
        console.log(this.age)
    }
}
function Female(){
    Person.apply(this,arguments)//將父元素所有方法在這里執(zhí)行一遍就繼承了
}
var dot = new Female("Dot",2)

使用 log 代理 console.log

function log(){
  console.log.apply(console, arguments);
}
// 當(dāng)然也有更方便的 var log = console.log()
call、apply和bind函數(shù)存在的區(qū)別

bind返回對應(yīng)函數(shù), 便于稍后調(diào)用; apply, call則是立即調(diào)用。

除此外, 在 ES6 的箭頭函數(shù)下, call 和 apply 將失效, 對于箭頭函數(shù)來說:

箭頭函數(shù)體內(nèi)的 this 對象, 就是定義時(shí)所在的對象, 而不是使用時(shí)所在的對象;所以不需要類似于var _this = this這種丑陋的寫法
箭頭函數(shù)不可以當(dāng)作構(gòu)造函數(shù),也就是說不可以使用 new 命令, 否則會(huì)拋出一個(gè)錯(cuò)誤
箭頭函數(shù)不可以使用 arguments 對象,,該對象在函數(shù)體內(nèi)不存在. 如果要用, 可以用 Rest 參數(shù)代替
不可以使用 yield 命令, 因此箭頭函數(shù)不能用作 Generator 函數(shù)

ajax

面試題: 11.ajax是同步還是異步,怎么樣實(shí)現(xiàn)同步;12.ajax實(shí)現(xiàn)過程
參考: https://blog.csdn.net/qq_2956...
https://blog.csdn.net/xxf1597...

Ajax全稱Asynchronous JavaScript and XML,也就是異步的js和XML技術(shù)。

Ajax的使用四大步驟詳解

第一步,創(chuàng)建xmlhttprequest對象

var xmlhttp =new XMLHttpRequest();
//XMLHttpRequest對象用來和服務(wù)器交換數(shù)據(jù)。
var xhttp;

if(window.XMLHttpRequest) {
//現(xiàn)代主流瀏覽器
xhttp= new XMLHttpRequest();
}else{
//針對瀏覽器,比如IE5或IE6
xhttp= new ActiveXObject("Microsoft.XMLHTTP");
}

第二步,使用xmlhttprequest對象的open()和send()方法發(fā)送資源請求給服務(wù)器。
xmlhttp.open(method,url,async) method包括get 和post,url主要是文件或資源的路徑,async參數(shù)為true(代表異步)或者false(代表同步)

xhttp.send();使用get方法發(fā)送請求到服務(wù)器。

xhttp.send(string);使用post方法發(fā)送請求到服務(wù)器。

post 發(fā)送請求什么時(shí)候能夠使用呢?
(1)更新一個(gè)文件或者數(shù)據(jù)庫的時(shí)候。
(2)發(fā)送大量數(shù)據(jù)到服務(wù)器,因?yàn)閜ost請求沒有字符限制。
(3)發(fā)送用戶輸入的加密數(shù)據(jù)。

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表單數(shù)據(jù)需要使用xmlhttprequest對象的setRequestHeader方法增加一個(gè)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 當(dāng)服務(wù)器準(zhǔn)備響應(yīng)時(shí)將執(zhí)行onreadystatechange函數(shù)。

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函數(shù),直接在send后面寫上執(zhí)行代碼。

xhttp.open("GET", "index.aspx", false);

xhttp.send();

document.getElementById("demo").innerHTML = xhttp.responseText;

第三步,使用xmlhttprequest對象的responseText或responseXML屬性獲得服務(wù)器的響應(yīng)。
使用responseText屬性得到服務(wù)器響應(yīng)的字符串?dāng)?shù)據(jù),使用responseXML屬性得到服務(wù)器響應(yīng)的XML數(shù)據(jù)。

例子如下:

document.getElementById("demo").innerHTML = xhttp.responseText;

//服務(wù)器響應(yīng)的XML數(shù)據(jù)需要使用XML對象進(jìn)行轉(zhuǎn)換。
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函數(shù)
當(dāng)發(fā)送請求到服務(wù)器,我們想要服務(wù)器響應(yīng)執(zhí)行一些功能就需要使用onreadystatechange函數(shù),每次xmlhttprequest對象的readyState發(fā)生改變都會(huì)觸發(fā)onreadystatechange函數(shù)。
onreadystatechange屬性存儲(chǔ)一個(gè)當(dāng)readyState發(fā)生改變時(shí)自動(dòng)被調(diào)用的函數(shù)。

readyState屬性,XMLHttpRequest對象的狀態(tài),改變從0到4,0代表請求未被初始化,1代表服務(wù)器連接成功,2請求被服務(wù)器接收,3處理請求,4請求完成并且響應(yīng)準(zhǔn)備。
status屬性,200表示成功響應(yīng),404表示頁面不存在。

在onreadystatechange事件中,服務(wù)器響應(yīng)準(zhǔn)備的時(shí)候發(fā)生,當(dāng)readyState==4和status==200的時(shí)候服務(wù)器響應(yīng)準(zhǔn)備。

步驟總結(jié)

創(chuàng)建XMLHttpRequest對象,也就是創(chuàng)建一個(gè)異步調(diào)用對象.
創(chuàng)建一個(gè)新的HTTP請求,并指定該HTTP請求的方法、URL及驗(yàn)證信息.? ? ? ?
設(shè)置響應(yīng)HTTP請求狀態(tài)變化的函數(shù).? ? ? ?
發(fā)送HTTP請求.? ? ? ?
獲取異步調(diào)用返回的數(shù)據(jù).? ? ? ?
使用JavaScript和DOM實(shí)現(xiàn)局部刷新.

同步&異步

AJAX中根據(jù)async的值不同分為同步(async = false)和異步(async = true)兩種執(zhí)行方式

$.ajax({?

??????? type: "post",?

?????? url: "path",?

?????? cache:false,?

?????? async:false,?

??????? dataType: ($.browser.msie) ? "text" : "xml",?

???????? success: function(xmlobj){?

? ? ? ? ? ? ? ? ? ? ? function1(){};

??????? }?

});

?function2(){};

一.什么是同步請求:(false)
同步請求即是當(dāng)前發(fā)出請求后,瀏覽器什么都不能做,必須得等到請求完成返回?cái)?shù)據(jù)之后,才會(huì)執(zhí)行后續(xù)的代碼,相當(dāng)于是排隊(duì),前一個(gè)人辦理完自己的事務(wù),下一個(gè)人才能接著辦。也就是說,當(dāng)JS代碼加載到當(dāng)前AJAX的時(shí)候會(huì)把頁面里所有的代碼停止加載,頁面處于一個(gè)假死狀態(tài),當(dāng)這個(gè)AJAX執(zhí)行完畢后才會(huì)繼續(xù)運(yùn)行其他代碼頁面解除假死狀態(tài)(即當(dāng)ajax返回?cái)?shù)據(jù)后,才執(zhí)行后面的function2)。?
二.什么是異步請求:(true)
? 異步請求就當(dāng)發(fā)出請求的同時(shí),瀏覽器可以繼續(xù)做任何事,Ajax發(fā)送請求并不會(huì)影響頁面的加載與用戶的操作,相當(dāng)于是在兩條線上,各走各的,互不影響。一般默認(rèn)值為true,異步。異步請求可以完全不影響用戶的體驗(yàn)效果,無論請求的時(shí)間長或者短,用戶都在專心的操作頁面的其他內(nèi)容,并不會(huì)有等待的感覺。

同步適用于一些什么情況呢?
我們可以想一下,同步是一步一步來操作,等待請求返回的數(shù)據(jù),再執(zhí)行下一步,那么一定會(huì)有一些情況,只有這一步執(zhí)行完,拿到數(shù)據(jù),通過獲取到這一步的數(shù)據(jù)來執(zhí)行下一步的操作。這是異步?jīng)]有辦法實(shí)現(xiàn)的,因此同步的存在一定有他存在的道理。
我們在發(fā)送AJAX請求后,還需要繼續(xù)處理服務(wù)器的響應(yīng)結(jié)果,如果這時(shí)我們使用異步請求模式同時(shí)未將結(jié)果的處理交由另一個(gè)JS函數(shù)進(jìn)行處理。這時(shí)就有可能發(fā)生這種情況:異步請求的響應(yīng)還沒有到達(dá),函數(shù)已經(jīng)執(zhí)行完了return語句了,這時(shí)將導(dǎo)致return的結(jié)果為空字符串。

閉包

面試題:13.閉包的作用理解,以及那些地方用過閉包,以及閉包的缺點(diǎn),如何實(shí)現(xiàn)閉包
參考:https://segmentfault.com/a/11...

閉包的作用理解

變量的作用域

變量的作用域無非就是兩種:全局變量和局部變量。Javascript語言的特殊之處,就在于函數(shù)內(nèi)部可以直接讀取全局變量。

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

//另一方面,在函數(shù)外部自然無法讀取函數(shù)內(nèi)的局部變量。
  function f1(){
    var n=999;
  }

  alert(n); // error
//這里有一個(gè)地方需要注意,函數(shù)內(nèi)部聲明變量的時(shí)候,一定要使用var命令。如果不用的話,你實(shí)際上聲明了一個(gè)全局變量!

從外部讀取局部變量

 function f1(){

    var n=999;

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

  }
/*在上面的代碼中,函數(shù)f2就被包括在函數(shù)f1內(nèi)部,這時(shí)f1內(nèi)部的所有局部變量,
對f2都是可見的。但是反過來就不行,f2內(nèi)部的局部變量,對f1就是不可見的。*/
/*這就是Javascript語言特有的"鏈?zhǔn)阶饔糜?結(jié)構(gòu)(chain scope),
子對象會(huì)一級一級地向上尋找所有父對象的變量。
所以,父對象的所有變量,對子對象都是可見的,反之則不成立。*/

閉包的概念

閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
由于在Javascript語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡單理解成"定義在一個(gè)函數(shù)內(nèi)部的函數(shù)"。

閉包的用途
閉包可以用在許多地方。它的最大用處有兩個(gè),一個(gè)是前面提到的可以讀取函數(shù)內(nèi)部的變量,另一個(gè)就是讓這些變量的值始終保持在內(nèi)存中。

function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

result實(shí)際上就是閉包f2函數(shù)。它一共運(yùn)行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數(shù)f1中的局部變量n一直保存在內(nèi)存中,并沒有在f1調(diào)用后被自動(dòng)清除。

原因就在于f1是f2的父函數(shù),而f2被賦給了一個(gè)全局變量,這導(dǎo)致f2始終在內(nèi)存中,而f2的存在依賴于f1,因此f1也始終在內(nèi)存中,不會(huì)在調(diào)用結(jié)束后,被垃圾回收機(jī)制(garbage collection)回收。

這段代碼中另一個(gè)值得注意的地方,就是"nAdd=function(){n+=1}"這一行,首先在nAdd前面沒有使用var關(guān)鍵字,因此nAdd是一個(gè)全局變量,而不是局部變量。其次,nAdd的值是一個(gè)匿名函數(shù)(anonymous function),而這個(gè)匿名函數(shù)本身也是一個(gè)閉包,所以nAdd相當(dāng)于是一個(gè)setter,可以在函數(shù)外部對函數(shù)內(nèi)部的局部變量進(jìn)行操作。

使用閉包的注意點(diǎn)
1)由于閉包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會(huì)造成網(wǎng)頁的性能問題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。

2)閉包會(huì)在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當(dāng)作對象(object)使用,把閉包當(dāng)作它的公用方法(Public Method),把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時(shí)一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。

閉包的使用場景

應(yīng)用場景一:setTimeout
原生的setTimeout有一個(gè)缺陷,你傳遞的第一個(gè)函數(shù)不能帶參數(shù)。即

setTimeout(func(parma),1000);

我們就可以用閉包來實(shí)現(xiàn)這個(gè)效果了

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

應(yīng)用場景二:用閉包模擬私有方法

// 可以減少閉包占用的內(nèi)存問題,因?yàn)闆]有指向匿名函數(shù)的引用。只要函數(shù)執(zhí)行畢,就可以立即銷毀其作用域鏈了
(function(){
    function createFunc() {
        var name = "wheeler";
        return function () {
            return name;
        }
    }

    var nameFunc = createFunc();

    var name = nameFunc();

    console.log(name);
})();
閉包的應(yīng)用場景
用閉包模擬私有方法
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());

應(yīng)用場景三:緩存

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

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

應(yīng)用場景四:封裝

var person = function(){
    var name = "default";//變量作用域?yàn)楹瘮?shù)內(nèi)部,外部無法訪問
    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.跨域方法以及怎么樣實(shí)現(xiàn)的與原理
參考:http://www.ruanyifeng.com/blo...
https://blog.csdn.net/qq_3409...
https://blog.csdn.net/qq_2860...
http://www.ruanyifeng.com/blo...

同源政策

所謂"同源"指的是"三個(gè)相同"。

協(xié)議相同

域名相同

端口相同

舉例

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

這個(gè)網(wǎng)址,協(xié)議是http://,域名是www.example.com,端口是80(默認(rèn)端口可以省略)。它的同源情況如下:

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 請求不能發(fā)送。

Cookie
Cookie 是服務(wù)器寫入瀏覽器的一小段信息,只有同源的網(wǎng)頁才能共享。

但是,兩個(gè)網(wǎng)頁一級域名相同,只是二級域名不同,瀏覽器允許通過設(shè)置document.domain共享 Cookie。

舉例來說,A網(wǎng)頁是http://w1.example.com/a.html,B網(wǎng)頁是http://w2.example.com/b.html,那么只要設(shè)置相同的document.domain,兩個(gè)網(wǎng)頁就可以共享Cookie。

document.domain = "example.com";
//現(xiàn)在,A網(wǎng)頁通過腳本設(shè)置一個(gè) Cookie。
document.cookie = "test1=hello";

//B網(wǎng)頁就可以讀到這個(gè) Cookie。
var allCookie = document.cookie;
注意,這種方法只適用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 無法通過這種方法
另外,服務(wù)器也可以在設(shè)置Cookie的時(shí)候,指定Cookie的所屬域名為一級域名,比如.example.com。
Set-Cookie: key=value; domain=.example.com; path=/

iframe
如果兩個(gè)網(wǎng)頁不同源,就無法拿到對方的DOM。典型的例子是iframe窗口和window.open方法打開的窗口,它們與父窗口無法通信。

//比如,父窗口運(yùn)行下面的命令,如果iframe窗口不是同源,就會(huì)報(bào)錯(cuò)。
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame

//子窗口獲取主窗口的DOM也會(huì)報(bào)錯(cuò)。
window.parent.document.body
// 報(bào)錯(cuò)

如果兩個(gè)窗口一級域名相同,只是二級域名不同,那么設(shè)置上一節(jié)介紹的document.domain屬性,就可以規(guī)避同源政策,拿到DOM。

例子:

/* 1. 在頁面 http://a.example.com/a.html 設(shè)置document.domain */




/* 2. 在頁面http:// b.example.com/b.html 中設(shè)置document.domain */

對于完全不同源的網(wǎng)站,目前有三種方法,可以解決跨域窗口的通信問題:

片段識(shí)別符(fragment identifier)

window.name

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

片段識(shí)別符

片段標(biāo)識(shí)符(fragment identifier)指的是,URL的#號(hào)后面的部分,比如http://example.com/x.html#fra...。如果只是改變片段標(biāo)識(shí)符,頁面不會(huì)重新刷新。
父窗口可以把信息,寫入子窗口的片段標(biāo)識(shí)符。

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

//子窗口通過監(jiān)聽hashchange事件得到通知。
window.onhashchange = checkMessage;

function checkMessage() {
  var message = window.location.hash;
  // ...
}
//同樣的,子窗口也可以改變父窗口的片段標(biāo)識(shí)符。
parent.location.href= target + "#" + hash;

window.name

瀏覽器窗口有window.name屬性。這個(gè)屬性的最大特點(diǎn)是,無論是否同源,只要在同一個(gè)窗口里,前一個(gè)網(wǎng)頁設(shè)置了這個(gè)屬性,后一個(gè)網(wǎng)頁可以讀取它。

//父窗口先打開一個(gè)子窗口,載入一個(gè)不同源的網(wǎng)頁,該網(wǎng)頁將信息寫入window.name屬性。
window.name = data;

//接著,子窗口跳回一個(gè)與主窗口同域的網(wǎng)址。
location = "http://parent.url.com/xxx.html";

//主窗口就可以讀取子窗口的window.name了。
var data = document.getElementById("myFrame").contentWindow.name;
這種方法的優(yōu)點(diǎn)是,window.name容量很大,可以放置非常長的字符串;缺點(diǎn)是必須監(jiān)聽子窗口window.name屬性的變化,影響網(wǎng)頁性能。

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



    
        
        
    
    

        

        
    

www.domain.com下b.html頁:



    
        
        
    
    
        2
        
        
    

window.postMessage

這個(gè)API為window對象新增了一個(gè)window.postMessage方法,允許跨窗口通信,不論這兩個(gè)窗口是否同源。

舉例來說,父窗口http://aaa.com向子窗口http://bbb.com發(fā)消息,調(diào)用postMessage方法就可以了。

var popup = window.open("http://bbb.com", "title");
popup.postMessage("Hello World!", "http://bbb.com");
postMessage方法的第一個(gè)參數(shù)是具體的信息內(nèi)容,第二個(gè)參數(shù)是接收消息的窗口的源(origin),即"協(xié)議 + 域名 + 端口"。也可以設(shè)為*,表示不限制域名,向所有窗口發(fā)送。

子窗口向父窗口發(fā)送消息的寫法類似。

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

//父窗口和子窗口都可以通過message事件,監(jiān)聽對方的消息。
window.addEventListener("message", function(e) {
  console.log(e.data);
},false);

message事件的事件對象event,提供以下三個(gè)屬性:

event.source:發(fā)送消息的窗口

event.origin: 消息發(fā)向的網(wǎng)址

event.data: 消息內(nèi)容

子窗口通過event.source屬性引用父窗口,然后發(fā)送消息:

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

event.origin屬性可以過濾不是發(fā)給本窗口的消息。

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: 指目標(biāo)窗口,也就是給哪個(gè)window發(fā)消息,是
window.frames 屬性的成員或者由 window.open 方法創(chuàng)建的窗口
message: 是要發(fā)送的消息,類型為 String、Object (IE8、9 不支持)
targetOrigin: 是限定消息接收范圍,不限制請使用 ‘*’

例子:

//本地代碼index.html

  
      
      
//server.com上remote.html,監(jiān)聽message事件,并檢查來源是否是要通信的域。


    
    

LocalStorage

主窗口寫入iframe子窗口的localStorage

//子窗口將父窗口發(fā)來的消息,寫入自己的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));
};

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

加強(qiáng)版的子窗口接收消息:

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;
  }
};

加強(qiáng)版的父窗口發(fā)送消息代碼:

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

這個(gè)辦法比較繞,但是可以解決完全跨域情況下的腳步置換問題。原理是利用location.hash來進(jìn)行傳值。www.a.com下的a.html想和www.b.com下的b.html通信(在a.html中動(dòng)態(tài)創(chuàng)建一個(gè)b.html的iframe來發(fā)送請求)
但是由于“同源策略”的限制他們無法進(jìn)行交流(b.html無法返回?cái)?shù)據(jù)),于是就找個(gè)中間人:www.a.com下的c.html(注意是www.a.com下的)。
b.html將數(shù)據(jù)傳給c.html(b.html中創(chuàng)建c.html的iframe),由于c.html和a.html同源,于是可通過c.html將返回的數(shù)據(jù)傳回給a.html,從而達(dá)到跨域的效果。

//a.html

//b.html

//由于兩個(gè)頁面不在同一個(gè)域下,IE、Chrome不允許修改parent.location.hash的值,所以要借助于a.com域名下的一個(gè)代理iframe,這里有一個(gè)a.com下的代理文件c.html。Firefox可以修改。
//c.html

直接訪問a.html,a.html向b.html發(fā)送的消息為”sayHi”;b.html通過消息判斷返回了”HiWorld”,并通過c.html改變了location.hash的值

AJAX
同源政策規(guī)定,AJAX請求只能發(fā)給同源的網(wǎng)址,否則就報(bào)錯(cuò)。

除了架設(shè)服務(wù)器代理(瀏覽器請求同源服務(wù)器,再由后者請求外部服務(wù)),有三種方法規(guī)避這個(gè)限制。

JSONP

WebSocket

CORS

JSONP
JSONP是服務(wù)器與客戶端跨源通信的常用方法。最大特點(diǎn)就是簡單適用,老式瀏覽器全部支持,服務(wù)器改造非常小。

它的基本思想是,網(wǎng)頁通過添加一個(gè)

閱讀需要支付1元查看
<