大廠的532道面試題知識點筆記

express&koa

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

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

初識二者

express:css

var express = require('express')
var app = express()  //建立一個APP實例
 
//建一個項目根目錄的get請求路由,回調方法中直接輸出字符串Hello World!
app.get('/', function (req, res) {
    res.send('Hello World!')
});
 
//監聽端口,啓動服務
app.listen(3000);

koa:html

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、是否啓用某個功能特性、接口配置、當前登陸用戶數據等等。 java

express:node

//共享配置,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:jquery

//配置,直接使用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,再取值。git

express:es6

// 獲取QueryString參數
// GET /shoes?order=desc&shoe[color]=blue
req.query.order
// => "desc"
 
req.query.shoe.color
// => "blue"
 
// 經過路由獲取Restful風格的URL參數
app.get('/user/:id?', function userIdHandler(req, res) {
    console.log(req.params.id);
    res.send('GET');
})
 
//獲取POST數據:須要body-parser中間件
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/', function (req, res) {
    console.log(req.body);
    res.json(req.body);

koa:

// 獲取QueryString參數
// GET /?action=delete&id=1234
this.request.query
// => { action: 'delete', id: '1234' }
 
// 經過路由獲取Restful風格的URL參數
var route = require('koa-route');
app.use(route.get('/post/:id', function *(id){
    console.log(id);
    // => 1234
}));
 
// 獲取POST數據:須要co-body中間件
// Content-Type: application/x-www-form-urlencoded
// title=Test&content=This+is+a+test+post
var parse = require('co-body');
app.use(route.post('/post/new', function *(){
    var post = yield parse(this.request);//this
    console.log(post);
    // => { title: 'Test', content: 'This is a test post' }
}));

路由Route

收到客戶端的請求,服務須要經過識別請求的方法(HTTP Method: GET, POST, PUT...)和請求的具體路徑(path)來進行不一樣的處理。這部分功能就是路由(Route)須要作的事情,說白了就是請求的分發,分發到不一樣的回調方法去處理。

express

// app.all表示對全部的路徑和請求方式都要通過這些回調方法的處理,能夠逗號方式傳入多個
app.all('*', authentication, loadUser);
// 也能夠屢次調用
app.all('*', requireAuthentication)
app.all('*', loadUser);
// 也能夠針對某具體路徑下面的全部請求
app.all('/api/*', requireAuthentication);
 
// app.get GET方式的請求
app.get('/user/:id', function(req, res) {
    res.send('user ' + req.params.id);
});
 
// app.post  POST方式的請求
app.post('/user/create', function(req, res) {
    res.send('create new user');
});

這裏須要說明2個問題,首先是app.get,在應用生命週期中也有一個app.get方法,用於獲取項目配置。Express內部就是公用的一個方法,若是傳入的只有1個參數就獲取配置,2個參數就做爲路由處理。其次是app.use('', cb) 與app.all('', cb) 的區別,前者是中間件方式,調用是有順序的,不必定會執行到;後者是路由方式,確定會執行到。

koa

// Koa
// 和Express不一樣,koa須要先引入route中間件
var route = require('koa-route');
 
//引入中間件以後支持的寫法差很少,只是路徑傳入route,而後把route做爲中間件掛載到app
app.use(route.get('/', list));
app.use(route.get('/post/new', add));
app.use(route.get('/post/:id', show));
app.use(route.post('/post', create));
 
//鏈式寫法
var router = require('koa-router')();
 
router.get('/', list)
      .get('/post/new', add)
      .get('/post/:id', show)
      .post('/post', create);
 
app.use(router.routes())
   .use(router.allowedMethods());

視圖view

Express框架自身集成了視圖功能,提供了consolidate.js功能,能夠是有幾乎全部Javascript模板引擎,並提供了視圖設置的便利方法。Koa須要引入co-views中間件,co-views也是基於consolidate.js,支持能力同樣強大。

express

// Express
// 這隻模板路徑和默認的模板後綴
app.set('views', __dirname + '/tpls');
app.set('view engine', 'html');
 
//默認,express根據template的後綴自動選擇模板
//引擎渲染,支持jade和ejs。若是不使用默認擴展名
app.engine(ext, callback)
 
app.engine('html', require('ejs').renderFile);
 
//若是模板引擎不支持(path, options, callback)
var engines = require('consolidate');
app.engine('html', engines.handlebars);
app.engine('tpl', engines.underscore);
 
app.get('list', function(res, req){
    res.render('list', {data});
});

koa

//須要引入co-views中間件
var views = require('co-views');
 
var render = views('tpls', {
    map: { html: 'swig' },//html後綴使用引擎
    default: "jade"//render不提供後綴名時
});
 
var userInfo = {
    name: 'tobi',
    species: 'ferret'
};
 
var html;
html = render('user', { user: userInfo });
html = render('user.jade', { user: userInfo });
html = render('user.ejs', { user: userInfo });

返回HTTP Response

獲取完請求參數、處理好了具體的請求、視圖也準備就緒,下面就該返回給客戶端了,那就是HTTP Response對象了。這部分也屬於框架的基礎部分,各類都作了封裝實現,顯著的區別是koa直接將輸出綁定到了ctx.body屬性上,另外輸出JSON或JSONP須要引入中間件。

express

//輸出普通的html
res.render('tplName', {data});
 
//輸出JSON
res.jsonp({ user: 'Samoay' });
// => { "user": "Samoay" }
 
//輸出JSONP   ?callback=foo
res.jsonp({ user: 'Samoay' });
// => foo({ "user": "Samoay" });
 
//res.send([body]);
res.send(new Buffer('whoop'));
res.send({ some: 'json' });
res.send('<p>some html</p>');
 
//設定HTTP Status狀態碼
res.status(200);

koa

app.use(route.get('/post/update/:id', function *(id){
    this.status = 404;
    this.body = 'Page Not Found';
}));
 
var views = require('co-views');
var render = views('tpls', {
    default: "jade"//render不提供後綴名時
});
app.use(route.get('/post/:id', function *(id){
    var post = getPost(id);
    this.status = 200;//by default, optional
    this.body = yield render('user', post);
}));
 
//JSON
var json = require('koa-json');
app.use(route.get('/post/:id', function *(id){
    this.body = {id:1234, title:"Test post", content:"..."};
}));

中間件 Middleware

對比了主要的幾個框架功能方面的使用,其實區別最大,使用方式最不一樣的地方是在中間件的處理上。Express因爲是在ES6特性以前的,中間件的基礎原理仍是callback方式的;而koa得益於generator特性和co框架(co會把全部generator的返回封裝成爲Promise對象),使得中間件的編寫更加優雅。

express

// req 用於獲取請求信息, ServerRequest 的實例
// res 用於響應處理結果, ServerResponse 的實例
// next() 函數用於將當前控制權轉交給下一步處理,
//        若是給 next() 傳遞一個參數時,表示出錯信息
var x = function (req, res, next) {
 
    // 對req和res進行必要的處理
 
    // 進入下一個中間件
    return next();
 
    // 傳遞錯誤信息到下一個中間件
    return next(err);
 
    // 直接輸出,再也不進入後面的中間件
    return res.send('show page');
};

koa

// koa 一切都在ctx對象上+generator
app.use(function *(){
    this; // is the Context
 
    this.request; // is a koa Request
    this.response; // is a koa Response
 
    this.req;// is node js request
    this.res;// is node js response
 
    //再也不進入後面的中間件, 回溯upstream
    return;
});

express處理多箇中間件:

