javascript中判斷數據類型

編寫javascript代碼的時候常常要判斷變量,字面量的類型,可以用typeof,instanceof,Array.isArray(),等方法,究竟哪一種最方便,最實用,最省心呢?本問探討這個問題。

1. typeof

1.1 語法

typeof返回一個字符串,表示未經計算的操作數的類型。

語法:typeof(operand) | typeof operand
參數:一個表示對象或原始值的表達式,其類型將被返回
描述:typeof可能返回的值如下:

類型 結果
Undefined “undefined”
Null “object”
Boolean “boolean”
Number “number”
Bigint “bigint”
String “string”
Symbol “symbol”
宿主對象(由JS環境提供) 取決於具體實現
Function對象 “function”
其他任何對象 “object”

從定義和描述上來看,這個語法可以判斷出很多的數據類型,但是仔細觀察,typeof null居然返回的是“object”,讓人摸不着頭腦,下面會具體介紹,先看看這個效果:

    // 數值
    console.log(typeof 37) // number
    console.log(typeof 3.14) // number
    console.log(typeof(42)) // number
    console.log(typeof Math.LN2) // number
    console.log(typeof Infinity) // number
    console.log(typeof NaN) // number 儘管它是Not-A-Number的縮寫,實際NaN是数字計算得到的結果,或者將其他類型變量轉化成数字失敗的結果
    console.log(Number(1)) //number Number(1)構造函數會把參數解析成字面量
    console.log(typeof 42n) //bigint
    // 字符串
    console.log(typeof '') //string
    console.log(typeof 'boo') // string
    console.log(typeof `template literal`) // string
    console.log(typeof '1') //string 內容為数字的字符串仍然是字符串
    console.log(typeof(typeof 1)) //string,typeof總是返回一個字符串
    console.log(typeof String(1)) //string String將任意值轉換成字符串
    // 布爾值
    console.log(typeof true) // boolean
    console.log(typeof false) // boolean
    console.log(typeof Boolean(1)) // boolean Boolean會基於參數是真值還是虛值進行轉換
    console.log(typeof !!(1)) // boolean 兩次調用!!操作想短語Boolean()
    // Undefined
    console.log(typeof undefined) // undefined
    console.log(typeof declaredButUndefinedVariabl) // 未賦值的變量返回undefined
    console.log(typeof undeclaredVariable ) // 未定義的變量返回undefined
    // 對象
    console.log(typeof {a: 1}) //object
    console.log(typeof new Date()) //object
    console.log(typeof /s/) // 正則表達式返回object
    // 下面的例子令人迷惑,非常危險,沒有用處,應避免使用,new操作符返回的實例都是對象
    console.log(typeof new Boolean(true)) // object
    console.log(typeof new Number(1)) // object
    console.log(typeof new String('abc')) // object
    // 函數
    console.log(typeof function () {}) // function
    console.log(typeof class C { }) // function
    console.log(typeof Math.sin) // function 

1.2 迷之null

javascript誕生以來,typeof null都是返回‘object’的,這個是因為javascript中的值由兩部分組成,一部分是表示類型的標籤,另一部分是表示實際的值。對象類型的值類型標籤是0,不巧的是null表示空指針,它的類型標籤也被設計成0,於是就有這個typeof null === ‘object’這個‘惡魔之子’。

曾經有ECMAScript提案讓typeof null返回‘null’,但是該提案被拒絕了。

1.3 使用new操作符

除Function之外所有構造函數的類型都是‘object’,如下:

    var str = new String('String');
    var num = new Number(100)
    console.log(typeof str) // object
    console.log(typeof num) // object
    var func = new Function()
    console.log(typeof func) // function 

1.4 語法中的括號

typeof運算的優先級要高於“+”操作,但是低於圓括號

    var iData = 99
    console.log(typeof iData + ' Wisen') // number Wisen
    console.log(typeof (iData + 'Wisen')) // string 

1.5 判斷正則表達式的兼容性問題

typeof /s/ === 'function'; // Chrome 1-12 , 不符合 ECMAScript 5.1
typeof /s/ === 'object'; // Firefox 5+ , 符合 ECMAScript 5.1 

1.6 錯誤

ECMAScript 2015之前,typeof總能保證對任何所給的操作數都返回一個字符串,即使是沒有聲明,沒有賦值的標示符,typeof也能返回undefined,也就是說使用typeof永遠不會報錯。

但是ES6中加入了塊級作用域以及let,const命令之後,在變量聲明之前使用由let,const聲明的變量都會拋出一個ReferenceError錯誤,塊級作用域變量在塊的頭部到聲明變量之間是“暫時性死區”,在這期間訪問變量會拋出錯誤。如下:

    console.log(typeof undeclaredVariable) // 'undefined'
    console.log(typeof newLetVariable) // ReferenceError
    console.log(typeof newConstVariable) // ReferenceError
    console.log(typeof newClass) // ReferenceError

    let newLetVariable
    const newConstVariable = 'hello'
    class newClass{} 

