分類
發燒車訊

一文梳理JavaScript中的this

最近零零碎碎看了許多關於this的文章,本着“好記性不如爛筆頭”的思想,特在這裏整理一下this有關的知識點。【長文警告!!!】

接下來,筆者將按照以下目錄對this進行闡述:

  • this是什麼?
  • this指向
    • this在全局範圍內
    • this在對象的構造函數內
    • this在對象的方法內
    • this在簡單函數內
    • this在箭頭函數內
    • this在一個事件偵聽器內
  • this綁定規則
    • 默認綁定
    • 隱式綁定
    • 显示綁定(this修改)
    • 優先級
  • 箭頭函數

1. this是什麼?

this是JavaScript的一個關鍵字,但它時常矇著面紗讓人無法捉摸,許多對this不明就裡的同學,常常會有這樣的錯誤認知:

  • this在函數內指向函數自身

    •   function foo(num){
            console.log("foo: " + num);
        
            //記錄foo被調用次數
            this.count++;
        }
        foo.count = 0;
        for(let i=0; i<10; i++){
            if(i > 5){
                foo(i);
            }
        }
        console.log(foo.count); // 0, this並沒有指向foo函數,foo.count沒有進行任何操作
      
  • this在函數內指向函數的作用域

    •   function foo(){
            var a = 2;
            this.bar();
        }
        function bar(){
            console.log(this.a);
        }
        foo();// undefined, window對象沒有bar這一屬性
      

2. this指向

this的指向取決於他所處的環境. 大致上,可以分為下面的6種情況:

  • this在全局範圍內
  • this在對象的構造函數內
  • this在對象的方法內
  • this在一個簡單的函數內
  • this在箭頭函數內
  • this在一個事件偵聽器內

2.1 this在全局範圍內

this在全局範圍內綁定什麼呢?這個相信只要學過JS,應該都知道答案。如果不知道,同學真的應該反思自己的學習態度和方法是否存在問題了。話不多說,直接上代碼,一探究竟,揭開this在全局範圍下的真面目:

console.log(this); // Window

不出意外,this在全局範圍內指向window對象()。通常, 在全局環境中, 我們很少使用this關鍵字, 因此對它也沒那麼在意. 讓我們繼續看下一個環境.

2.2 this在對象的構造函數內

當我們使用new創建構造函數的實例時會發生什麼呢?以這種方式調用構造函數會經歷以下四個步驟:

  • 創建一個空對象;

  • 將構造函數的作用域賦給新對象(this指向了這個新對象),繼承函數的原型;

  • 執行構造函數中的代碼;

  • 返回新對象。

看完上面的內容,大家想必也知道this在對象的構造函數內的指向了吧!當你使用new關鍵字創建一個對象的新的實例時, this關鍵字指向這個實例 .

舉個栗子:

function Human (age) {
    this.age = age;
}
let greg = new Human(22);
let thomas = new Human(24);

console.log(greg); // this.age = 22
console.log(thomas); // this.age = 24

// answer
Person { age:22}
Person { age:24}

2.3 this在對象方法內

方法是與對象關聯的函數的通俗叫法, 如下所示:

let o = {
    sayThis(){
        console.log(this);
    }
}

如上所示,在對象的任何方法內的this都是指向對象本身 .

好了,繼續下一個環境!

2.4 this在簡單函數內

可能看到這裏,許多同學心裏會有疑問,什麼是簡單函數?

其實簡單函數大家都很熟悉,就像下面一樣,以相同形式編寫的匿名函數也被認為是簡單函數(非箭頭函數)。

function hello(){
    console.log("hello"+this);
}

這裏需要注意,在瀏覽器中,不管函數聲明在哪裡,匿名或者不匿名,只要不是直接作為對象的方法,this指向始終是window對象(除非使用call,apply,bind修改this指向)。

舉個栗子說明一下:

// 显示函數,直接定義在sayThis方法內,this指向依舊不變
function simpleFunction() {
    console.log(this);
}

var o = {
    sayThis() {
        simpleFunction();
    }
}

simpleFunction(); // Window
o.sayThis(); // Window


// 匿名函數
var o = {
    sayThis(){
        (function(){consoloe.log(this);})();
    }
} 
o.sayThis();// Window

對於初學者來說,this在簡單函數內的表現時常讓他們懵逼不已,難道this不應該指向對象本身?這個問題曾經也出現在我的腦海里過,沒錯,在寫代碼時我也踩過這個坑。

通常的,當我們要在對象方法內調用函數,而這個函數需要用到this時,我們都會創建一個變量來保存對象中的this的引用. 通常, 這個變量名稱叫做self或者that。具體說下所示:

const o = {
    doSomethingLater() {
        const self = this;
        setTimeout(function() {
            self.speakLeet();
        }, 1000);
    },
    speakLeet() {
        console.log(`1337 15 4W350M3`);
    }
}

o.doSomethingLater(); // `1337 15 4W350M3`

心細的同學可能已經發現,這裏的簡單函數沒有將箭頭函數包括在內,那麼下一個環境是什麼想必也能猜到啦,那麼現在進入下一個環境,看看this指向什麼。

2.5 this在箭頭函數內

和簡單函數表現不太一樣,this在箭頭函數中總是跟它在箭頭函數所在作用域的this一樣(在它直接作用域). 所以, 如果你在對象中使用箭頭函數, 箭頭函數中的this總是指向這個對象本身, 而不是指向Window.

下面我們使用箭頭函數,重寫一下上面的案例:

const o = {
    doSomethingLater() {
        setTimeout(() => this.speakLeet(), 1000);
    },
    speakLeet() {
        console.log(`1337 15 4W350M3`);
    }
}
o.doSomethingLater(); // `1337 15 4W350M3`

最後,讓我們來看看最後一種環境 – 事件偵聽器.

2.6 this在事件偵聽器內

在事件偵聽器內, this被綁定的是觸發這個事件的元素:

let button = document.querySelector('button');

button.addEventListener('click', function() {
    console.log(this); // button
});

3. this綁定規則

事實上,只要記住上面this在不同環境的綁定值,足以應付大部分工作。然而,好學的同學總是會忍不住想說,為什麼呢?對,為什麼this在這些情況下綁定這些值呢?學習,我們不能只知其然,而不知所以然。所以,現在就讓我們來探尋,this值獲取的真相吧。

現在,讓我們回憶一下,在講什麼是this的時候,我們說到“this的綁定取決於他所處的環境”。這句話其實不是十分準確,準確的說,this不是編寫時綁定,而是運行時綁定。它依賴於函數調用的上下文條件this綁定和函數聲明的位置無關,反而和函數被調用的方式有關

當一個函數被調用時,會建立一個活動記錄,也稱為執行環境。這個記錄包含函數是從何處(call-stack)被調用的,函數是 如何被調用的,被傳遞了什麼參數等信息。這個記錄的屬性之一,就是在函數執行期間將被使用的this引用。this實際上是在函數被調用時建立的一個綁定,它指向什麼是完全由函數被調用的調用點來決定的

僅僅是規則

現在我們將注意力轉移到調用點 如何 決定在函數執行期間this指向哪裡。