const app = require("express")();
app.use((req,res,next)=>{
    console.log("first");
    //next();
});
app.use((req,res,next)=>{
    console.log("second");
    //next();
});
app.use((req,res,next)=>{
    console.log("third");
    res.status(200).send("<h1>headers ...</h1>");
});
app.listen(3001);

koa處理多箇中間件:

const Koa = require('koa');
const app = new Koa();
app.use((ctx,next) => {
   ctx.body = 'Hello Koa-1';
   next();
 });
 app.use((ctx,next) => {
   ctx.body = 'Hello Koa-2';
   next();
 });
 app.use((ctx,next) => {
   ctx.body = 'Hello Koa-3';
   next();
 });
app.listen(3000);

/*與express相似,koa中間件的入參也有兩個,
後一個就是next。next的功能與express同樣*/

/*上面介紹了koa的next()的功能,這裏的next()須要同步調用,千萬不要採用異步調用
*/

koa捕獲異常

異常捕獲

const http = require('http');
const https = require('https');
const Koa = require('koa');
const app = new Koa();
app.use((ctx)=>{
  str="hello koa2";//沒有聲明變量
  ctx.body=str;
})
app.on("error",(err,ctx)=>{//捕獲異常記錄錯誤日誌
   console.log(new Date(),":",err);
});
http.createServer(app.callback()).listen(3000);

上面的代碼運行後在瀏覽器訪問返回的結果是「Internal Server error」;咱們發現當錯誤發生的時候後端程序並無死掉,只是拋出了異常,前端也同時接收到了錯誤反饋,對於KOA來講,異常發生在中間件的執行過程當中,因此只要咱們在中間件執行過程當中將異常捕獲並處理就OK了。

添加中間鍵use方法

use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    if (isGeneratorFunction(fn)) {
      deprecate('Support for generators will be removed in v3. ' +
                'See the documentation for examples of how to convert old middleware ' +
                'https://github.com/koajs/koa/blob/master/docs/migration.md');
      fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
    this.middleware.push(fn);
    return this;
  }

/*fn能夠是三種類型的函數,普通函數,generator函數,
還有async函數。最後generator會被轉成async函數,。
因此最終中間件數組只會有普通函數和async函數。*/

異常處理

當異常捕獲是有兩種處理方式,一種就是響應錯誤請求,而就是觸發註冊註冊全局錯誤事件,好比記錄錯誤日誌

async 函數

一句話,async 函數就是 Generator 函數的語法糖。

前文有一個 Generator 函數,依次讀取兩個文件:

var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) reject(error);
      resolve(data);
    });
  });
};

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

寫成 async 函數,就是下面這樣:

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

一比較就會發現,async 函數就是將 Generator 函數的星號(*)替換成 async,將 yield 替換成 await,僅此而已。

async 函數的優勢
(1)內置執行器。 Generator 函數的執行必須靠執行器,因此纔有了 co 函數庫,而 async 函數自帶執行器。也就是說,async 函數的執行,與普通函數如出一轍,只要一行。

var result = asyncReadFile();

(2)更好的語義。 async 和 await,比起星號和 yield,語義更清楚了。async 表示函數裏有異步操做,await 表示緊跟在後面的表達式須要等待結果。
(3)更廣的適用性。 co 函數庫約定,yield 命令後面只能是 Thunk 函數或 Promise 對象,而 async 函數的 await 命令後面,能夠跟 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操做)。

async 函數的實現
async 函數的實現,就是將 Generator 函數和自動執行器,包裝在一個函數裏。

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

// 等同於

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

全部的 async 函數均可以寫成上面的第二種形式,其中的 spawn 函數就是自動執行器。

async 函數的用法
同 Generator 函數同樣,async 函數返回一個 Promise 對象,可使用 then 方法添加回調函數。當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操做完成,再接着執行函數體內後面的語句。

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

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

上面代碼是一個獲取股票報價的函數,函數前面的async關鍵字,代表該函數內部有異步操做。調用該函數時,會當即返回一個Promise對象。

指定多少毫秒後輸出一個值:

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('hello world', 50);

await 命令後面的 Promise 對象,運行結果多是 rejected,因此最好把 await 命令放在 try...catch 代碼塊中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另外一種寫法

async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
  });
}

await 命令只能用在 async 函數之中,若是用在普通函數,就會報錯。可是,若是將 forEach 方法的參數改爲 async 函數,也有問題。

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

  // 可能獲得錯誤結果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}
//上面代碼可能不會正常工做,緣由是這時三個 db.post 操做將是併發執行,
//也就是同時執行,而不是繼發執行。正確的寫法是採用 for 循環。

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

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

若是確實但願多個請求併發執行,可使用 Promise.all 方法。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的寫法

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

promise: https://segmentfault.com/n/13...

JS的繼承

面試題目:9.js的繼承

參考:http://www.ruanyifeng.com/blo...

構造函數的繼承

例子:

function Animal(){

    this.species = "動物";

  }

function Cat(name,color){

    this.name = name;

    this.color = color;

  }

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

function Cat(name,color){

    Animal.apply(this, arguments);

    this.name = name;

    this.color = color;

  }

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

  alert(cat1.species); // 動物

2、 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;

3、 直接繼承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

4、 利用空對象做爲中介

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); // 動物

5、 拷貝繼承

上面是採用prototype對象,實現繼承。咱們也能夠換一種思路,純粹採用"拷貝"方法實現繼承。簡單說,把父對象的全部屬性和方法,拷貝進子對象

  function Animal(){}

  Animal.prototype.species = "動物";

實現屬性拷貝的目的:

function extend2(Child, Parent) {

    var p = Parent.prototype;

    var c = Child.prototype;

    for (var i in p) {

      c[i] = p[i];

      }

    c.uber = p;
    //這個函數的做用,就是將父對象的prototype對象中的屬性,一一拷貝給Child    
      //對象的prototype對象。

  }

使用的時候,這樣寫:

extend2(Cat, Animal);

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

  alert(cat1.species); // 動物

非構造函數的繼承

例子:

var Chinese = {
    nation:'中國'
  };
  var Doctor ={
    career:'醫生'
  }

這兩個對象都是普通對象,不是構造函數,沒法使用構造函數方法實現"繼承"。

object()方法

 function object(o) {

    function F() {}

    F.prototype = o;

    return new F();

  }
//這個object()函數,其實只作一件事,就是把子對象的prototype屬性,
//指向父對象,從而使得子對象與父對象連在一塊兒。

使用的時候,第一步先在父對象的基礎上,生成子對象:

var Doctor = object(Chinese);

而後,再加上子對象自己的屬性:

Doctor.career = '醫生';

這時,子對象已經繼承了父對象的屬性了

 alert(Doctor.nation); //中國

淺拷貝

除了使用"prototype鏈"之外,還有另外一種思路:把父對象的屬性,所有拷貝給子對象,也能實現繼承。

function extendCopy(p) {

    var c = {};

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

    c.uber = p;

    return c;
  }

使用的時候,這樣寫:

var Doctor = extendCopy(Chinese);

Doctor.career = '醫生';

alert(Doctor.nation); // 中國

可是,這樣的拷貝有一個問題。那就是,若是父對象的屬性等於數組或另外一個對象,那麼實際上,子對象得到的只是一個內存地址,而不是真正拷貝,所以存在父對象被篡改的可能。

//如今給Chinese添加一個"出生地"屬性,它的值是一個數組。
 Chinese.birthPlaces = ['北京','上海','香港'];

//而後,咱們爲Doctor的"出生地"添加一個城市:
 Doctor.birthPlaces.push('廈門');

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

extendCopy()只是拷貝基本類型的數據,咱們把這種拷貝叫作"淺拷貝"。這是早期jQuery實現繼承的方式。