1.7 例外

當前所有瀏覽器都暴露一個類型為undefined的非標準宿主對象document.all。typeof document.all === ‘undefined’。景觀規範允許為非標準的外來對象自定義類型標籤,單要求這些類型標籤與已有的不同,document.all的類型標籤為undefined的例子在web領域被歸類為對原ECMA javascript標準的“故意侵犯”,可能就是瀏覽器的惡作劇。

總結:typeof返回變量或者值的類型標籤,雖然對大部分類型都能返回正確結果,但是對null,構造函數實例,正則表達式這三種不太理想。 

2. instanceof

2.1 語法

instanceof運算符用於檢測實例對象(參數)的原型鏈上是否出現構造函數的prototype。

語法:object instanceof constructor
參數:object 某個實例對象
          constructor 某個構造函數
描述:instanceof運算符用來檢測constructor.property是否存在於參數object的原型鏈上。

// 定義構造函數
    function C() {
    }
    function D() {
    }
    var o = new C()
    console.log(o instanceof C) //true,因為Object.getPrototypeOf(0) === C.prototype
    console.log(o instanceof D) //false,D.prototype不在o的原型鏈上
    console.log(o instanceof Object) //true 同上

    C.prototype = {}
    var o2 = new C()
    console.log(o2 instanceof C) // true
    console.log(o instanceof C) // false C.prototype指向了一個空對象,這個空對象不在o的原型鏈上
    D.prototype = new C() // 繼承
    var o3 = new D()
    console.log(o3 instanceof D) // true
    console.log(o3 instanceof C) // true C.prototype現在在o3的原型鏈上

需要注意的是,如果表達式obj instanceof Foo返回true,則並不意味着該表達式會永遠返回true,應為Foo.prototype屬性的值可能被修改,修改之後的值可能不在obj的原型鏈上,這時表達式的值就是false了。另外一種情況,改變obj的原型鏈的情況,雖然在當前ES規範中,只能讀取對象的原型而不能修改它,但是藉助非標準的__proto__偽屬性,是可以修改的,比如執行obj.__proto__ = {}后,obj instanceof Foo就返回false了。此外ES6中Object.setPrototypeOf(),Reflect.setPrototypeOf()都可以修改對象的原型。

instanceof和多全局對象(多個iframe或多個window之間的交互)

瀏覽器中,javascript腳本可能需要在多個窗口之間交互。多個窗口意味着多個全局環境,不同全局環境擁有不同的全局對象,從而擁有不同的內置構造函數。這可能會引發一些問題。例如表達式[] instanceof window.frames[0].Array會返回false,因為Array.prototype !== window.frames[0].Array.prototype。

起初,這樣可能沒有意義,但是當在腳本中處理多個frame或多個window以及通過函數將對象從一個窗口傳遞到另一個窗口時,這就是一個非常有意義的話題。實際上,可以通過Array.isArray(myObj)或者Object.prototype.toString.call(myObj) = “[object Array]”來安全的檢測傳過來的對象是否是一個數組。

2.2 示例

String對象和Date對象都屬於Object類型(它們都由Object派生出來)。

但是,使用對象文字符號創建的對象在這裡是一個例外,雖然原型未定義,但是instanceof of Object返回true。

    var simpleStr = "This is a simple string";
    var myString  = new String();
    var newStr    = new String("String created with constructor");
    var myDate    = new Date();
    var myObj     = {};
    var myNonObj  = Object.create(null);

    console.log(simpleStr instanceof String); // 返回 false,雖然String.prototype在simpleStr的原型鏈上,但是後者是字面量,不是對象
    console.log(myString  instanceof String); // 返回 true
    console.log(newStr    instanceof String); // 返回 true
    console.log(myString  instanceof Object); // 返回 true

    console.log(myObj instanceof Object);    // 返回 true, 儘管原型沒有定義
    console.log(({})  instanceof Object);    // 返回 true, 同上
    console.log(myNonObj instanceof Object); // 返回 false, 一種創建非 Object 實例的對象的方法

    console.log(myString instanceof Date); //返回 false

    console.log( myDate instanceof Date);     // 返回 true
    console.log(myDate instanceof Object);   // 返回 true
    console.log(myDate instanceof String);   // 返回 false 

注意:instanceof運算符的左邊必須是一個對象,像”string” instanceof String,true instanceof Boolean這樣的字面量都會返回false。

下面代碼創建了一個類型Car,以及該類型的對象實例mycar,instanceof運算符表明了這個myca對象既屬於Car類型,又屬於Object類型。

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var mycar = new Car("Honda", "Accord", 1998);
var a = mycar instanceof Car;    // 返回 true
var b = mycar instanceof Object; // 返回 true 

