摘要:在運行這一行之后,也指向這顯然會導致繼承鏈的紊亂明明是用構造函數生成的,因此我們必須手動糾正,將對象的值改為。下文都遵循這一點,即如果替換了對象,那么,下一步必然是為新的對象加上屬性,并將這個屬性指回原來的構造函數。
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"http://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"http://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是 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); //1call
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(); // Dotbind
和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 函數
面試題: 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是服務器與客戶端跨源通信的常用方法。最大特點就是簡單適用,老式瀏覽器全部支持,服務器改造非常小。
它的基本思想是,網頁通過添加一個