深拷貝

所謂"深拷貝",就是可以實現真正意義上的數組和對象的拷貝。它的實現並不難,只要遞歸調用"淺拷貝"就好了。

function deepCopy(p, c) {

    var c = c || {};

    for (var i in p) {

      if (typeof p[i] === 'object') {

        c[i] = (p[i].constructor === Array) ? [] : {};

        deepCopy(p[i], c[i]);

      } else {

         c[i] = p[i];

      }
    }

    return c;
  }

使用的時候這樣寫:

var Doctor = deepCopy(Chinese,Doctor);

如今,給父對象加一個屬性,值爲數組。而後,在子對象上修改這個屬性

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

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

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

  alert(Chinese.birthPlaces); //北京, 上海, 香港

call和apply的區別

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

this 用法

this是 JavaScript 語言的一個關鍵字。
它是函數運行時,在函數體內部自動生成的一個對象,只能在函數體內部使用。

狀況一:純粹的函數調用

這是函數的最一般用法,屬於全局性調用,所以this就表明全局對象。

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

狀況二:做爲對象方法的調用

函數還能夠做爲某個對象的方法調用,這時this就指這個上級對象。

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

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

obj.m(); // 1

狀況三 做爲構造函數調用

所謂構造函數,就是經過這個函數,能夠生成一個新對象。這時,this就指這個新對象。

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

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

//爲了代表這時this不是全局對象,咱們對代碼作一些改變
var x = 2;
function test() {
  this.x = 1;
}

var obj = new test();
x  // 2
//運行結果爲2,代表全局變量x的值根本沒變。

狀況四 apply 調用

apply()是函數的一個方法,做用是改變函數的調用對象。它的第一個參數就表示改變後的調用這個函數的對象。所以,這時this指的就是這第一個參數。

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

var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply() // 0

//若是把最後一行代碼修改成
obj.m.apply(obj); //1

call

call 方法第一個參數是要綁定給this的值,後面傳入的是一個參數列表。當第一個參數爲null、undefined的時候,默認指向window。

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

例子:

var obj = {
    message: 'My name is: '
}

function getName(firstName, lastName) {
    console.log(this.message + firstName + ' ' + lastName)
}

getName.call(obj, 'Dot', 'Dolby')

apply

apply接受兩個參數,第一個參數是要綁定給this的值,第二個參數是一個參數數組。當第一個參數爲null、undefined的時候,默認指向window。

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

當函數須要傳遞多個變量時, apply 能夠接受一個數組做爲參數輸入, call 則是接受一系列的單獨變量。

例子:

var obj = {
    message: 'My name is: '
}

function getName(firstName, lastName) {
    console.log(this.message + firstName + ' ' + lastName)
}

getName.apply(obj, ['Dot', 'Dolby'])// My name is: Dot Dolby

call和apply可用來借用別的對象的方法,這裏以call()爲例:

var Person1  = function () {
    this.name = 'Dot';
}
var Person2 = function () {
    this.getname = function () {
        console.log(this.name);
    }
    Person1.call(this);
}
var person = new Person2();
person.getname();       // Dot

bind

和call很類似,第一個參數是this的指向,從第二個參數開始是接收的參數列表。區別在於bind方法返回值是函數以及bind接收的參數列表的使用。

var obj = {
    name: 'Dot'
}

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

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

//bind 方法不會當即執行,而是返回一個改變了上下文 this 後的函數。
 //而原函數printName 中的 this 並無被改變,依舊指向全局對象 window。

參數的使用

function fn(a, b, c) {
    console.log(a, b, c);
}
var fn1 = fn.bind(null, 'Dot');

fn('A', 'B', 'C');            // A B C
fn1('A', 'B', 'C');           // Dot A B
fn1('B', 'C');                // Dot B C
fn.call(null, 'Dot');      // Dot undefined undefined

//call 是把第二個及之後的參數做爲 fn 方法的實參傳進去,
//而 fn1 方法的實參實則是在 bind 中參數的基礎上再日後排。

應用場景

求數組中的最大和最小值

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

將類數組轉化爲數組

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

數組追加

var arr1 = [1,2,3];
var arr2 = [4,5,6];
var total = [].push.apply(arr1, arr2);//6
// arr1 [1, 2, 3, 4, 5, 6]
// arr2 [4,5,6]

判斷變量類型

function isArray(obj){
    return Object.prototype.toString.call(obj) == '[object Array]';
}
isArray([]) // true
isArray('dot') // false

利用call和apply作繼承

function Person(name,age){
    // 這裏的this都指向實例
    this.name = name
    this.age = age
    this.sayAge = function(){
        console.log(this.age)
    }
}
function Female(){
    Person.apply(this,arguments)//將父元素全部方法在這裏執行一遍就繼承了
}
var dot = new Female('Dot',2)

使用 log 代理 console.log

function log(){
  console.log.apply(console, arguments);
}
// 固然也有更方便的 var log = console.log()

call、apply和bind函數存在的區別

bind返回對應函數, 便於稍後調用; apply, call則是當即調用。

除此外, 在 ES6 的箭頭函數下, call 和 apply 將失效, 對於箭頭函數來講:

箭頭函數體內的 this 對象, 就是定義時所在的對象, 而不是使用時所在的對象;因此不須要相似於var _this = this這種醜陋的寫法
箭頭函數不能夠看成構造函數,也就是說不可使用 new 命令, 不然會拋出一個錯誤
箭頭函數不可使用 arguments 對象,,該對象在函數體內不存在. 若是要用, 能夠用 Rest 參數代替
不可使用 yield 命令, 所以箭頭函數不能用做 Generator 函數

ajax

面試題: 11.ajax是同步仍是異步,怎麼樣實現同步;12.ajax實現過程
參考: https://blog.csdn.net/qq_2956...
https://blog.csdn.net/xxf1597...

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

Ajax的使用四大步驟詳解

第一步,建立xmlhttprequest對象

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

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

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

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

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

post 發送請求何時可以使用呢?
(1)更新一個文件或者數據庫的時候。
(2)發送大量數據到服務器,由於post請求沒有字符限制。
(3)發送用戶輸入的加密數據。

get例子:

xhttp.open("GET","ajax_info.txt",true);

xhttp.open("GET","index.html",true);

xhttp.open("GET","demo_get.asp?t="+ Math.random(), true);xhttp.send();

post例子

xhttp.open("POST", "demo_post.asp", true);

xhttp.send();

post表單例子
post表單數據須要使用xmlhttprequest對象的setRequestHeader方法增長一個HTTP頭。

xhttp.open("POST","ajax_test.aspx",true);

xhttp.setRequestHeader("Content-type",
"application/x-www-form-urlencoded");

xhttp.send("fname=Henry&lname=Ford");

async=true 當服務器準備響應時將執行onreadystatechange函數。

xhttp.onreadystatechange= function(){
if(xhttp.readyState == 4 && xhttp.status == 200) {
   document.getElementById("demo").innerHTML=xhttp.responseText;
}

};

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

xhttp.send();

asyn=false 則將不須要寫onreadystatechange函數,直接在send後面寫上執行代碼。

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

xhttp.send();

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

第三步,使用xmlhttprequest對象的responseText或responseXML屬性得到服務器的響應。
使用responseText屬性獲得服務器響應的字符串數據,使用responseXML屬性獲得服務器響應的XML數據。

例子以下:

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

//服務器響應的XML數據須要使用XML對象進行轉換。
xmlDoc= xhttp.responseXML;

txt= "";

x= xmlDoc.getElementsByTagName("ARTIST");