不是…的實例

要檢測對象不是某個構造函數的實例時,可以使用!運算符,例如if(!(mycar instanceof Car))

instanceof雖然能夠判斷出對象的類型,但是必須要求這個參數是一個對象,簡單類型的變量,字面量就不行了,很顯然,這在實際編碼中也是不夠實用。

總結:obj instanceof constructor雖然能判斷出對象的原型鏈上是否有構造函數的原型,但是只能判斷出對象類型變量,字面量是判斷不出的。

3. Object.prototype.toString()

3.1. 語法

toString()方法返回一個表示該對象的字符串。

語法:obj.toString()
返回值:一個表示該對象的字符串
描述:每個對象都有一個toString()方法,該對象被表示為一個文本字符串時,或一個對象以預期的字符串方式引用時自動調用。默認情況下,toString()方法被每個Object對象繼承,如果此方法在自定義對象中未被覆蓋,toString()返回“[object type]”,其中type是對象的類型,看下面代碼:

    var o = new Object();
    console.log(o.toString()); // returns [object Object] 

注意:如ECMAScript 5和隨後的Errata中所定義,從javascript1.8.5開始,toString()調用null返回[object, Null],undefined返回[object Undefined]

3.2. 示例

覆蓋默認的toString()方法

可以自定義一個方法,來覆蓋默認的toString()方法,該toString()方法不能傳入參數,並且必須返回一個字符串,自定義的toString()方法可以是任何我們需要的值,但如果帶有相關的信息,將變得非常有用。

下面代碼中定義Dog對象類型,並在構造函數原型上覆蓋toString()方法,返回一個有實際意義的字符串,描述當前dog的姓名,顏色,性別,飼養員等信息。

function Dog(name,breed,color,sex) {
        this.name = name;
        this.breed = breed;
        this.color = color;
        this.sex = sex;
    }
    Dog.prototype.toString = function dogToString() {
        return "Dog " + this.name + " is a " + this.sex + " " + this.color + " " + this.breed
    }

    var theDog = new Dog("Gabby", "Lab", "chocolate", "female");
    console.log(theDog.toString()) //Dog Gabby is a female chocolate Lab 

4. 使用toString()檢測數據類型

目前來看toString()方法能夠基本滿足javascript數據類型的檢測需求,可以通過toString()來檢測每個對象的類型。為了每個對象都能通過Object.prototype.toString()來檢測,需要以Function.prototype.call()或者Function.prototype.apply()的形式來檢測,傳入要檢測的對象或變量作為第一個參數,返回一個字符串”[object type]”。

    // null undefined
    console.log(Object.prototype.toString.call(null)) //[object Null] 很給力
    console.log(Object.prototype.toString.call(undefined)) //[object Undefined] 很給力

    // Number
    console.log(Object.prototype.toString.call(Infinity)) //[object Number]
    console.log(Object.prototype.toString.call(Number.MAX_SAFE_INTEGER)) //[object Number]
    console.log(Object.prototype.toString.call(NaN)) //[object Number],NaN一般是数字運算得到的結果,返回Number還算可以接受
    console.log(Object.prototype.toString.call(1)) //[object Number]
    var n = 100
    console.log(Object.prototype.toString.call(n)) //[object Number]
    console.log(Object.prototype.toString.call(0)) // [object Number]
    console.log(Object.prototype.toString.call(Number(1))) //[object Number] 很給力
    console.log(Object.prototype.toString.call(new Number(1))) //[object Number] 很給力
    console.log(Object.prototype.toString.call('1')) //[object String]
    console.log(Object.prototype.toString.call(new String('2'))) // [object String]

    // Boolean
    console.log(Object.prototype.toString.call(true)) // [object Boolean]
    console.log(Object.prototype.toString.call(new Boolean(1))) //[object Boolean]

    // Array
    console.log(Object.prototype.toString.call(new Array(1))) // [object Array]
    console.log(Object.prototype.toString.call([])) // [object Array]

    // Object
    console.log(Object.prototype.toString.call(new Object())) // [object Object]
    function foo() {}
    let a = new foo()
    console.log(Object.prototype.toString.call(a)) // [object Object]

    // Function
    console.log(Object.prototype.toString.call(Math.floor)) //[object Function]
    console.log(Object.prototype.toString.call(foo)) //[object Function]

    // Symbol
    console.log(Object.prototype.toString.call(Symbol('222'))) //[object Symbol]

    // RegExp
    console.log(Object.prototype.toString.call(/sss/)) //[object RegExp] 