你必須考察call-site並判定4種規則中的哪一個適用。我們將首先獨立的解釋一下這4種規則中的每一種,之後我們來展示一下如果有多種規則可以適用調用點時,它們的優先級。

3.1 默認綁定規則

第一種規則來源於函數調用的最常見的情況:獨立函數調用。可以認為這種this規則是在沒有其他規則適用時的默認規則。我們給它一個稱呼“默認綁定”.

現在來看這段代碼:

function foo(){
    console.log(this); 
}
var a = 2;
demo(); // 2

當foo()被調用時,this.a解析為我們的全局變量a。為什麼?因為在這種情況下,對此方法調用的this實施了 默認綁定,所以使this指向了全局對象。

在我們的代碼段中,foo()是被一個直白的,毫無修飾的函數引用調用的。沒有其他的我們將要展示的規則適用於這裏,所以 默認綁定 在這裏適用。

如果strict mode在這裏生效,那麼對於 默認綁定 來說全局對象是不合法的,所以this將被設置為undefined。

'use strict'
function foo(){
    console.log(this.a); // TypeError: Cannot read property 'a' of undefined
}
const a = 1;
foo();
function foo(){
	'use strict'
    console.log(this.a); // TypeError: Cannot read property 'a' of undefined
}
const a = 1;
foo();

微妙的是,即便所有的this綁定規則都是完全基於調用點,如果foo()的 內容 沒有在strint mode下執行,對於 默認綁定 來說全局對象是 唯一 合法的;foo()的call-site的strict mode狀態與此無關。

function foo(){
    console.log(this.a); 
}
var a = 1;
(function(){
	'use strict';
	foo(); // 1
})();

注意: 在代碼中故意混用strict mode和非strict mode通常是讓人皺眉頭的。你的程序整體可能應當不是 Strict 就是非Strict。然而,有時你可能會引用與你的 Strict 模式不同的第三方包,所以對這些微妙的兼容性細節要多加小心。

3.2 隱式綁定

另一種要考慮的規則是:調用點是否有一個環境對象(context object),也稱為擁有者(owning)或容器(containing)對象。

讓我們來看這段代碼:

function foo() {
    console.log(this.a);
}
let o = {
    a: 2,
    foo,
}
o.foo(); // 2

這裏,我們注意到foo函數被聲明然後作為對象o的方法,無論foo()是否一開始就在obj上被聲明,還是後來作為引用添加(如上面代碼所示),都是這個 函數 被obj所“擁有”或“包含”。這裏,調用點使用obj環境來引用函數,所以可以說 obj對象在函數被調用的時間點上“擁有”或“包含”這個 函數引用。

當一個方法引用存在一個環境對象時,隱式綁定 規則會說:是這個對象應當被用於這個函數調用的this綁定。

只有對象屬性引用鏈的最後一層是影響調用點的。比如:

function foo(){
    console.log(this.a);
}

var obj1 = {
    a:2,
    obj2:obj2
};
var obj2 = {
    a:42,
    foo:foo
};
obj1.obj2.foo(); // 42

隱式綁定的隱患

當一個 隱含綁定丟失了它的綁定,這通常意味着它會退回到 默認綁定, 根據strict mode的狀態,結果不是全局對象就是undefined。

下面來看這段代碼:

function foo(){
    console.log(this.a);
}

var obj = {
    a:2,
    foo
};
var bar = obj.foo;
var a = "Global variable";
bar(); // "Global variable"

儘管bar似乎是obj.foo的引用,但實際上它只是另一個foo自己的引用而已。另外,起作用的調用點是bar(),一個直白,毫無修飾的調用,因此 默認綁定 適用於這裏。

這種情況發生的更加微妙,更常見,更意外的方式,是當我們考慮傳遞一個回調函數時:

function foo(){
    console.log(this.a);
}

function doFoo(fn){
	fn();
}

var obj = {
    a:2,
    foo,
};
var a = "Global variable";
dooFoo(obj.foo); // "Global variable"

參數傳遞僅僅是一種隱含的賦值,而且因為我們在傳遞一個函數,它是一個隱含的引用賦值,所以最終結果和我們前一個代碼段一樣。同樣的,語言內建,如setTimeout也一樣,如下所示

function foo(){
    console.log(this.a);
}

var obj = {
    a:2,
    foo,
};
var a = "Global variable";
setTimeout(obj.foo, 100); // "Global variable"

把這個粗糙的setTimeout()假想實現當做JavaScript環境內建的實現的話:

function setTimeout(fn, delay){
    // 等待delay毫秒
    fn();
}

正如我們看到的, 隱含綁定丟失了它的綁定是十分常見的,不管哪一種意外改變this的方式,你都不能真正地控制你的回調函數引用將如何被執行,所以你(還)沒有辦法控制調用點給你一個故意的綁定。但是我們可以使用显示綁定強行固定this。

3.3 显示綁定

我們看到隱含綁定,需要我們不得不改變目標對象使它自身包含一個對函數的引用,而後使用這個函數引用屬性來間接地(隱含地)將this綁定到這個對象上。

但是,如果你想強制一個函數調用使用某個特定對象作為this綁定,而不在這個對象上放置一個函數引用屬性呢?

js有提供call()、apply()方法,ES5中也提供了內置的方法 Function.prototype.bind,可以引用一個對象時進行強制綁定調用。

考慮這段代碼:

function foo(){
    console.log(this.a);
}
var obj = {
    a:2,
};
foo.call(obj); // 2

通過foo.call(..)使用 明確綁定 來調用foo,允許我們強制函數的this指向obj。

如果你傳遞一個簡單原始類型值(string,boolean,或 number類型)作為this綁定,那麼這個原始類型值會被包裝在它的對象類型中(分別是new String(..),new Boolean(..),或new Number(..))。這通常稱為“boxing(封箱)”。

注意: 就this綁定的角度講,call(..)和apply(..)是完全一樣的。它們確實在處理其他參數上的方式不同,但那不是我們當前關心的。

單獨依靠call和apply,仍然可能出現函數“丟失”自己原本的this綁定,或者被第三方覆蓋等問題。

但有一個技巧可以避免出現這些問題

考慮這段代碼:

function foo(){
    console.log(this.a);
}
var obj = {
	a:2
};
var bar = function(){
	foo.call(obj);
}
bar(); // 2
setTimeout(bar, 100); // 2
bar.call(window); // 2

我們創建了一個函數bar(),在它的內部手動調用foo.call(obj),由此強制this綁定到obj並調用foo。無論你過後怎樣調用函數bar,它總是手動使用obj調用foo。這種綁定即明確又堅定,該方法被開發者稱為 硬綁定(显示綁定的變種)(hard binding)

用硬綁定將一個函數包裝起來的最典型的方法,是為所有傳入的參數和傳出的返回值創建一個通道:

function foo(something){
    console.log(this.a, something);
    return this.a + something;
}
var obj = {
    a:2
};
var bar = function() {
    return foo.apply(obj, arguments);
}
var b = bar(3);
console.log(b); //  5

另一種表達這種模式的方法是創建一個可復用的幫助函數:

function foo(something){
    console.log(this.a, something);
    return this.a + something;
}