for(i = 0; i < x.length; i++) {
txt+= x[i].childNodes[0].nodeValue + "<br>";
}
document.getElementById("demo").innerHTML= txt;

第四步,onreadystatechange函數
當發送請求到服務器,咱們想要服務器響應執行一些功能就須要使用onreadystatechange函數,每次xmlhttprequest對象的readyState發生改變都會觸發onreadystatechange函數。
onreadystatechange屬性存儲一個當readyState發生改變時自動被調用的函數。

readyState屬性,XMLHttpRequest對象的狀態,改變從0到4,0表明請求未被初始化,1表明服務器鏈接成功,2請求被服務器接收,3處理請求,4請求完成而且響應準備。
status屬性,200表示成功響應,404表示頁面不存在。

在onreadystatechange事件中,服務器響應準備的時候發生,當readyState==4和status==200的時候服務器響應準備。

步驟總結

建立XMLHttpRequest對象,也就是建立一個異步調用對象.
建立一個新的HTTP請求,並指定該HTTP請求的方法、URL及驗證信息.       
設置響應HTTP請求狀態變化的函數.       
發送HTTP請求.       
獲取異步調用返回的數據.       
使用JavaScript和DOM實現局部刷新.

同步&異步

AJAX中根據async的值不一樣分爲同步(async = false)和異步(async = true)兩種執行方式

$.ajax({ 

        type: "post", 

       url: "path", 

       cache:false, 

       async:false, 

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

         success: function(xmlobj){ 

                      function1(){};

        } 

});

 function2(){};

一.什麼是同步請求:(false)
同步請求便是當前發出請求後,瀏覽器什麼都不能作,必須得等到請求完成返回數據以後,纔會執行後續的代碼,至關因而排隊,前一我的辦理完本身的事務,下一我的才能接着辦。也就是說,當JS代碼加載到當前AJAX的時候會把頁面裏全部的代碼中止加載,頁面處於一個假死狀態,當這個AJAX執行完畢後纔會繼續運行其餘代碼頁面解除假死狀態(即當ajax返回數據後,才執行後面的function2)。 
二.什麼是異步請求:(true)
  異步請求就當發出請求的同時,瀏覽器能夠繼續作任何事,Ajax發送請求並不會影響頁面的加載與用戶的操做,至關因而在兩條線上,各走各的,互不影響。通常默認值爲true,異步。異步請求能夠徹底不影響用戶的體驗效果,不管請求的時間長或者短,用戶都在專心的操做頁面的其餘內容,並不會有等待的感受。


同步適用於一些什麼狀況呢?
咱們能夠想一下,同步是一步一步來操做,等待請求返回的數據,再執行下一步,那麼必定會有一些狀況,只有這一步執行完,拿到數據,經過獲取到這一步的數據來執行下一步的操做。這是異步沒有辦法實現的,所以同步的存在必定有他存在的道理。
咱們在發送AJAX請求後,還須要繼續處理服務器的響應結果,若是這時咱們使用異步請求模式同時未將結果的處理交由另外一個JS函數進行處理。這時就有可能發生這種狀況:異步請求的響應尚未到達,函數已經執行完了return語句了,這時將致使return的結果爲空字符串。

閉包

面試題:13.閉包的做用理解,以及那些地方用過閉包,以及閉包的缺點,如何實現閉包
參考: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 */

<iframe id = "iframe" src="http://b.example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
    document.domain = 'example.com';//設置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 對象
    }
</script>

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

<script type="text/javascript">
    document.domain = 'example.com';//在iframe載入這個頁面也設置document.domain,使之與主頁面的document.domain相同
</script>

對於徹底不一樣源的網站,目前有三種方法,能夠解決跨域窗口的通訊問題:

  • 片斷識別符(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頁:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>

        <button>click!</button>

        <script type="text/javascript">

            var a=document.getElementsByTagName("button")[0];

            a.onclick=function(){                               //button添加click事件
                var inf=document.createElement("iframe");       //建立iframe
                inf.src="http://www.domain.com/window.name/b.html"+"?h=5"  //加載數據頁www.domain.com/window.name/b.html同事攜帶參數h=5
                var body=document.getElementsByTagName("body")[0];
                body.appendChild(inf);                          //引入a頁面

                inf.onload=function(){
                    inf.src='http://www.test.com/b.html'       //iframe加載完成,加載www.test.com域下邊的空白頁b.html
                    console.log(inf.contentWindow.name)        //輸出window.name中的數據
                    body.removeChild(inf)                      //清除iframe
                }
            }

        </script>
    </body>
</html>

www.domain.com下b.html頁:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <a href="" target="" title="">2</a>
        <script type="text/javascript" src="../cross/jquery-2.2.3.js">

        </script>
        <script>
            var str=window.location.href.substr(-1,1);      //獲取url中攜帶的參數值
            $.ajax({
                type:"get",
                url:"http://www.domain.com/a.php"+"?m="+str, //經過ajax將查詢參數傳給php頁面
                async:true,
                success:function(res){
                    window.name=res                         //將接收到的查詢數據賦值給window.name
                },
                error:function(){
                    window.name='error'                      //..
                }
            });
        </script>
    </body>
</html>

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,提供如下三個屬性:

  1. event.source:發送消息的窗口
  2. event.origin: 消息發向的網址
  3. 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 (IE八、9 不支持)
targetOrigin: 是限定消息接收範圍,不限制請使用 ‘*’


例子:

//本地代碼index.html

<body>  
    <iframe id="proxy" src="http://server.com/remote.html" onload = "postMsg()" style="display: none" ></iframe>  
    <script type="text/javascript">  
        var obj = {  
            msg: 'hello world'  
        }  
        function postMsg (){  
            var iframe = document.getElementById('proxy');  
            var win = iframe.contentWindow;  
            win.postMessage(obj,'http://server.com');  
        }  
    </script>  
</body>
//server.com上remote.html,監聽message事件,並檢查來源是不是要通訊的域。

<head>
    <title></title>
    <script type="text/javascript">
        window.onmessage = function(e){
            if(e.origin !== 'http://localhost:8088') return;
            alert(e.data.msg+" from "+e.origin);
        }
    </script>
</head>

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,從而達到跨域的效果。

clipboard.png

//a.html

<script>
function startRequest(){  
    var ifr = document.createElement('iframe');  
    ifr.style.display = 'none';  
    ifr.src = 'http://www.b.com/b.html#sayHi'; //傳遞的location.hash 
    document.body.appendChild(ifr);  
}  
function checkHash() {  
    try {  
        var data = location.hash ? location.hash.substring(1) : '';  
        if (console.log) {  
            console.log('Now the data is '+data);  
        }  
    } catch(e) {};  
}  
setInterval(checkHash, 2000); 
window.onload = startRequest;
</script>
//b.html

<script>
function checkHash(){
  var data = '';
  //模擬一個簡單的參數處理操做
  switch(location.hash){
    case '#sayHello': data = 'HelloWorld';break;
    case '#sayHi': data = 'HiWorld';break;
    default: break;
  }
  data && callBack('#'+data);
}
function callBack(hash){
  // ie、chrome的安全機制沒法修改parent.location.hash,因此要利用一箇中間的www.a.com域下的代理iframe
  var proxy = document.createElement('iframe');
  proxy.style.display = 'none';
  proxy.src = 'http://localhost:8088/proxy.html'+hash;  // 注意該文件在"www.a.com"域下
  document.body.appendChild(proxy);
}
window.onload = checkHash;
</script>
//因爲兩個頁面不在同一個域下,IE、Chrome不容許修改parent.location.hash的值,因此要藉助於a.com域名下的一個代理iframe,這裏有一個a.com下的代理文件c.html。Firefox能夠修改。
//c.html
<script>parent.parent.location.hash = self.location.hash.substring(1);  </script>

直接訪問a.html,a.html向b.html發送的消息爲」sayHi」;b.html經過消息判斷返回了」HiWorld」,並經過c.html改變了location.hash的值

clipboard.png

AJAX
同源政策規定,AJAX請求只能發給同源的網址,不然就報錯。

除了架設服務器代理(瀏覽器請求同源服務器,再由後者請求外部服務),有三種方法規避這個限制。

  1. JSONP
  2. WebSocket
  3. CORS

JSONP
JSONP是服務器與客戶端跨源通訊的經常使用方法。最大特色就是簡單適用,老式瀏覽器所有支持,服務器改造很是小。

它的基本思想是,網頁經過添加一個<script>元素,向服務器請求JSON數據,這種作法不受同源政策限制;服務器收到請求後,將數據放在一個指定名字的回調函數裏傳回來。

首先,網頁動態插入<script>元素,由它向跨源網址發出請求。

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};
//上面代碼經過動態添加<script>元素,向服務器example.com發出請求。
//注意,該請求的查詢字符串有一個callback參數,用來指定回調函數的名字,
//這對於JSONP是必需的。