上面的結果,除了NaN返回Number稍微有點差池之外其他的都返回了意料之中的結果,都能滿足實際開發的需求,於是我們可以寫一個通用的函數來檢測變量,字面量的類型。如下:

    let Type = (function () {
        let type = {};
        let typeArr = ['String', 'Object', 'Number', 'Array', 'Undefined', 'Function', 'Null', 'Symbol', 'Boolean', 'RegExp', 'BigInt'];
        for (let i = 0; i < typeArr.length; i++) {
            (function (name) {
                type['is' + name] = function (obj) {
                    return Object.prototype.toString.call(obj) === '[object ' + name + ']'
                }
            })(typeArr[i])
        }
        return type
    })()
    let s = true
    console.log(Type.isBoolean(s)) // true
    console.log(Type.isRegExp(/22/)) // true 

除了能檢測ECMAScript規定的八種數據類型(七種原始類型,BooleanNullUndefinedNumberBigIntStringSymbol,一種複合類型Object)之外,還能檢測出正則表達式RegExpFunction這兩種類型,基本上能滿足開發中的判斷數據類型需求。

5. 判斷相等

既然說道這裏,不妨說一說另一個開發中常見的問題,判斷一個變量是否等於一個值。ES5中比較兩個值是否相等,可以使用相等運算符(==),嚴格相等運算符(===),但它們都有缺點,== 會將‘4’轉換成4,後者NaN不等於自身,以及+0 !=== -0。ES6中提出”Same-value equality“(同值相等)算法,用來解決這個問題。Object.is就是部署這個算法的新方法,它用來比較兩個值是否嚴格相等,與嚴格比較運算(===)行為基本一致。

    console.log(5 == '5') // true
    console.log(NaN == NaN) // false
    console.log(+0 == -0) // true
    console.log({} == {}) // false

    console.log(5 === '5') // false
    console.log(NaN === NaN) // false
    console.log(+0 === -0) // true
    console.log({} === {}) // false

Object.js()不同之處有兩處,一是+0不等於-0,而是NaN等於自身,如下:

    let a = {}
    let b = {}
    let c = b
    console.log(a === b) // false
    console.log(b === c) // true
    console.log(Object.is(b, c)) // true 

注意兩個空對象不能判斷相等,除非是將一個對象賦值給另外一個變量,對象類型的變量是一個指針,比較的也是這個指針,而不是對象內部屬性,對象原型等。

 

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

安利一個繪製指引線的JS庫leader-line

前言

之前看到一篇推薦這個搜索引擎的新聞,對於這個搜索引擎是否好用咱們不予置評,但是我在這個搜索引擎上面發現了一個好玩的前端功能。

如上圖,將鼠標浮動到學習來源上時,會展示一堆指引線。

本博客的右側文章目錄也集成了這個功能,諸位可以玩一玩。

當時覺得這個功能很好玩,而且前端領域其實這種指引線還是有很多用處的,比如新手指引,功能指引,腦圖之類的功能。

鑒於以後很可能需要用到,當時就調試了一下這個網站,發現使用了這個庫。

然後百度了一下,發現網上也沒什麼人介紹這個庫,所以這裏寫個安利文吧。

LeaderLine

這個庫在Github上的介紹很簡單:

Draw a leader line in your web page.

意思就是在網頁上畫指引線。

使用起來也非常方便:

<script src="leader-line.min.js"></script>
<script>
  new LeaderLine(
    document.getElementById('start'),
    document.getElementById('end')
  );
</script>

new一個LeaderLine對象即可,只需要輸入兩個dom元素節點而已。

當然也可以輸入更多的參數來繪製各種各樣的指引線:

具體的使用方法可以去查看lead-line的,這裏就不贅述了。

而且這個庫本身就提供了hover繪製指引線的功能,並且能偏移起始點和結束點的位置,同時當起始點和結束點變動時,也可以實時調整指引線。

這兩個功能可以將鼠標hover到右側的文章目錄上,然後滾動鼠標輪來查看效果。

原理

這個庫的實現原理其實很簡單,根據提供的兩個dom元素,找到這兩個dom元素的位置,然後通過svg在body下繪製一條指引線。

這個庫雖然只是個js,但是在引入後會將一些樣式寫到一個id為leader-line-defs的svg元素內。

這些指引線使用了一個叫leader-line的樣式class,如果繪製指引線時出現遮擋情況,可以通過調整這個樣式class的z-index或者position來處理。

可以預想一下,這些指引線都是position:absolute的,因為position:fixed的元素在滾動時肯定會存在問題。

原理都講了,所以諸位請在頁面有fixed元素或者absolute元素時,仔細查看指引線是否會與這些元素產生遮擋。

示例代碼

這裏就以我博客右側目錄集成的指引線功能作為示例代碼:

// 生成目錄上的指引線
function createCatalogLeaderLine($h2Arr) { // $h2Arr是一個dom元素集合,注意不是數組哦
  // lines的目的是為了保留leader-line變量,方便重繪
  var lines = {};
  var options = {
    color: '#5bf', // 指引線顏色
    endPlug: "disc", // 指引線結束點的樣式
    size: 2, // 線條尺寸
    startSocket: "left", //在指引線開始的地方從元素左側開始
    endSocket: "right", //在指引線開始的地方從元素右側結束
    hide:true // 繪製時隱藏,默認為false,在初始化時可能會出現閃爍的線條
  };
  [].slice.call($h2Arr).forEach(function (item) {
    var anchor = LeaderLine.mouseHoverAnchor(document.getElementById('catalog' + item.id), 'draw', {
      // 指引線動效
      animOptions: {
        duration: 500
      },
      // 清除默認的hover樣式
      hoverStyle:{
        backgroundColor: null
      },
      // 起始點樣式,這裏為了清除默認樣式
      style: {
        paddingTop: null,
        paddingRight: null,
        paddingBottom: null,
        paddingLeft: null,
        cursor: null,
        backgroundColor: null,
        backgroundImage: null,
        backgroundSize: null,
        backgroundPosition: null,
        backgroundRepeat: null
      },
      // 當起始點被hover時調用的事件
      onSwitch: function (event) {
        var line = lines[item.id]
        // 浮動上去就重繪
        if (event.type == "mouseenter") {
          line.position();
        }
      }
    });
    lines[item.id] = new LeaderLine(
      anchor,
      document.getElementById(item.id),
      options
    );
  })
  // 滾動時重繪指引線
  $(window).scroll(function () {
    for (var key in lines) {
      lines[key].position()
    }
  })
}

其中LeaderLine.mouseHoverAnchor為leader-line提供的api,顧名思義即可。

代碼就不講了,關鍵點都有註釋。

總結

沒什麼好總結的,這裏發一個小吐槽。

其實我博客集成這個功能時,最開始是直接把這個庫的js複製粘貼到了博客園的自定義js代碼中,沒想到博客園這方面做了大小限制。

所以我就把Magi這個搜索引擎的引用地址拿來用了,萬一哪天這個搜索引擎不能用了或者js地址變了那麼我目錄的指引功能可能就掛了。

N年之後你看到這篇文章,也許功能失效了,到時候別忘了給我發個短消息提醒我一下。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

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

手把手帶你實戰下Spring的七種事務傳播行為

目錄

本文介紹Spring的七種事務傳播行為並通過代碼演示下。

一、什麼是事務傳播行為?

事務傳播行為(propagation behavior)指的就是當一個事務方法被另一個事務方法調用時,這個事務方法應該如何運行。

例如:methodA方法調用methodB方法時,methodB是繼續在調用者methodA的事務中運行呢,還是為自己開啟一個新事務運行,這就是由methodB的事務傳播行為決定的。

二、事務的7種傳播行為

Spring在TransactionDefinition接口中規定了7種類型的事務傳播行為。事務傳播行為是Spring框架獨有的事務增強特性。這是Spring為我們提供的強大的工具箱,使用事務傳播行為可以為我們的開發工作提供許多便利。

7種事務傳播行為如下:

1.PROPAGATION_REQUIRED

如果當前沒有事務,就創建一個新事務,如果當前存在事務,就加入該事務,這是最常見的選擇,也是Spring默認的事務傳播行為。

2.PROPAGATION_SUPPORTS

支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。

3.PROPAGATION_MANDATORY

支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就拋出異常。

4.PROPAGATION_REQUIRES_NEW

創建新事務,無論當前存不存在事務,都創建新事務。

5.PROPAGATION_NOT_SUPPORTED

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

6.PROPAGATION_NEVER

以非事務方式執行,如果當前存在事務,則拋出異常。

7.PROPAGATION_NESTED

如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則按REQUIRED屬性執行。

其實這7中我也沒看懂,不過不急,咱們接下來直接看效果。

三、7種傳播行為實戰

演示前先建兩個表,用戶表和用戶角色表,一開始兩個表裡沒有數據。

需要注意下,為了數據更直觀,每次執行代碼時 先清空下user和user_role表的數據。

user表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `sex` int(11) DEFAULT NULL,
  `des` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

user_role表:

CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.PROPAGATION_REQUIRED測試

如果當前沒有事務,就創建一個新事務,如果當前存在事務,就加入該事務,這是最常見的選擇,也是Spring默認的事務傳播行為。

場景一:

此場景外圍方法沒有開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation=Propagation.REQUIRED,然後在測試方法中同時調用兩個方法並在調用結束后拋出異常。

2.主要代碼

外層調用方法代碼:

/**
     * 測試 PROPAGATION_REQUIRED
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_REQUIRED() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

4.結果分析

外圍方法未開啟事務,插入用戶表和用戶角色表的方法在自己的事務中獨立運行,外圍方法異常不影響內部插入,所以兩條記錄都新增成功。

場景二:

此場景外圍方法開啟事務。

1.主要代碼

測試方法代碼如下:

/**
     * 測試 PROPAGATION_REQUIRED
     *
     * @Author: java_suisui
     */
    @Transactional
    @Test
    void test_PROPAGATION_REQUIRED() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

2.代碼執行后數據庫截圖

兩張表數據都為空,截圖如下:

3.結果分析

外圍方法開啟事務,內部方法加入外圍方法事務,外圍方法回滾,內部方法也要回滾,所以兩個記錄都插入失敗。

結論:以上結果證明在外圍方法開啟事務的情況下Propagation.REQUIRED修飾的內部方法會加入到外圍方法的事務中,所以Propagation.REQUIRED修飾的內部方法和外圍方法均屬於同一事務,只要一個方法回滾,整個事務均回滾。

2.PROPAGATION_SUPPORTS測試

支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。

場景一:

此場景外圍方法沒有開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation=Propagation.SUPPORTS,然後在測試方法中同時調用兩個方法並在調用結束后拋出異常。

2.主要代碼

外層調用方法代碼:

    /**
     * 測試 PROPAGATION_SUPPORTS
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_SUPPORTS() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

4.結果分析

外圍方法未開啟事務,插入用戶表和用戶角色表的方法以非事務的方式獨立運行,外圍方法異常不影響內部插入,所以兩條記錄都新增成功。

場景二:

此場景外圍方法開啟事務。

1.主要代碼

test_PROPAGATION_SUPPORTS方法添加註解@Transactional即可。

2.代碼執行后數據庫截圖

兩張表數據都為空,截圖如下:

3.結果分析

外圍方法開啟事務,內部方法加入外圍方法事務,外圍方法回滾,內部方法也要回滾,所以兩個記錄都插入失敗。

結論:以上結果證明在外圍方法開啟事務的情況下Propagation.SUPPORTS修飾的內部方法會加入到外圍方法的事務中,所以Propagation.SUPPORTS修飾的內部方法和外圍方法均屬於同一事務,只要一個方法回滾,整個事務均回滾。

3.PROPAGATION_MANDATORY測試

支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就拋出異常。

通過上面的測試,“支持當前事務,如果當前存在事務,就加入該事務”,這句話已經驗證了,外層添加@Transactional註解后兩條記錄都新增失敗,所以這個傳播行為只測試下外層沒有開始事務的場景。

場景一:

此場景外圍方法沒有開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation = Propagation.MANDATORY,主要代碼如下。

2.主要代碼

外層調用方法代碼:

    /**
     * 測試 PROPAGATION_MANDATORY
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_MANDATORY() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.MANDATORY)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.MANDATORY)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都為空,截圖如下:

4.結果分析

運行日誌如下,可以發現在調用userService.add()時候已經報錯了,所以兩個表都沒有新增數據,驗證了“如果當前不存在事務,就拋出異常”。

at com.example.springboot.mybatisannotation.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$50090f18.add(<generated>)
    at com.example.springboot.mybatisannotation.SpringBootMybatisAnnotationApplicationTests.test_PROPAGATION_MANDATORY(SpringBootMybatisAnnotationApplicationTests.java:78)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

4.PROPAGATION_REQUIRES_NEW測試

創建新事務,無論當前存不存在事務,都創建新事務。

這種情況每次都創建事務,所以我們驗證一種情況即可。

場景一:

此場景外圍方法開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation = Propagation.REQUIRES_NEW,主要代碼如下。

2.主要代碼

外層調用方法代碼:

    /**
     * 測試 REQUIRES_NEW
     *
     * @Author: java_suisui
     */
    @Test
    @Transactional
    void test_REQUIRES_NEW() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

4.結果分析

無論當前存不存在事務,都創建新事務,所以兩個數據新增成功。

5.PROPAGATION_NOT_SUPPORTED測試

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

場景一:

此場景外圍方法不開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation = Propagation.NOT_SUPPORTED,主要代碼如下。

2.主要代碼