function bind(fn, obj){
    return function(){
        return fn.apply(obj, arguments);
    };
}

var obj = { a:2};
var bar = bind(foo, obj);
var b = bar(3);
console.log(b); // 5

由於 硬綁定 是一個如此常用的模式,它已作為ES5的內建工具提供,即前文提到的Function.prototype.bind:

function foo(something){
    console.log(this.a, something);
    return this.a + something;
}
var obj = { a:2};
var bar = foo.bind(obj);
var b = bar();
cobsole.log(b); // 5

bind(..)返回一個硬編碼的新函數,它使用你指定的this環境來調用原本的函數。

注意: 在ES6中,bind(..)生成的硬綁定函數有一個名為.name的屬性,它源自於原始的 目標函數(target function)。舉例來說:bar = foo.bind(..)應該會有一個bar.name屬性,它的值為”bound foo”,這個值應當會显示在調用棧軌跡的函數調用名稱中。

3.4new 綁定

第四種也是最後一種this綁定規則

當在函數前面被加入new調用時,也就是構造器調用時,下面這些事情會自動完成:

  • 一個全新的對象會憑空創建(就是被構建)
  • 這個新構建的對象會被接入原形鏈([[Prototype]]-linked)
  • 這個新構建的對象被設置為函數調用的this綁定
  • 除非函數返回一個它自己的其他 對象,這個被new調用的函數將 自動 返回這個新構建的對象。

考慮這段代碼:

function foo(a){
    console.log(this.a);
}
var bar = new foo(2);
console.log(bar.a); // 2

通過在前面使用new來調用foo(..),我們構建了一個新的對象並這個新對象作為foo(..)調用的this。 new是函數調用可以綁定this的最後一種方式,我們稱之為 new綁定(new binding)。

3.5 優先級

  • new綁定
  • 显示綁定
  • 隱式綁定
  • 默認綁定(嚴格模式下會綁定到undefined)

4. 箭頭函數

箭頭函數並非使用function關鍵字進行定義,而是通過所謂的“大箭頭”操作符:=>,所以不會使用上面所講解的this四種標準規範,箭頭函數從封閉它的(function或global)作用域採用this綁定,即箭頭函數會繼承自外層函數調用的this綁定。

執行 fruit.call(apple)時,箭頭函數this已被綁定,無法再次被修改。

function fruit(){
    return () => {
        console.log(this.name);
    }
}
var apple = {
    name: '蘋果'
}
var banana = {
    name: '香蕉'
}
var fruitCall = fruit.call(apple);
fruitCall.call(banana); // 蘋果

5. 小結

this是JavaScript的一個關鍵字,this不是編寫時綁定,而是運行時綁定。它依賴於函數調用的上下文條件。this綁定和函數聲明的位置無關,反而和函數被調用的方式有關。為執行中的函數判定this綁定需要找到這個函數的直接調用點。找到之後,4種規則將會以 這個 優先順序施用於調用點:

  • 被new調用?使用新構建的對象。
  • 被call或apply(或 bind)調用?使用指定的對象。
  • 被持有調用的環境對象調用?使用那個環境對象。
  • 默認:strict mode下是undefined,否則就是全局對

與這4種綁定規則不同,ES6的箭頭方法使用詞法作用域來決定this綁定,這意味着它們採用封閉他們的函數調用作為this綁定(無論它是什麼)。它們實質上是ES6之前的self = this代碼的語法替代品。

參考文章:

深入理解JavScript中的this

詳解JavaScript中的this

你不懂this:豁然開朗

你不懂this:this是什麼?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

MOJITO 發布一周,爬一波彈幕分析下

MOJITO

最近一直啥都沒寫,追個熱點都趕不上熱乎的,鄙視自己一下。

周董的新歌 「MOJITO」 發售(6 月 12 日的零點)至今大致過去了一周,翻開 B 站 MV 一看,播放量妥妥破千萬,彈幕破十萬,這人氣還真是杠杠的。

說實話, 「MOJITO」 這個名字對我來講有點超綱了,第一次見到完全不知道啥意思。

不過問題不大,沒有什麼是百度解決不了的,如果有,那就再加一個知乎。

MOJITO 的中文名是莫吉托,百度百科上是這麼介紹莫吉托的:

莫吉托(Mojito)是最有名的朗姆調酒之一。起源於古巴。傳統上,莫吉托是一種由五種材料製成的雞尾酒:淡朗姆酒、糖(傳統上是用甘蔗汁)、萊姆(青檸)汁、蘇打水和薄荷。最原始的古巴配方是使用留蘭香或古巴島上常見的檸檬薄荷。萊姆(青檸)與薄荷的清爽口味是為了與朗姆酒的烈性相互補,同時也使得這種透明無色的調酒成為夏日的熱門飲料之一。這種調酒有着相對低的酒精含量(大約10%)。

酒精度數在 10% 左右的話,姑且可以認為一種飲料吧。

當然,如果要開車的話就不能把 MOJITO 當成飲料了,酒精含量再低那也是酒精。

整個 MV 我翻來覆去的看了好幾遍, 「MOJITO」 這個東西除了在歌詞和名字中有出現,在 MV 當中一次都沒出現,毫無存在感。

爬取 B 站彈幕

彈幕數據的爬取比較簡單,我就不一步一步的抓請求給各位演示了,注意下面這幾個請求連接:

彈幕請求地址:

https://api.bilibili.com/x/v1/dm/list.so?oid=XXX

https://comment.bilibili.com/XXX.xml

第一個地址由於 B 站的網頁做了更換,現在在 Chrome 工具的 network 裏面已經找不到了,不過還可以用,這個是我之前找到的。

第二個地址來源於百度,我也不知道各路大神是從哪找出來這個地址的,供參考吧。

上面這兩個彈幕地址實際上都需要一個叫 oid 的東西,這個 oid 獲取方式如下:

首先可以找到一個目錄頁接口:

https://api.bilibili.com/x/player/pagelist?bvid=XXX&jsonp=jsonp

這個接口也是來源於 Chrome 的 network ,其中 bvid 這個參數來源於視頻地址,比如周董的這個 「MOJITO」 的 MV ,地址是 https://www.bilibili.com/video/BV1PK4y1b7dt ,那麼這個 bvid 的值就是最後那一部分 BV1PK4y1b7dt

接下來在 https://api.bilibili.com/x/player/pagelist?bvid=BV1PK4y1b7dt&jsonp=jsonp 這個接口中,我們可以看到返回的 json 參數,如下:

{
    "code":0,
    "message":"0",
    "ttl":1,
    "data":[
        {
            "cid":201056987,
            "page":1,
            "from":"vupload",
            "part":"JAY-MOJITO_完整MV(更新版)",
            "duration":189,
            "vid":"",
            "weblink":"",
            "dimension":{
                "width":1920,
                "height":1080,
                "rotate":0
            }
        }
    ]
}

注意:由於這個 MV 只有一個完整的視頻,所以這裏只有一個 cid ,如果一個視頻是分不同小節發布的,這裏就會有多個 cid ,不同的 cid 代表不同的視頻。