//服務器收到這個請求之後,會將數據放在回調函數的參數位置返回。
foo({
  "ip": "8.8.8.8"
});

WebSocket
WebSocket是一種通訊協議,使用ws://(非加密)和wss://(加密)做爲協議前綴。該協議不實行同源政策,只要服務器支持,就能夠經過它進行跨源通訊。
具體實例:https://segmentfault.com/a/11...

跨域資源共享 CORS

它容許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。

兩種請求

瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

簡單請求:(只要同時知足如下兩大條件)
(1) 請求方法是如下三種方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的頭信息不超出如下幾種字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限於三個值application/x-www-form-urlencoded、
    multipart/form-data、text/plain

凡是不一樣時知足上面兩個條件,就屬於非簡單請求。

簡單請求

對於簡單請求,瀏覽器直接發出CORS請求。具體來講,就是在頭信息之中,增長一個Origin字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

/*上面的頭信息中,Origin字段用來講明,
本次請求來自哪一個源(協議 + 域名 + 端口)。
服務器根據這個值,決定是否贊成此次請求。*/

若是Origin指定的源,不在許可範圍內,服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。注意,這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200。

若是Origin指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

(1)Access-Control-Allow-Origin
該字段是必須的。它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求。

(2)Access-Control-Allow-Credentials
該字段可選。它的值是一個布爾值,表示是否容許發送Cookie。默認狀況下,Cookie不包括在CORS請求之中。設爲true,即表示服務器明確許可,Cookie能夠包含在請求中,一塊兒發給服務器。這個值也只能設爲true,若是服務器不要瀏覽器發送Cookie,刪除該字段便可。

(3)Access-Control-Expose-Headers
該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。若是想拿到其餘字段,就必須在Access-Control-Expose-Headers裏面指定。上面的例子指定,getResponseHeader('FooBar')能夠返回FooBar字段的值。

withCredentials 屬性

,CORS請求默認不發送Cookie和HTTP認證信息。若是要把Cookie發到服務器,一方面要服務器贊成,指定Access-Control-Allow-Credentials字段。

Access-Control-Allow-Credentials: true

開發者必須在AJAX請求中打開withCredentials屬性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

不然,即便服務器贊成發送Cookie,瀏覽器也不會發送。或者,服務器要求設置Cookie,瀏覽器也不會處理。

可是,若是省略withCredentials設置,有的瀏覽器仍是會一塊兒發送Cookie。這時,能夠顯式關閉withCredentials。

xhr.withCredentials = false;

須要注意的是,若是要發送Cookie,Access-Control-Allow-Origin就不能設爲星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也沒法讀取服務器域名下的Cookie。

CORS與JSONP的使用目的相同,可是比JSONP更強大。
JSONP只支持GET請求,CORS支持全部類型的HTTP請求。JSONP的優點在於支持老式瀏覽器,以及能夠向不支持CORS的網站請求數據。

快排

"快速排序"的思想很簡單,整個排序過程只須要三步:
(1)在數據集之中,選擇一個元素做爲"基準"(pivot)。
(2)全部小於"基準"的元素,都移到"基準"的左邊;全部大於"基準"的元素,都移到"基準"的右邊。
(3)對"基準"左邊和右邊的兩個子集,不斷重複第一步和第二步,直到全部子集只剩下一個元素爲止。

例子

舉例來講,如今有一個數據集{85, 24, 63, 45, 17, 31, 96, 50},怎麼對其排序呢?

第一步,選擇中間的元素45做爲"基準"。(基準值能夠任意選擇,可是選擇中間的值比較容易理解。)

clipboard.png
第二步,按照順序,將每一個元素與"基準"進行比較,造成兩個子集,一個"小於45",另外一個"大於等於45"。

clipboard.png
第三步,對兩個子集不斷重複第一步和第二步,直到全部子集只剩下一個元素爲止。

clipboard.png

clipboard.png

clipboard.png

//首先,定義一個quickSort函數,它的參數是一個數組。

var quickSort = function(arr) {

};
//而後,檢查數組的元素個數,若是小於等於1,就返回。
var quickSort = function(arr) {

  if (arr.length <= 1) { return arr; }

};
/*接着,選擇"基準"(pivot),並將其與原數組分離,
再定義兩個空數組,用來存放一左一右的兩個子集。*/
var quickSort = function(arr) {

  if (arr.length <= 1) { return arr; }

  var pivotIndex = Math.floor(arr.length / 2) ;

  var pivot = arr.splice(pivotIndex, 1)[0];

  var left = [];

  var right = [];

};
//開始遍歷數組,小於"基準"的元素放入左邊的子集,大於基準的元素放入右邊的子集。
for (var i = 0; i < arr.length; i++){

    if (arr[i] < pivot) {

      left.push(arr[i]);

    } else {

      right.push(arr[i]);

    }

  }
//使用遞歸不斷重複這個過程,就能夠獲得排序後的數組

js的事件機制

參考:https://www.cnblogs.com/lazyc...
clipboard.png


DOM0級事件處理程序

var btn5 = document.getElementById('btn5');
 btn5.onclick=function(){
    console.log(this.id);//btn5   
 };

基於DOM0的事件,對於同一個dom節點而言,只能註冊一個,後邊註冊的 同種事件 會覆蓋以前註冊的。利用這個原理咱們能夠解除事件,btn5.onclick=null;其中this就是綁定事件的那個元素;

以這種方式添加的事件處理程序會在事件流的冒泡階段被處理;

DOM2級事件處理程序

DOM2支持同一dom元素註冊多個同種事件,事件發生的順序按照添加的順序依次觸發(IE是相反的)。
DOM2事件經過addEventListener和removeEventListener管理

addEventListener(eventName,handlers,boolean);removeEventListener(),兩個方法都同樣接收三個參數,第一個是要處理的事件名,第二個是事件處理程序,第三個值爲false時表示在事件冒泡階段調用事件處理程序,通常建議在冒泡階段使用,特殊狀況纔在捕獲階段;
注意:經過addEventListener()添加的事件處理程序只能用removeEventListener()來移除,而且移除時傳入的參數必須與添加時傳入的參數同樣;

var btn2 = document.getElementById('btn2');
var handlers = function () {
   console.log(this.id);
};

btn2.addEventListener('click',handlers,false);

btn2.removeEventListener('click',handlers,false);

IE事件處理程序