外層調用方法代碼:

    /**
     * 測試 PROPAGATION_NOT_SUPPORTED
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_NOT_SUPPORTED() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

4.結果分析

以非事務方式執行,所以兩個數據新增成功。

場景二:

此場景外圍方法開啟事務。

1.主要代碼

test_PROPAGATION_NOT_SUPPORTED方法添加註解@Transactional即可。

2.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

3.結果分析

如果當前存在事務,就把當前事務掛起,相當於以非事務方式執行,所以兩個數據新增成功。

6.PROPAGATION_NEVER測試

以非事務方式執行,如果當前存在事務,則拋出異常。

上面已經有類似情況,外層沒有事務會以非事務的方式運行,兩個表新增成功;有事務則拋出異常,兩個表都都沒有新增數據。

7.PROPAGATION_NESTED測試

如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則按REQUIRED屬性執行。

上面已經有類似情況,外層沒有事務會以REQUIRED屬性的方式運行,兩個表新增成功;有事務但是用的是一個事務,方法最後拋出了異常導致回滾,兩個表都都沒有新增數據。

到此Spring的7種事務傳播行為已經全部介紹完成了,有問題歡迎留言溝通哦!

完整源碼地址: https://github.com/suisui2019/springboot-study

推薦閱讀

限時領取免費Java相關資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo/Kafka、Hadoop、Hbase、Flink等高併發分佈式、大數據、機器學習等技術。
關注下方公眾號即可免費領取:

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

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

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

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

南投搬家前需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

聚類——密度聚類DBSCAN

Clustering 聚類

密度聚類——DBSCAN

  前面我們已經介紹了兩種聚類算法:k-means和譜聚類。今天,我們來介紹一種基於密度的聚類算法——DBSCAN,它是最經典的密度聚類算法,是很多算法的基礎,擁有很多聚類算法不具有的優勢。今天,小編就帶你理解密度聚類算法DBSCAN的實質。

 

DBSCAN

 

基礎概念

    作為最經典的密度聚類算法,DBSCAN使用一組關於“鄰域”概念的參數來描述樣本分佈的緊密程度,將具有足夠密度的區域劃分成簇,且能在有噪聲的條件下發現任意形狀的簇。在學習具體算法前,我們先定義幾個相關的概念:

  • 鄰域:對於任意給定樣本x和距離ε,x的ε鄰域是指到x距離不超過ε的樣本的集合;

  • 核心對象:若樣本x的ε鄰域內至少包含minPts個樣本,則x是一個核心對象;

  • 密度直達:若樣本b在a的ε鄰域內,且a是核心對象,則稱樣本b由樣本x密度直達;

  • 密度可達:對於樣本a,b,如果存在樣例p1,p2,…,pn,其中,p1=a,pn=b,且序列中每一個樣本都與它的前一個樣本密度直達,則稱樣本a與b密度可達;

  • 密度相連:對於樣本a和b,若存在樣本k使得a與k密度可達,且k與b密度可達,則a與b密度相連。

 

光看文字是不是繞暈了?下面我們用一個圖來簡單表示上面的密度關係:

當minPts=3時,虛線圈表示ε鄰域,則從圖中我們可以觀察到:

  • x1是核心對象;

  • x2由x1密度直達;

  • x3由x1密度可達;

  • x3與x4密度相連。

為什麼要定義這些看上去差不多又容易把人繞暈的概念呢?其實ε鄰域使用(ε,minpts)這兩個關鍵的參數來描述鄰域樣本分佈的緊密程度,規定了在一定鄰域閾值內樣本的個數(這不就是密度嘛)。那有了這些概念,如何根據密度進行聚類呢?

DBSCAN聚類思想

  DBSCAN聚類的原理很簡單:由密度可達關係導出最大密度相連的樣本集合(聚類)。這樣的一個集合中有一個或多個核心對象,如果只有一個核心對象,則簇中其他非核心對象都在這個核心對象的ε鄰域內;如果是多個核心對象,那麼任意一個核心對象的ε鄰域內一定包含另一個核心對象(否則無法密度可達)。這些核心對象以及包含在它ε鄰域內的所有樣本構成一個類。

  那麼,如何找到這樣一個樣本集合呢?一開始任意選擇一個沒有被標記的核心對象,找到它的所有密度可達對象,即一個簇,這些核心對象以及它們ε鄰域內的點被標記為同一個類;然後再找一個未標記過的核心對象,重複上邊的步驟,直到所有核心對象都被標記為止。

  算法的思想很簡單,但是我們必須考慮一些細節問題才能產出一個好的聚類結果:

  • 首先對於一些不存在任何核心對象鄰域內的點,再DBSCAN中我們將其標記為離群點(異常);
  • 第二個是距離度量,如歐式距離,在我們要確定ε鄰域內的點時,必須要計算樣本點到所有點之間的距離,對於樣本數較少的場景,還可以應付,如果數據量特別大,一般採用KD樹或者球樹來快速搜索最近鄰,不熟悉這兩種方法的同學可以找相關文獻看看,這裏不再贅述;
  • 第三個問題是如果存在樣本到兩個核心對象的距離都小於ε,但這兩個核心對象不屬於同一個類,那麼該樣本屬於哪一個類呢?一般DBSCAN採用先來後到的方法,樣本將被標記成先聚成的類。

DBSCAN算法流程

DBSCAN算法小結

      之前我們學過了kmeans算法,用戶需要給出聚類的個數k,然而我們往往對k的大小無法確定。DBSCAN算法最大的優勢就是無需給定聚類個數k,且能夠發現任意形狀的聚類,且在聚類過程中能自動識別出離群點。那麼,我們在什麼時候使用DBSCAN算法來聚類呢?一般來說,如果數據集比較稠密且形狀非凸,用密度聚類的方法效果要好一些。

DBSCAN算法優點:

  1. 不需要事先指定聚類個數,且可以發現任意形狀的聚類;

  2. 對異常點不敏感,在聚類過程中能自動識別出異常點;

  3. 聚類結果不依賴於節點的遍歷順序;

DBSCAN缺點:

  1. 對於密度不均勻,聚類間分佈差異大的數據集,聚類質量變差;

  2. 樣本集較大時,算法收斂時間較長;

  3. 調參較複雜,要同時考慮兩個參數;

 

小結:

基於密度的聚類算法是廣為使用的算法,特別是對於任意形狀聚類以及存在異常點的場景。上面我們也提到了DBSCAN算法的缺點,但是其實很多研究者已經在DBSCAN的基礎上做出了改進,實現了多密度的聚類,針對海量數據的場景,提出了micro-cluster的結構來表徵距離近的一小部分點,減少存儲壓力和計算壓力…還有很多先進的密度聚類算法及其應用,相信看完這篇文章再去讀相關的論文會比較輕鬆。

 

掃碼關注

獲取有趣的算法知識

 

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※幫你省時又省力,新北清潔一流服務好口碑

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

說服川普抗暖化 影后珍芳達曾想祭出美女戰術

摘錄自2019年12月18日中央社報導

珍芳達將在12月21日歡慶82歲生日,17日她在全國記者俱樂部(National Press Club)表示,她曾試圖在2016年美國總統川普當選後,安排包括女星潘蜜拉安德森在內的一群「美麗、性感、傑出」環保人士與川普會面,以說服他對付全球暖化問題。珍芳達(Jane Fonda)曾經跟川普女婿庫許納(Jared Kushner)和女兒伊凡卡(Ivanka Trump)討論她的想法,但並未得到回覆。最終這讓她搬到華盛頓住上幾個月,利用自己的名人力量動員群眾。

珍芳達曾分別以1971年「柳巷芳草」(Klute)與1978年「歸返家園」(Coming Home)兩部作品,摘下奧斯卡影后殊榮。

長年投身社會運動的珍芳達經常參與氣候變遷抗議活動,曾於今年10月在美國國會山莊外被警方逮捕。

 

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

燃煤電廠轉型燒生質能? 歐洲智庫警告:砍樹燒木屑 未必能減碳

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

風災後福島輻射污染擴散 學者提醒志工應佩戴口罩

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

紐西蘭白島火山罹難者增至19人 含2名失蹤者

摘錄自2019年12月23日中央社報導

紐西蘭白島(White Island)火山9日突然噴發,當時有47人在島上觀光,其中大部分是澳洲旅客,紐西蘭警方23日證實又一人在醫院過世後,白島火山噴發死亡人數升至19人,目前仍有25人在醫院且多人傷勢危急。死亡總數包括遺體尚未尋獲但據信已經罹難的兩人,17歲澳洲公民藍福特(Winona Langford)和40歲紐西蘭導遊馬歇爾-殷曼(Hayden Marshall-Inman)的遺體據信已被捲進海裡。

紐西蘭警方救援白島火山罹難者。照片來源:

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

日本風災後核廢棄物外流 空拍機搜索仍大批失蹤

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

核電廠延役聲浪時起 德國2022年廢核不變

摘錄自2019年12月22日中央社報導

德國社會上最近有聲音認為應該延長核電廠的使用年限,檢討廢核政策;不過,德國政府發言人強調廢核是朝野共識,政府2022年廢核的計畫不變。

執政的基督教民主黨(CDU)能源政策發言人菲佛(Joachim Pfeiffer)18日接受「明鏡」週刊(Der Spiegel)訪問時表示,廢核是錯誤的政策;不過,他強調基民黨團不會主動提案。

對此,德國聯邦環境部發言人費克特納(Nikolai Fichtner)在例行記者會表示,朝野政黨2011年達成共識,解決了核電數十年來在德國社會的爭議性。核電是高風險的科技,興建核電廠的計畫顯示核電成本太高,此外還有核廢料的問題。總之,核電帶給未來世代許多問題,環境部認為廢核共識不應該翻盤。

總理梅克爾(Angela Merkel)的發言人塞柏特(Steffen Seibert)也指出,政府對核電的態度沒變,廢核將照原計畫執行。

德國西南部菲力普斯堡(Philippsburg)的核電廠,由安能亞太EnBW營運。照片來源:

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

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

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

南投搬家前需注意的眉眉角角,別等搬了再說!