當然,這裏的 cid 就是我們剛才想找的那個 oid ,把這個 cid 拼到剛才的鏈接上,可以得到 https://api.bilibili.com/x/v1/dm/list.so?oid=201056987 這樣一個地址,然後輸入到瀏覽器中,可以看到彈幕的返回數據,是一個 xml 格式的文本。

源代碼如下:

import requests
import re

# 獲取 cid
res = requests.get("https://api.bilibili.com/x/player/pagelist?bvid=BV1PK4y1b7dt&jsonp=jsonp")
cid = res.json()['data'][0]['cid']

# 將彈幕 xml 通過正則取出,生成 list
danmu_url = f"https://api.bilibili.com/x/v1/dm/list.so?oid={cid}"
result = requests.get(danmu_url).content.decode('utf-8')
pattern = re.compile('<d.*?>(.*?)</d>')
danmu_list = pattern.findall(result)

# 將彈幕 list 保存至 txt 文件
with open("dan_mu.txt", mode="w", encoding="utf-8") as f:
    for item in danmu_list:
        f.write(item)
        f.write("\n")

這裏我將獲取到的彈幕保存在了 dan_mu.txt 文件中,方便後續分析。

繪製詞雲圖

第一步先將剛才保存在 dan_mu.txt 文件中的彈幕讀取出來,放到了一個 list 當中:

# 讀取彈幕 txt 文件
with open("dan_mu.txt", encoding="utf-8") as f:
    txt = f.read()
danmu_list = txt.split("\n")

然後使用分詞工具對彈幕進行分詞,我這裏使用的分詞工具是最好的 Python 中文分詞組件 jieba ,沒有安裝過 jieba 的同學可以使用以下命令進行安裝:

pip install jieba

使用 jieba 對剛才獲得的彈幕 list 進行分詞:

# jieba 分詞
danmu_cut = [jieba.lcut(item) for item in danmu_list]

這樣,我們獲得了分詞后的 danmu_cut ,這個同樣是一個 list 。

接着我們對分詞后的 danmu_cut 進行下一項操作,去除停用詞:

# 獲取停用詞
with open("baidu_stopwords.txt",encoding="utf-8") as f:
    stop = f.read()
stop_words = stop.split()

# 去掉停用詞后的最終詞
s_data_cut = pd.Series(danmu_cut)
all_words_after = s_data_cut.apply(lambda x:[i for i in x if i not in stop])

這裏我引入了一個 baidu_stopwords.txt 文件,這個文件是百度停用詞庫,這裏我找到了幾個常用的中文停用詞庫,來源: https://github.com/goto456/stopwords 。

詞表文件 詞表名
baidu_stopwords.txt 百度停用詞表
hit_stopwords.txt 哈工大停用詞表
scu_stopwords.txt 四川大學機器智能實驗室停用詞庫
cn_stopwords.txt 中文停用詞表

這裏我使用的是百度停用詞表,大家可以根據自己的需要使用,也可以對這幾個停用詞表先做整合后再使用,主要的目的就是去除一些無需關注的詞,上面這幾個停用詞庫我都會提交到代碼倉庫,有需要的自取。

接着我們統計去除停用詞后的詞頻:

# 詞頻統計
all_words = []
for i in all_words_after:
    all_words.extend(i)
word_count = pd.Series(all_words).value_counts()

最後一步就是生成我們的最終結果,詞雲圖:

wordcloud.WordCloud(
    font_path='msyh.ttc',
    background_color="#fff",
    max_words=1000,
    max_font_size=200,
    random_state=42,
    width=900,
    height=1600
).fit_words(word_count).to_file("wordcloud.png")

最終結果就是下面這個:

從上面這個詞雲圖中可以看到,粉絲對「MOJITO」這首歌是真愛啊,出現頻率最高的就是 啊啊啊 還有

當然哈,這個 也有可能是說 MV 當中那台騷氣十足的粉色的老爺車。

還有一個出現頻率比較高的是 爺青回 ,我估計這個意思應該是 爺的青春回來啦 ,確實,周董伴隨着我這個年齡段的人一路走來,做為一位 79 年的人現在已經是 41 歲的「高齡」了,回首往昔,讓人唏噓不已。

當年一首 「雙節棍」 火遍了中華大地,大街上的音像店整天都在循環這幾首歌,在學校上學的我這一代人,基本上是人人都能哼兩句,「快使用雙截棍,哼哼哈嘿」成了我們這一代人共有的回憶。

智能情感傾向分析

我們還可以對彈幕進行一次情感傾向分析,這裏我使用的是 「百度 AI 開放平台」 的情感傾向分析接口。

百度 AI 開放平台文檔地址:https://ai.baidu.com/ai-doc/NLP/zk6z52hds

首先是根據文檔接入 「百度 AI 開放平台」 ,獲取 access_token ,代碼如下:

# 獲取 Baidu API access_token
access_token_url = f'https://aip.baidubce.com/oauth/2.0/token?grant_type={grant_type}&client_id={client_id}&client_secret={client_secret}&'

res = requests.post(access_token_url)

access_token = res.json()['access_token']

# 通用情感接口
# sentiment_url = f'https://aip.baidubce.com/rpc/2.0/nlp/v1/sentiment_classify?charset=UTF-8&access_token={access_token}'
# 定製化情感接口
sentiment_url = f'https://aip.baidubce.com/rpc/2.0/nlp/v1/sentiment_classify_custom?charset=UTF-8&access_token={access_token}'

百度 AI 開放平台有兩個情感分析接口,一個是通用的,還有一個是定製化的,我這裏使用的是經過訓練的定製化的接口,如果沒有定製化的接口,使用通用的接口也沒有問題。

上面使用到的 grant_typeclient_idclient_secret 這幾個參數,大家註冊一下就能得到, 「百度 AI 開放平台」 上的這些接口都有調用數量的限制,不過我們自己使用已經足夠了。

然後讀取我們剛才保存的彈幕文本:

with open("dan_mu.txt", encoding="utf-8") as f:
    txt = f.read()
danmu_cat = txt.split("\n")

在調用接口獲得情感傾向之前,我們還需要做一件事情,對彈幕進行一次處理,因為彈幕中會有一些 emoji 表情,而 emoji 直接請求百度的接口會返回錯誤,這裏我使用另一個工具包對 emoji 表情進行處理。

首先安裝工具包 emoji :

pip install emoji

使用是非常簡單的,我們對彈幕數據使用 emoji 進行一次處理:

import emoji

with open("dan_mu.txt", encoding="utf-8") as f:
    txt = f.read()
danmu_list = txt.split("\n")

for item in danmu_list:
    print(emoji.demojize(item))

我們的彈幕數據中是有這樣的 emoji 表情的:



# 處理后:
:red_heart::red_heart::red_heart::red_heart::red_heart::red_heart::red_heart:

然後,我們就可以調用百度的情感傾向分析接口,對我們的彈幕數據進行分析了:

# 情感計數器
optimistic = 0
neutral = 0
pessimistic = 0