IE用了attachEvent(),detachEvent(),接收兩個參數,事件名稱和事件處理程序,經過attachEvent()添加的事件處理程序都會被添加到冒泡階段,因此平時爲了兼容更多的瀏覽器最好將事件添加到事件冒泡階段,IE8及之前只支持事件冒泡;

var btn3 = document.getElementById('btn3');
 var handlers2=function(){
    console.log(this===window);//true,注意attachEvent()添加的事件處理程序運行在全局做用域中;
 };
 btn3.attachEvent('onclick',handlers2);

跨瀏覽器事件處理程序

//建立的方法是addHandlers(),removeHandlers(),這兩個方法屬於一個叫EventUtil的對象;可是這個沒有考慮到IE中做用域的問題,不過就添加和移除事件仍是足夠的。
 
var EventUtil = {
   addHandlers: function (element, type, handlers) {
      if (element.addEventListener) {
         element.addEventListener(type, handlers, false);
      } else if (element.attachEvent) {
         element.attachEvent(on + type, handlers);
      } else {
         element['on' + type] = handlers;
      }
   },
   removeHandlers: function (element, type, handlers) {
      if (element.removeEventListener) {
         element.removeEventListener(type, handlers, false);
      } else if (element.detachEvent) {
         element.detachEvent(on + type, handlers);
      } else {
         element['on' + type] = null;
      }
   }
};

例子:

var btn4=document.getElementById('btn4');
var handlers3=function(){
   console.log('123')
};
EventUtil.addHandlers(btn4,'click',handlers3);
//……
EventUtil.removeHandlers(btn4,'click',handlers3);

事件對象

兼容觸發DOM上的某個事件時,會產生一個事件對象event,這個對象中包含了全部與事件有關的信息,好比致使事件的元素target,事件的類型,及其餘特定的相關信息。例如鼠標操做致使的事件對象中會包含鼠標的位置,單雙擊等,而鍵盤操做致使的事件對象會包含按下的鍵等信息;

事件被觸發時,會默認給事件處理程序傳入一個參數e , 表示事件對象;經過e,咱們能夠得到其中包含的與事件有關的信息;

只有在事件處理程序執行期間,event對象纔會存在,一旦事件處理程序執行完畢,event對象就會被銷燬;


DOM中的事件對象

兼容DOM的瀏覽器會自動將一個事件對象event傳遞給事件處理程序

(注:event.target不支持IE瀏覽器,應該用event.srcElement;還有 IE中經過attachment添加的事件是運行在全局做用域中的,this===window)

當事件綁定在真正的目標元素上時,this===target===currentTarget ,並且綁定事件時是否捕獲結果都是同樣的,此時eventParse==2;

event.stopPropagation()能夠阻止事件的傳播,但它阻止不了綁定在該元素上的其餘函數的執行
event.stopImmediatePropagation()阻止全部的事件執行

IE中的事件對象

IE中event參數是未定的,事件對象是做爲window的一個屬性存在的,所以能夠經過window.event來訪問event對象,不一樣於DOM級中event是做爲參數直接傳入和返回;

![圖片上傳中...]

事件委託

每當將事件處理程序指定給元素時,運行中的瀏覽器代碼與支持頁面交互的JS代碼之間就會創建一個鏈接,而這種鏈接越多,頁面執行起來就越慢。考慮內存和性能問題,爲了解決事件處理程序過多的問題,採用事件委託變得頗有必要。
冒泡機制,好比既然點擊子元素,也會觸發父元素的點擊事件,那咱們徹底能夠將子元素的事件要作的事寫到父元素的事件裏,也就是將子元素的事件處理程序寫到父元素的事件處理程序中,這就是事件委託;利用事件委託,只指定一個事件處理程序,就能夠管理某一個類型的全部事件;

例子:

window.onload = function(){
  var oUl = document.getElementById("ul");
  var aLi = oUl.getElementsByTagName("li");

/*
這裏要用到事件源:event 對象,事件源,無論在哪一個事件中,只要你操做的那個元素就是事件源。
ie:window.event.srcElement
標準下:event.target
nodeName:找到元素的標籤名
*/
  oUl.onmouseover = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    //alert(target.innerHTML);
    if(target.nodeName.toLowerCase() == "li"){
    target.style.background = "red";
    }
  }
  oUl.onmouseout = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    //alert(target.innerHTML);
    if(target.nodeName.toLowerCase() == "li"){
    target.style.background = "";
    }
  }
}

幾種最適合採用事件委託的事件:click,mousedown,mouseup,keydown,keyup,keypress

由於把事件綁定到了父節點上,所以省了綁定事件。就算後面新增的子節點也有了相關事件,刪除部分子節點不用去銷燬對應節點上綁定的事件
父節點是經過event.target來找對應的子節點的。(事件處理程序中的this值始終等於currentTarget的值,指向的是綁定到的那個元素)

JQ中的事件委託

html:
<ul id = "lists">
         <li>列表1</li>
         <li>列表2</li>
         <li>列表3</li>
         <li>列表4</li>
         <li>列表5</li>
         <li>列表6</li>
 </ul>
JS:
var lists = document.getElementById("lists");

        lists.addEventListener("click",function(event){

            var target = event.target;
            //防止父元素ul也觸發事件
            if(target.nodeName == "LI"){
               target.style.backgroundColor = "red";
            }
        })
JQ用on方法:

$(function(){
            $("#lists").on("click","li",function(event){
                var target = $(event.target);
                target.css("background-color","red");
            })
        })
用delegate()方法:

$(function(){
            $("#lists").delegate("li","click",function(event){
                var target = $(event.target);
                target.css("background-color","red");
            })
        })

on()方法和delegate()方法對於事件委託的寫法很像。而且執行事件委託的時候只有子元素(本文中的li)會觸發事件,而代爲執行的父元素(本文中爲ul)不會觸發事件,因此咱們不須要盤判斷觸發事件的元素節點名,這一點明顯優於原生的JavaScript。

用bind()方法:

$(function(){
            $("#lists").bind("click","li",function(event){
                var target = $(event.target);
                if(target.prop("nodeName")=="LI"){
                target.css("background-color","red");}
            })
        })

bind()方法同原生的JavaScript實現方法同樣,當父元素代子元素執行事件時,父元素也會 觸發事件,因此咱們須要判斷一下觸發事件的元素名。此外,用bind()方法給元素綁定事件的時候要注意,它只能給已經存在DOM元素添加事件,不能給將來存在DOM

元素添加添加事件。若是要頻繁地添加DOM元素,而且給新添加的DOM元素綁定事件的話,用on()方法

數組去重

方法一:

雙層循環,外層循環元素,內層循環時比較值
若是有相同的值則跳過,不相同則push進數組

Array.prototype.distinct = function(){
 var arr = this,
  result = [],
  i,
  j,
  len = arr.length;
 for(i = 0; i < len; i++){
  for(j = i + 1; j < len; j++){
   if(arr[i] === arr[j]){
    j = ++i;
   }
  }
  result.push(arr[i]);
 }
 return result;
}
var arra = [1,2,3,4,4,1,1,2,1,1,1];
arra.distinct();    //返回[3,4,2,1]

方法二:利用splice直接在原數組進行操做

雙層循環,外層循環元素,內層循環時比較值
值相同時,則刪去這個值
注意點:刪除元素以後,須要將數組的長度也減1.

Array.prototype.distinct = function (){
 var arr = this,
  i,
  j,
  len = arr.length;
 for(i = 0; i < len; i++){
  for(j = i + 1; j < len; j++){
   if(arr[i] == arr[j]){
    arr.splice(j,1);
    len--;
    j--;
   }
  }
 }
 return arr;
};
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,];
var b = a.distinct();
console.log(b.toString()); //1,2,3,4,5,6,56