for danmu in danmu_list:
    # 因調用 QPS 限制,每次調用間隔 0.5s
    time.sleep(0.5)
    req_data = {
        'text': emoji.demojize(danmu)
    }
    # 調用情感傾向分析接口
    if len(danmu) > 0:
        r = requests.post(sentiment_url, json = req_data)
        print(r.json())
        for item in r.json()['items']:
            if item['sentiment'] == 2:
                # 正向情感
                optimistic += 1
            if item['sentiment'] == 1:
                # 中性情感
                neutral += 1
            if item['sentiment'] == 0:
                # 負向情感
                pessimistic += 1

print('正向情感:', optimistic)
print('中性情感:', neutral)
print('負向情感:', pessimistic)

attr = ['正向情感','中性情感','負向情感']
value = [optimistic, neutral, pessimistic]

c = (
    Pie()
    .add("", [list(attr) for attr in zip(attr, value)])
    .set_global_opts(title_opts=opts.TitleOpts(title="「MOJITO」彈幕情感分析"))
    .render("pie_base.html")
)

最後的結果圖長這樣:

從最後的結果上來看,正向情感佔比大約在 2/3 左右,而負向情感只有不到 1/4 ,看來大多數人看到周董的新歌還是滿懷激動的心情。

不過這個數據不一定準確,最多可以做一個參考。

源代碼

需要源代碼的同學可以在公眾號後台回復「MOJITO」獲取。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

程泰打入Tesla Model 3供應鏈,預計年底出貨

程泰16日參加宏遠證券所舉辦自動化產業投資論壇,對於2017年營運展望,程泰表示,目前母公司程泰集團在手訂單已回升至5億多元,且未含3月台北國際工具機展部分,同時在中國大陸訂單約有2-3億元,整體而言,目前整體訂單能見度看到第二季底。程泰集團董事長楊德華則表示,集團整體在手訂單規模約22億至23億元,至於3月台北國際工具機展接單規模約在3億至5億元。

程泰也指出,公司業務以車床為主,由於波音預期未來20年全球航太市場規模將達180兆元,預估民航機需求約3.8萬架,加上政府要推國機國造,因此未來也會聚焦在航太產業。此外,在電動車布局方面,程泰打入美國特斯拉電動車Model 3供應鏈,於台中大里工業區建立代工基地,預計今年底起,每月將出貨減速齒輪箱的齒輪傳動軸前端車銑磨6萬套。

程泰集團旗下包括程泰及亞崴兩工具機廠,亞崴表示,農曆年後,台灣航太及汽車零組件業開始投資擴廠,加上推進國機國造及航太零件國產化,預期今年台灣市場會有不錯表現,目前台灣地區在手訂單14億元,大陸吳江及嘉興廠在手訂單3-4億元,兩岸合併在手訂單約18億元,訂單能見度看到7月;在產能布局方面,嘉義大埔美一期新廠預計今年底完工,大陸吳江廠亦規劃最快於今年底興建二期廠。

(本文內容由授權使用。圖片出處:Tesla)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

分類
發燒車訊

擴電動車版圖,鴻海投資中國鋰電池廠

鴻海自2014開始將旗下事業版圖擴大至電動車領域,3月29日公告於深圳的子公司富泰華工業以44.7億新台幣買下中國寧德時代新能源1.19%之股權,共擁有7,666,525股。

母公司為鋰電池製造商新能源科技(ATL),寧德時代(CATL)主要研發生產電池芯、電池管理系統和動力電池系統,產品應用於電動車及儲能領域。

鴻海董事長郭台銘事業瞄準電動車市場,繼2015年旗下富士康同騰訊及和諧汽車,三方合資成立和諧富騰以生產智慧電動車之後,和諧富騰於2016又投資成立初創公司Future Mobility Corporation(FMC),瞄準高階智慧電動車。

FMC於2017年1月宣布將斥資116億元人民幣,於南京建高端智能電動車廠,第一期工程預計2019年可以完成。關於此次寧德時代的投資,董事長郭台銘表示為長期投資。

(首圖來源:寧德時代)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

積極拓電動車市場,Hyundai推EV車用平台

 

特斯拉(Tesla)電動車成功引領話題,各大車廠跟紛紛仿效,搶攻此一市場。原本力推燃料電池車的韓國車廠–現代汽車(Hyundai Motor),眼看市場風向轉變,也宣布要研發該公司首款電動車專屬的車用平台(car platform)。

路透社30日報導,車用平台意指外觀不同的汽車共享相通的設計、工程、生產流程,以及主要零件等,能夠壓低研發成本。現代汽車之前力推燃料電池車,如今轉攻電池車,突顯投資人施壓,要求該公司積極進軍新市場。

現代汽車的電動車平台,電池將位於汽車底部,以便容納大容量電池,並讓車內空間最大化。現代-起亞環保車主管Lee Ki-sang說,電動車平台初期投資費用高,但是他們必須替未來做準備。分析師說現代別無選擇,必須追隨特斯拉、通用汽車、Daimler AG旗下的賓士(Mercedes-Benz),打造單獨的電動車平台,才能留在此一市場。

Hi Investment & Securities分析師Ko Tae-bong表示,單獨平台初始時可能會造成虧損,但是現代若不研發長程電動車將落後對手,例如300、500、600公里車款。

電動車大廠特斯拉(Tesla Inc.)要價35,000美元的平價車種「Model 3」預計2017年稍晚就能開賣,摩根士丹利(通稱大摩)認為,這款電動車的安全度會是一般車輛的10倍之多,發生死亡車禍的機率有望比其他車種低90%。

MarketWatch、Business Insider等外電報導,大摩分析師Adam Jonas 23日發表研究報告指出,特斯拉為每台車安裝超級電腦後,車子安全性提升至其他車輛兩倍已經不夠看,他相信Model 3的安全度會是其他車輛的十倍之多,這會讓死亡車禍的發生機會降低90%。

Jonas認為,缺少特斯拉駕車輔助科技的二手車價值將因而猛掉,未來甚至會被禁止上路。特斯拉蒐集資料的能力超群,還能將先進的安全輔助技術應用到電動車,還未推出類似科技的傳統車廠,競爭力堪虞。

(本文內容由授權使用。圖片出處:Hyundai)  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

這一次搞懂Spring Web零xml配置原理以及父子容器關係

前言

在使用Spring和SpringMVC的老版本進行開發時,我們需要配置很多的xml文件,非常的繁瑣,總是讓用戶自行選擇配置也是非常不好的。基於約定大於配置的規定,Spring提供了很多註解幫助我們簡化了大量的xml配置;但是在使用SpringMVC時,我們還會使用到WEB-INF/web.xml,但實際上我們是完全可以使用Java類來取代xml配置的,這也是後來SpringBoott的實現原理。本篇就來看看Spring是如何實現完全的零XML配置。

正文

先來看一下原始的web.xml配置:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      <!--加載spring配置-->
      classpath:spring.xml
    </param-value>
  </context-param>
  <context-param>
    <param-name>webAppRootKey</param-name>
    <param-value>ServicePlatform.root</param-value>
  </context-param>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    <!--<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>-->
  </listener>

  <servlet>
    <servlet-name>spring-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <!--springmvc的配置文件-->
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-dispatcher.xml</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>spring-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

這裏各個配置的作用簡單說下,context-param是加載我們主的sping.xml配置,比如一些bean的配置和開啟註解掃描等;listener是配置監聽器,Tomcat啟動會觸發監聽器調用;servlet則是配置我們自定義的Servlet實現,比如DispatcherServlet。還有其它很多配置就不一一說明了,在這裏主要看到記住context-paramservlet配置,這是SpringIOC父子容器的體現。在之前的I文章中講過IOC容器是以父子關係組織的,但估計大部分人都不能理解,除了看到複雜的繼承體系,並沒有看到父容器作用的體現,稍後來分析。
了解了配置,我們就需要思考如何替換掉這些繁瑣的配置。實際上Tomcat提供了一個規範,有一個ServletContainerInitializer接口:

public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}

Tomcat啟動時會調用該接口實現類的onStartup方法,這個方法有兩個參數,第二個不用說,主要是第一個參數什麼?從哪裡來?另外我們自定義的實現類又怎麼讓Tomcat調用呢?
首先解答最後一個問題,這裏也是利用SPI來實現的,因此我們實現了該接口后,還需要在META-INF.services下配置。其次,這裏傳入的第一個參數也是我們自定義的擴展接口的實現類,我們可以通過我們自定義的接口實現很多需要在啟動時做的事,比如加載Servlet,但是Tomcat又是怎麼知道我們自定義的接口是哪個呢?這就需要用到@HandlesTypes註解,該註解就是標註在ServletContainerInitializer的實現類上,其值就是我們擴展的接口,這樣Tomcat就知道需要傳入哪個接口實現類到這個onStartup方法了。來看一個簡單的實現:

@HandlesTypes(LoadServlet.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        Iterator var4;
        if (set != null) {
            var4 = set.iterator();
            while (var4.hasNext()) {
                Class<?> clazz = (Class<?>) var4.next();
                if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && LoadServlet.class.isAssignableFrom(clazz)) {
                    try {
                        ((LoadServlet) clazz.newInstance()).loadOnstarp(servletContext);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

public interface LoadServlet {

    void loadOnstarp(ServletContext servletContext);
}

public class LoadServletImpl implements LoadServlet {
    @Override
    public void loadOnstarp(ServletContext servletContext) {
        ServletRegistration.Dynamic initServlet = servletContext.addServlet("initServlet", "org.springframework.web.servlet.DispatcherServlet");
        initServlet.setLoadOnStartup(1);
        initServlet.addMapping("/init");
	}
}

這就是Tomcat給我們提供的規範,通過這個規範我們就能實現Spring的零xml配置啟動,直接來看Spring是如何做的。
根據上面所說我們可以在spring-web工程下找到META-INF/services/javax.servlet.ServletContainerInitializer配置:

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

核心的實現就是WebApplicationInitializer,先看看其繼承體系

AbstractReactiveWebInitializer不用管,主要看另外一邊,但是都是抽象類,也就是說真的實例也是由我們自己實現,但需要我們實現什麼呢?我們一般直接繼承AbstractAnnotationConfigDispatcherServletInitializer類,有四個抽象方法需要我們實現:

    //父容器
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{SpringContainer.class};
    }

    //SpringMVC配置子容器
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{MvcContainer.class};
    }

    //獲取DispatcherServlet的映射信息
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

	// filter配置
    @Override
    protected Filter[] getServletFilters() {
        MyFilter myFilter = new MyFilter();
        CorsFilter corsFilter = new CorsFilter();
        return new Filter[]{myFilter,corsFilter};
    }

這裏主要注意getRootConfigClassesgetServletConfigClasses方法,分別加載父、子容器:

@ComponentScan(value = "com.dark",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class SpringContainer {
}

@ComponentScan(value = "com.dark",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
public class MvcContainer {
}

看到這兩個類上的註解應該不陌生了吧,父容器掃描裝載了所有不帶@Controller註解的類,子容器則相反,但需要對象時首先從當前容器中找,如果沒有則從父容器中獲取,為什麼要這麼設計呢?直接放到一個容器中不行么?先思考下, 稍後解答。
回到onStartup方法中,直接回調用到AbstractDispatcherServletInitializer類:

	public void onStartup(ServletContext servletContext) throws ServletException {
		super.onStartup(servletContext);
		//註冊DispatcherServlet
		registerDispatcherServlet(servletContext);
	}

先是調用父類:

	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}

	protected void registerContextLoaderListener(ServletContext servletContext) {

		//創建spring上下文,註冊了SpringContainer
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
			//創建監聽器
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			listener.setContextInitializers(getRootApplicationContextInitializers());
			servletContext.addListener(listener);
		}
	}

然後調用createRootApplicationContext創建父容器:

	protected WebApplicationContext createRootApplicationContext() {
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
			context.register(configClasses);
			return context;
		}
		else {
			return null;
		}
	}

可以看到就是創建了一個AnnotationConfigWebApplicationContext對象,並將我們的配置類SpringContainer註冊了進去。接着創建Tomcat啟動加載監聽器ContextLoaderListener,該監聽器有一個contextInitialized方法,會在Tomcat啟動時調用。

	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

	 */
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		long startTime = System.currentTimeMillis();
		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			return this.context;
		}
	}

可以看到就是去初始化容器,這個和之前分析xml解析是一樣的,主要注意這裏封裝了ServletContext對象,並將父容器設置到了該對象中。
父容器創建完成后自然就是子容器的創建,來到registerDispatcherServlet方法:

	protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");

		//創建springmvc的上下文,註冊了MvcContainer類
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

		//創建DispatcherServlet
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
					"Check if there is another servlet registered under the same name.");
		}

		/*
		* 如果該元素的值為負數或者沒有設置,則容器會當Servlet被請求時再加載。
			如果值為正整數或者0時,表示容器在應用啟動時就加載並初始化這個servlet,
			值越小,servlet的優先級越高,就越先被加載
		* */
		registration.setLoadOnStartup(1);
		registration.addMapping(getServletMappings());
		registration.setAsyncSupported(isAsyncSupported());

		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}

		customizeRegistration(registration);
	}

	protected WebApplicationContext createServletApplicationContext() {
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			context.register(configClasses);
		}
		return context;
	}