優勢:簡單易懂
缺點:佔用內存高,速度慢

方法三:利用對象的屬性不能相同的特色進行去重

Array.prototype.distinct = function (){
 var arr = this,
  i,
  obj = {},
  result = [],
  len = arr.length;
 for(i = 0; i< arr.length; i++){
  if(!obj[arr[i]]){ //若是能查找到,證實數組元素重複了
   obj[arr[i]] = 1;
   result.push(arr[i]);
  }
 }
 return result;
};
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,];
var b = a.distinct();
console.log(b.toString()); //1,2,3,4,5,6,56

方法四:數組遞歸去重

先排序,而後從最後開始比較,遇到相同,則刪除

Array.prototype.distinct = function (){
 var arr = this,
  len = arr.length;
 arr.sort(function(a,b){  //對數組進行排序才能方便比較
  return a - b;
 })
 function loop(index){
  if(index >= 1){
   if(arr[index] === arr[index-1]){
    arr.splice(index,1);
   }
   loop(index - 1); //遞歸loop函數進行去重
  }
 }
 loop(len-1);
 return arr;
};
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,56,45,56];
var b = a.distinct();
console.log(b.toString());  //1,2,3,4,5,6,45,56

方法五:利用indexOf以及forEach

Array.prototype.distinct = function (){
 var arr = this,
  result = [],
  len = arr.length;
 arr.forEach(function(v, i ,arr){  //這裏利用map,filter方法也能夠實現
  var bool = arr.indexOf(v,i+1);  //從傳入參數的下一個索引值開始尋找是否存在重複
  if(bool === -1){
   result.push(v);
  }
 })
 return result;
};
var a = [1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,2,3,3,2,2,1,23,1,23,2,3,2,3,2,3];
var b = a.distinct();
console.log(b.toString()); //1,23,2,3

方法六:利用ES6的set

function dedupe(array){
 return Array.from(new Set(array));
}
dedupe([1,1,2,3]) //[1,2,3]

拓展運算符(...)內部使用for...of循環

let arr = [1,2,3,3];
let resultarr = [...new Set(arr)]; 
console.log(resultarr); //[1,2,3]

1、concat()方法

思路:concat() 方法將傳入的數組或非數組值與原數組合並,組成一個新的數組並返回。該方法會產生一個新的數組。

function concatArr(arr1, arr2){
  var arr = arr1.concat(arr2);
  arr = unique1(arr);//再引用上面的任意一個去重方法
  return arr;
}

2、Array.prototype.push.apply()

思路:該方法優勢是不會產生一個新的數組

var a = [1, 2, 3];
var b = [4, 5, 6];
Array.prototype.push.apply(a, b);//a=[1,2,3,4,5,6]
//等效於:a.push.apply(a, b);
//也等效於[].push.apply(a, b); 
function concatArray(arr1,arr2){
  Array.prototype.push.apply(arr1, arr2);
  arr1 = unique1(arr1);
  return arr1;
}

filter

arr.filter(function(element,index){
return index === arr.indexOf(element)
})

補充es6知識點

參考:https://www.cnblogs.com/chris...
https://www.cnblogs.com/rlann...

解構:

解構的做用是能夠快速取得數組或對象當中的元素或屬性,而無需使用arr[x]或者obj[key]等傳統方式進行賦值

//數組解構賦值:

var arr = ['this is a string', 2, 3];

//傳統方式
var a = arr[0],
    b = arr[1],
    c = arr[2];

//解構賦值,是否是簡潔不少?
var [a, b, c] = arr;

console.log(a);//this is a string
console.log(b);//2
console.log(c);//3

//嵌套數組解構:
var arr = [[1, 2, [3, 4]], 5, 6];
var [[d, e, [f, g]], h, i] = arr;
console.log(d);//1
console.log(f);//3
console.log(i);//6

//函數傳參解構:
var arr = ['this is a string', 2, 3];

function fn1([a, b, c]) {
    console.log(a);
    console.log(b);
    console.log(c);
}

fn1(arr);
//this is a string
//2
//3

//for循環解構:

var arr = [[11, 12], [21, 22], [31, 32]];
for (let [a, b] of arr) {
    console.log(a);
    console.log(b);
}
//11
//12
//21
//22
//31
//32

//對象賦值解構:
var obj = {
    name: 'chris',
    sex: 'male',
    age: 26,
    son: {
        sonname: '大熊',
        sonsex: 'male',
        sonage: 1
    }
};

function fn2({sex, age, name}) {
    console.log(name + ' ' + sex + ' ' + age);
}

fn2(obj);
//chris male 26

//變量名與對象屬性名不一致解構:
var obj = {
    name: 'chris',
    sex: 'male',
    age: 26
};
var {name: nickname, age: howold} = obj;
console.log(nickname + ' ' + howold); //chris 26

//嵌套對象解構:
var obj = {
    name: 'chris',
    sex: 'male',
    age: 26,
    son: {
        sonname: '大熊',
        sonsex: 'male',
        sonage: 1
    }
};
var {name, sex, age, son: {sonname, sonsex, sonage}} = obj;
console.log(sonname + ' ' + sonsex + ' ' + sonage);
//大熊 male 1

//Babel暫不支持這種嵌套解構
obj = {
    name: 'chris',
    sex: 'male',
    age: [1, 2, 3]
}

{name, sex, age: [a, b, c]} = obj;
console.log(c);

//嵌套對象屬性重名,解構時須要更改變量名:
name: 'chris',
    sex: 'male',
    age: 26,
    son: {
        name: '大熊',
        sex: 'male',
        age: 1
    }
};
//賦值解構
var {name: fathername, son: {name, sex, age}} = obj;
console.log(fathername); //chris
console.log(name); //大熊

//傳參解構
function fn3({sex, age, name, son: {name: sonname}}) {
    console.log(name + ' ' + sex + ' ' + age + ' ' + sonname);
}

fn3(obj);
//chris male 26 大熊

//循環解構對象:
var arr = [{name: 'chris', age: 26}, {name: 'jack',    age: 27}, {name: 'peter',age: 28}];

for (let {age, name} of arr) {
    console.log(name + ' ' + age);
}
//chris 26
//jack 27
//peter 28

//解構的特殊應用場景:
//變量互換
var x = 1,
    y = 2;
var [x, y] = [y, x];
console.log(x); //2
console.log(y); //1

//字符串解構
var str = 'love';
var [a, b, c, d] = str;
console.log(a);//l
console.log(b);//o
console.log(c);//v
console.log(d);//e

擴展運算符

擴展運算符用三個點號表示,功能是把數組或類數組對象展開成一系列用逗號隔開的值

var foo = function(a, b, c) {
    console.log(a);
    console.log(b);
    console.log(c);
}

var arr = [1, 2, 3];

//傳統寫法
foo(arr[0], arr[1], arr[2]);

//使用擴展運算符
foo(...arr);
//1
//2
//3

特殊應用場景:

//數組深拷貝
var arr2 = arr;
var arr3 = [...arr];
console.log(arr===arr2); //true, 說明arr和arr2指向同一個數組
console.log(arr===arr3); //false, 說明arr3和arr指向不一樣數組

//把一個數組插入另外一個數組字面量
var arr4 = [...arr, 4, 5, 6];
console.log(arr4);//[1, 2, 3, 4, 5, 6]

//字符串轉數組
var str = 'love';
var arr5 = [...str];
console.log(arr5);//[ 'l', 'o', 'v', 'e' ]

應用:

//將一個數組轉爲用逗號分隔的參數序列
console.log(...[a, b, c])  
// a b c