這裏也是創建了一個AnnotationConfigWebApplicationContext對象,不同的只是這裏註冊的配置類就是我們的Servlet配置了。然後創建了DispatcherServlet對象,並將上下文對象設置了進去。看到這你可能會疑惑,既然父子容器創建的都是相同類的對象,何來的父子容器之說?別急,這個在初始化該上文時就明白了。但是這裏的初始化入口在哪呢?沒有看到任何監聽器的創建和調用。實際上這裏的上下文對象初始化是在Servlet初始化時實現的,即init方法,直接來到HttpServletBeaninit方法(分析SpringMVC源碼時講過):

	public final void init() throws ServletException {
		...省略
		
		// Let subclasses do whatever initialization they like.
		initServletBean();
	}

	protected final void initServletBean() throws ServletException {
		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
	}

	protected WebApplicationContext initWebApplicationContext() {
		//這裡會從servletContext中獲取到父容器,就是通過監聽器加載的容器
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					//容器加載
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

看到這裏想你也應該明白了,首先從ServletContext中拿到父容器,然後設置到當前容器的parent中,實現了父子容器的組織,而這樣設計好處我想也是很清楚的,子容器目前裝載的都是MVC的配置和Bean,簡單點說就是Controller,父容器中都是Service,Controller是依賴於Service的,如果不構建這樣的層級關係並優先實例化父容器,你怎麼實現Controller層的依賴注入成功呢?

總結

本篇結合之前的文章,分析了SpringMVC零XML配置的實現原理,也補充了之前未分析到父子容器關係,讓我們能從細節上更加全面的理解SpringIOC的實現原理,相信看完本篇對於SpringBoot的實現你也會有自己的想法。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

分類
發燒車訊

南京金龍加碼純電動物流車 四款新品將在振威新能源汽車展亮相

近年來,在電子商務快速發展的強勢需求拉動下,中國電商物流繼續保持快速增長。在環境壓力較大的當下,物流行業推行新能源汽車也成了改善環境,治理霧霾的一個重要手段。

在新能源物流車形勢一片大好的情況下,新能源客車產品在細分市場銷量第二把交椅的南京金龍自然也不會錯過這一片藍海。據瞭解,南京金龍將於2017上海國際新能源汽車產業博覽會上推出四款新品純電動物流車。展會由振威展覽股份、中國土木工程學會城市公共交通學會、廣東省新能源汽車產業協會、廣東省充電設施協會及充電設施線上網聯合舉辦,將於2017年8月23-25日在上海新國際博覽中心舉行。

據瞭解,本次南京金龍四款純電動物流車型分別為開沃D07、開沃D09、開沃D10和開沃D11。此四款物流車涵蓋3.9-5.9米的中小型物流車,且秉承其一貫的強勁續航能力,滿載續航均在220-260公里,是名副其實的續航之王。其中開沃D07為微型純電動物流車,主要用於城市內的快遞派送。作為“最後一公里”快遞配送專家,其載貨空間達3.4立方米,載貨量可達600KG,完美滿足市網點到客戶的快遞配送。開沃D09、D10為輕型純電動物流車,載貨空間分別在6立方米和8立方米,載重分別為750kg和1000kg,主要用作城市間的物流配送“貨的”。

開沃D7純電動物流車

去年,南京金龍就推出了素有“快遞王”之稱的開沃E7,“物流王”開沃D11,早已以其380公里左右的高續航、1.55噸的高載重、12.3個立方的高容量等特點,4小時繞北京五環三圈贏得了新能源物流車界的良好口碑。D11作為城市大空間長續航的物流王更是經受市場考驗超過3年、以好品質得到客戶認可和擁護的車型,輕鬆勝任城市間的物流運輸。

3月1日,工信部官網正式公佈了《新能源汽車推廣應用推薦車型目錄(2017年第2批)》主要車型及參數。此次發佈的目錄涉及新能源乘用車、新能源客車及新能源專用車共有40家企業201款車型進入目錄。其中,南京金龍開沃牌NJL5040XXYBEV1純電動廂式運輸車入圍本次目錄。

資料顯示,2016年純電動物流車產量達5.78萬輛,且2017年剛開始,工信部就給電動物流車行業一個驚喜。首批新能源汽車推薦目錄發佈,以電動物流車為代表的專用車型共有36款,占總比19%。“新能源物流車正成為新能源商用車發展的一大亮點。”全國乘聯會秘書長崔東樹也指出,新能源物流車使用費用低,維(護)修簡單,從國家政策的扶持以及城市空氣污染的角度來看,新能源物流車急劇增長是必然的,也是長期的發展趨勢。

據不完全統計,北京、上海、廣東、天津等37省市出臺的新能源汽車政策裡都有提及純電動物流車的相關政策。隨著政策的明朗,行業逐漸趨於理性,純電動物流車市場迎來健康發展,其也將迎來真正的發展元年。業界普遍認為,2017年電動物流車行業預計將實現10萬輛左右的銷量,同比增速超過100%。

南京金龍市場負責人表示,本次展會的召開是一個很好的契機,南京金龍開沃純電動物流車憑藉靈活多變的空間及穩定的滿負荷續航里程,將極大地改變我國新能源物流車產業發展格局。未來,南京金龍憑藉產品技術優勢,不斷開拓物流、快遞行業需求,為全面實現“零排放物流”的行業大計而持續努力。

組委會聯繫方式:+86 20-83953286
連絡人:黃俊鵬
官方微信公眾號:nevechina

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

分類
發燒車訊

台達電佈局電動車產品,拓歐美電動車市場

台達電於4月28日舉行法說會,公佈2017年第一季財報合併營收489.25億元新台幣,季減14%,年增2.8%;毛利率27.2%,季減0.06個百分點,年增0.26個百分點;因管控得宜,第一季匯兌收益1.4億元;稅後淨利39.19億元,每股盈餘1.51元,低於前一季的1.91元,略高於去年同期的1.50元。

台達電執行長鄭平表示,公司5月起進行組織調整,擴大在電動車相關產品佈局,目前公司已打入歐美電動車廠,提供包括車載充電器、動力馬達、DC-DC轉換器等電動車零組件;在中國大陸也打入合資車廠,雖然電動車領域營收短期內成長幅度不大,但仍看好電動車領域未來長期的布局效益。

台達電宣布組織調整,自2日起,將以「電源及零組件」、「自動化」與「基礎設施」為新三大業務範疇,其中,電源及零組件業務包括電動車方案事業群(EVSBG)、嵌入式電源系統事業群(EPSBG)、商用電源事業群(MPBG)、零組件事業群(CPBG)、風扇暨熱傳導事業群(FMBG);自動化業務包括機電事業群(IABG)、樓宇自動化事業群(BABG);至於基礎設施業務則包括資通訊基礎設施事業群(ICTBG)、能源基礎設施事業群(EISBG)。以新三大業務範疇區分,第一季電源及零組件占營收比重為55%、自動化占比為11%,基礎設施則占31%。

對於未來營運展望,台達電董事長海英俊表示,第二季看好IA、樓宇自動化、數據中心的營運動能;今年雖然有匯率變數,但強調公司營運不會受到影響,並將持續朝向高毛利產品發展,讓毛利率能維持目前水準或更好。

(本文內容由授權使用)  

 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

自動化主管請辭Tesla,市場憂電動車量產計畫

特斯拉(Tesla Inc.)負責自動化、工程專業的主管Klaus Grohmann,傳出因理念和執行長馬斯克(Elon Musk)不合,已經在3月份離職。市場擔憂,這恐怕會影響特斯拉電動車的量產計畫。

路透社27日獨家報導(),特斯拉在2016年11月收購Grohmann創辦的Grohmann Engineering,原本打算靠著這家企業的自動化、工程專長,在2018年將電動車年產量拉升至50萬台。

不過,消息人士透露,Grohmann跟馬斯克在如何對待現有客戶的問題上衝突不斷,是促使Grohmann離職的主因。據傳,馬斯克要求Grohmann把重心擺到特斯拉專案,將Grohmann Engineering原有客戶(如戴姆勒和BMW)的利益放到後頭。

消息稱,特斯拉仍計畫運用Grohmann留下的技術和人員拉高產能,但部分人在創辦人離開後,對只仰賴一家客戶(也就是特斯拉)的作法感到相當不安。

特斯拉正在加速趕工,希望能在2017年9月讓平價電動車「Model 3」如期投產,但馬斯克為了達標、在生產策略上背負不小風險,未來可能會面臨召回、維修等龐大成本。

路透社4月24日報導,大多數的汽車製造商都會先訂購較便宜的原型設備來測試新車款的生產線,一旦成功打造出合適的車門、儀表板等零組件,就會把這些便宜的設備報廢。

然而,特斯拉在打造Model 3時卻跳過這項程序,直接訂購較為昂貴的永久設備加速趕工,目標就是趕上自己設定的9月量產期限。不過,用來量產數百萬輛汽車的設備假如無法順利製造出合適的零件,想要修正或直接替代,都得花費大把資金。Model 3預設的年產量多達50萬台,一旦需要召回或進行保固期維修,都會拉高公司成本。

CNET、路透社等多家外電報導,特斯拉甫於4月20日宣布在全球召回53,000輛Model S、Model X電動車,以便修正電子手煞車的問題。

特斯拉在聲明中表示,受到影響的是在2016年2月至10月期間生產的Model S與Model X,這些汽車的電子手煞車當中,有一款由第三方廠商供應的小型零組件因製造不當而容易裂開,會讓手煞車無法解除。

特斯拉強調,上述瑕疵至今並未引發任何車禍、也未導致人員死傷,估計這些車輛中,只有不到5%有問題,更換手煞車的時間僅需不到45分鐘。根據聲明,特斯拉正在跟義大利供應商Freni Brembo SpA合作,取得需要更替的零組件。

(本文內容由授權使用。圖片出處:public domain CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

K8S-磁盤配額管理-整理

1.  ephemeral-storage介紹

Kubernetes在1.8的版本中引入了一種類似於CPU,RAM的新的資源模式:ephemeral-storage屬性(直譯為臨時存儲),並且在1.10的版本kubelet默認啟用了這個特性。

ephemeral-storage實現了對Pod應用存儲資源的管理,可以有效的降低Pod應用失控消耗完 node磁盤空間的風險。官網中對該屬性的描述如下:

(https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/):

 

從上述官網介紹,總結如下:

  1. 臨時存儲:臨時的含義是指容器內的數據未做持久化處理,生命周期和容器一致。
  2. 作用對象:容器日誌(/var/log)、EmptyDir類型的volume數據(/var/lib/kubelet)、鏡像層和容器可寫層(/var/lib/docker)。由此可見,基本覆蓋了Pod各個方面的磁盤消耗。
  3. 管理的文件系統:ephemeral-storage對kubelet的根目錄(默認是/var/lib/kubelet)所在的節點分區(文件系統)進行管理,即如果把/var/lib/docker獨立分區,ephemeral-storage將不對/var/lib/docker目錄進行管理。
  4. Pod調度流程:節點上的kubelet啟動的時候,kubelet會統計當前節點的kubelet分區的可分配的磁盤資源,或者你可以覆蓋節點上kubelet的配置來自定義可分配的資源。在創建Pod時會根據存儲需求調度到滿足存儲的節點,在Pod使用超過限制的存儲時會對其做驅逐的處理來保證不會耗盡節點上的磁盤空間。

2. ephemeral-storage功能驗證

2.1   環境準備

  • 虛擬機配置

1)    規格:16 vCpu + 80 GB RAM + 1000 GB 磁盤

2)    分區:/var/lib/docker、/var/lib/kubelet/和/var/log全在同一個系統分區上

  • 測試容器鏡像

1)  Dockerfile

FROM ubuntu:16.04
ADD start.sh /home/
RUN mkdir -p /lq/log/
ENTRYPOINT /home/start.sh

2)  Start.sh

#!/bin/bash
while true
do
dateString=`date`
echo "$dateString==================================">> /lq/log/test.log
done
  • 集群環境

1)  Kuberneters版本:1.15.6

2)  Docker版本:18.06

2.2   容器可寫層大小

  • 容器的部署文件

 

說明:

1)    容器的啟動腳本start.sh會持續的向容器內路徑/lq/log下寫test.log日誌

2)    該日誌並未掛載出來,故日誌文件在宿主機的容器可寫層目錄下

3)    該容器申請10Mi的磁盤空間,上限為20Mi

  • 創建該Pod

使用kubectl apply -f xxxx.yaml,觀察可寫層日誌大小情況以及Pod運行情況

很快可寫層的日誌就達到了16Mi

  • 繼續觀察Pod

 

Pod驅逐了(容器被殺掉,容器內數據全部丟失)

 從上述Event事件可以看到,Pod可用磁盤空間被限制住了

2.3   EmptyDir日誌

  • 部署文件

 

說明:

1)    容器的啟動腳本start.sh會持續的向容器內路徑/lq/log下寫test.log日誌

2)    該日誌通過EmptyDir掛載出來,故日誌文件在宿主機的/var/lib/kubelet目錄下

3)    該容器申請10Mi的磁盤空間,上限為100Mi,emptyDir路徑上限為40Mi

  • 創建該Pod

使用kubectl apply -f  xxxxxxxx.yaml

  • 查詢日誌路徑以及Pod運行情況

 

 

 

可以看到日誌已經到了32Mi,目前Pod運行正常

  • 繼續等待,觀察Pod情況

 

Pod被驅逐了(容器殺死,全部數據丟失)

  • 查看Pod事件

 

可見該日誌磁盤空間被限制了

2.4   /var/log目錄日誌

一般Pod的啟動日誌(k8s上的控制台日誌)會記錄到宿主機的/var/log目錄下,並且根據前面介紹得知ephemeral-storage會對該目錄下的容器日誌磁盤空間大小進行管理,但是由於我使用的測試鏡像並無啟動日誌,故通過hostPath掛載的方式掛載到該路徑下,看看我們显示指定掛載路徑的時候,ephemeral-storage還能否生效。

  • 部署文件

 

  • 創建該Pod

 

  • 觀察Pod運行情況

 

可以看到,自己显示掛載的路徑並做不到磁盤空間限制。

  • 換一種思路

由於無啟動日誌,故想書寫腳本向容器的啟動日誌瘋狂輸出日誌,觀測Pod運行情況

1)  修改start.sh

 

2)  修改部署文件

 

3)  創建該Pod

 

Docker logs查看,容器在瘋狂的輸出日誌

 

4)  持續一段時間,觀察Pod運行狀態

 

Pod驅逐了

2.5   其他情況說明

  1. /var/lib/docker和/var/lib/docker分區文件系統不一致。未做截圖,但是已經實際驗證:/var/lib/docker的分區和kubelet分區不一致的時候,ephemeral-storage對/var/lib/docker目錄磁盤空間大小不做管控

3.  驗證結論

  1. 分區要一致,否則ephemeral-storage管理不到
  2. ephemeral-storage管理的是容器相關的目錄路徑下的磁盤大小,自己顯式掛載的定製化路徑無法控制磁盤空間

4.  參考文檔

  1. https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
  2. https://blog.csdn.net/sdmei/article/details/101017405(總結的非常好)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準