//將一個數組,變爲參數序列
 let add = (x, y) => x + y;
            let numbers = [3, 45];
            console.log(add(...numbers))//48
            
//使用擴展運算符展開數組代替apply方法,將數組轉爲函數的參數
//ES5 取數組最大值
console.log(Math.max.apply(this, [654, 233, 727]));
//ES6 擴展運算符
console.log(Math.max(...[654, 233, 727]))
//至關於
console.log(Math.max(654, 233, 727))

//使用push將一個數組添加到另外一個數組的尾部
// ES5  寫法  
var arr1 = [1, 2, 3];  
var arr2 = [4, 5, 6];  
Array.prototype.push.apply(arr1, arr2); 

//push方法的參數不能是數組,經過apply方法使用push方法 
// ES6  寫法  
let arr1 = [1, 2, 3];  
let arr2 = [4, 5, 6];  
arr1.push(...arr2); 

//合併數組
var arr1 = ['a', 'b'];  
var arr2 = ['c'];  
var arr3 = ['d', 'e'];  
// ES5 的合併數組  
arr1.concat(arr2, arr3);  
// [ 'a', 'b', 'c', 'd', 'e' ]  
// ES6 的合併數組  
[...arr1, ...arr2, ...arr3]  
// [ 'a', 'b', 'c', 'd', 'e' ] 

//將字符串轉換爲數組
[...'hello']  
// [ "h", "e", "l", "l", "o" ] 
//ES5
str.split('')

//轉換僞數組爲真數組
var nodeList = document.querySelectorAll('p');  
var array = [...nodeList]; 
//具備iterator接口的僞數組,非iterator對象用Array.from方法

//map結構
let map = new Map([  
[1, 'one'],  
[2, 'two'],  
[3, 'three'],  
]);  
let arr = [...map.keys()]; // [1, 2, 3]

rest運算符

rest運算符也是三個點號,不過其功能與擴展運算符剛好相反,把逗號隔開的值序列組合成一個數組

//主要用於不定參數,因此ES6開始能夠再也不使用arguments對象
var bar = function(...args) {
    for (let el of args) {
        console.log(el);
    }
}

bar(1, 2, 3, 4);
//1
//2
//3
//4

bar = function(a, ...args) {
    console.log(a);
    console.log(args);
}

bar(1, 2, 3, 4);
//1
//[ 2, 3, 4 ]

rest運算符配合解構使用:

var [a, ...rest] = [1, 2, 3, 4];
console.log(a);//1
console.log(rest);//[2, 3, 4]

對於三個點號,三點放在形參或者等號左邊爲rest運算符; 放在實參或者等號右邊爲spread運算符,或者說,放在被賦值一方爲rest運算符,放在賦值一方爲擴展運算符。

  • 在等號賦值或for循環中,若是須要從數組或對象中取值,儘可能使用解構。
  • 在本身定義函數的時候,若是調用者傳來的是數組或對象,形參儘可能使用解構方式,優先使用對象解構,其次是數組解構。代碼可讀性會很好。
  • 在調用第三方函數的時候,若是該函數接受多個參數,而且你要傳入的實參爲數組,則使用擴展運算符。能夠避免使用下標形式傳入參數。也能夠避免不少人習慣的使用apply方法傳入數組。
  • rest運算符使用場景應該稍少一些,主要是處理不定數量參數,能夠避免arguments對象的使用。

箭頭函數

參考:https://developer.mozilla.org...

引入箭頭函數有兩個方面的做用:更簡短的函數而且不綁定this。

//更短的函數

var materials = [
  'Hydrogen',
  'Helium',
  'Lithium',
  'Beryllium'
];

materials.map(function(material) { 
  return material.length; 
}); // [8, 6, 7, 9]

materials.map((material) => {
  return material.length;
}); // [8, 6, 7, 9]

materials.map(material => material.length); // [8, 6, 7, 9]

不綁定this:

function Person() {
  // Person() 構造函數定義 `this`做爲它本身的實例.
  this.age = 0;

  setInterval(function growUp() {
    // 在非嚴格模式, growUp()函數定義 `this`做爲全局對象, 
    // 與在 Person()構造函數中定義的 `this`並不相同.
    this.age++;
  }, 1000);
}

var p = new Person();

//在ECMAScript 3/5中,經過將this值分配給封閉的變量,能夠解決this問題。
function Person() {
  var that = this;
  that.age = 0;

  setInterval(function growUp() {
    //  回調引用的是`that`變量, 其值是預期的對象. 
    that.age++;
  }, 1000);
}

//箭頭函數不會建立本身的this,它只會從本身的做用域鏈的上一層繼承this。
function Person(){
  this.age = 0;

  setInterval(() => {
    this.age++; // |this| 正確地指向person 對象
  }, 1000);
}

var p = new Person();

經過 call 或 apply 調用

因爲 箭頭函數沒有本身的this指針,經過 call() 或 apply() 方法調用一個函數時,只能傳遞參數(不能綁定this---譯者注),他們的第一個參數會被忽略。(這種現象對於bind方法一樣成立---譯者注)

var adder = {
  base : 1,
    
  add : function(a) {
    var f = v => v + this.base;
    return f(a);
  },

  addThruCall: function(a) {
    var f = v => v + this.base;
    var b = {
      base : 2
    };
            
    return f.call(b, a);
  }
};

console.log(adder.add(1));         // 輸出 2
console.log(adder.addThruCall(1)); // 仍然輸出 2(而不是3 ——譯者注

不綁定arguments

var arguments = [1, 2, 3];
var arr = () => arguments[0];

arr(); // 1

function foo(n) {
  var f = () => arguments[0] + n; // 隱式綁定 foo 函數的 arguments 對象. arguments[0] 是 n
  return f();
}

foo(1); // 2

箭頭函數也可使用條件(三元)運算符:

var simple = a => a > 15 ? 15 : a;
simple(16); // 15
simple(10); // 10

let max = (a, b) => a > b ? a : b;

箭頭函數也可使用閉包:

// 標準的閉包函數
function A(){
      var i=0;
      return function b(){
              return (++i);
      };
};

var v=A();
v();    //1
v();    //2


//箭頭函數體的閉包( i=0 是默認參數)
var Add = (i=0) => {return (() => (++i) )};
var v = Add();
v();           //1
v();           //2

//由於僅有一個返回,return 及括號()也能夠省略
var Add = (i=0)=> ()=> (++i);

模板字符串

使用美圓符號和大括號包裹變量${對象名.屬性名}

es5:

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);
es6:

$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

若是使用模板字符串表示多行字符串,全部的空格和縮進都會被保留在輸出之中。

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`);

模板字符串中嵌入變量,須要將變量名寫在${}之中。(注:不聲明會報錯)

let name = 'jim',
     age = 18,
     gender = 'male';
console.log(`name : ${name} , age : ${age} , gender : ${gender}`)

大括號內部能夠放入任意的JavaScript表達式,能夠進行運算,以及引用對象屬性,並且還能調用函數

let x = 1,
    y = 2;
console.log(`${x} + ${y * 2} = ${x + y * 2}`)//  "1 + 4 = 5"


let obj = {x: 1, y: 2};
console.log(`${obj.x + obj.y}`)//  3


function fn() {
    return "Hello World";
        }
console.log(`foo ${fn()} bar`)// foo Hello World bar

模板字符串還能夠嵌套

const tmpl = addrs => `
  <table>
  ${addrs.map(addr => `
    <tr><td>${addr.first}</td></tr>
    <tr><td>${addr.last}</td></tr>
  `).join('')}
  </table>
`;
相關文章
相關標籤/搜索