[javascript]關於javascript的那些奇形怪狀的事
前言
最近在協助團隊寫vue.js的時候,因為看到很多人對javascript的坑不理解,或者是不熟悉導致問題連連,在jquery的世界很多情境可能是取取dom改一下元素,或者不明究理的硬改讓效果能出來就好了,但當你要使用前端框架的時候,良好的javascript基礎是很重要的,而javascript太自由了,雖然寫起來很方便,但也會遇到很多坑啊雷的地方,雖然在babel和es6或typescript的協助下,已經為此語言改善了非常多,但是人在江湖上誰能不挨刀,就是會遇到很老舊的網站得維護,所以什麼奇怪不明究理的程式碼到處充斥著也不足為奇了,筆者就來記錄一下有關javascript的一些比較少用的用法,或者一些奇怪的雷和觀念,希望能幫助到各位和自己。
導覽
- 關於true或false的一些秘密
- &&或||的運用
- 不傳參數的function也能成立
- 物件和陣列判斷出來都是物件
- 如何deep clone一個by reference的物件呢
- 關於this的一些bug
- IIFE是什麼鬼東西
- call,bind,apply
- 建立一個不同reference新物件
- javascript的hosting
- 結論
在javascript裡面,其實我們常常在判斷值的時候,會用如下語法來做判斷,如果你還不是這樣子做判斷的話,那請記得此規則囉。
if (!a) console.log(a)
a = null
if (!a) console.log(a)
a = ''
if (!a) console.log(a)
a=0
if (!a) console.log(a)
a=false
if (!a) console.log(a)
a=123
if (!a) console.log(a)
除了最後兩個之外,全部都會印出來,從此實驗我們可以得到一個結論,有值的就是為true,除了兩個特例,一是a=false的話會為false,二是a=0的話會為false,所以如果我們的值為數字的話,就要明確判斷數字是否大於0比較保險哦。
這兩個比較常用到的是||,有時候我們希望為一個不存在的值給一個預設值,就可以用如下的方式去做
var a = undefined
a = a || 123
console.log(a)
最後a會等於123,可以想像成a如果不成立的話,就把123給予a變數,或者用更白話的意思,如果a沒有值的話,就給予123,接著我們來看一下&&又代表什麼呢?
var a = 123
a = a && 'abc'
console.log(a)
a會等於abc,因為a有值所以判斷成功,所以把'abc'給予a變數,但其實我們還可以當成判斷條件式的應用,比如下方的例子
var a = undefined
a || console.log('is false') //等同於 if(!a)console.log('is false')
a = 123
a && console.log(a) //等同於 if(a) console.log(a)
在javascript的世界裡面,你定義了一個method,就算你不傳參數也能呼叫,只是參數會變成undefined而已,如下例子
callMethodWithoutParam()
function callMethodWithoutParam(msg){
console.log(msg)
}
會得到一個undefined,並不會報錯哦,這跟c#明顯是不一樣的事,所以在沒有es6的時候,我們如果要預防空值的話,會用如下的方式去做
callMethodWithoutParam()
function callMethodWithoutParam(msg){
msg=msg||'default value'
console.log(msg)
}
在es6就更簡便了,為參數給一個default value就好了
callMethodWithoutParam()
function callMethodWithoutParam(msg='default value'){
console.log(msg)
}
在javascript建立一個物件有很多方法,但是最直接也最好的方法就是使用{}來定義,如下方式
var a={
id:1,
name:'kin'
}
而如果要定義陣列的話,可以如下方式
var b=[1,2,3]
但是事實上這兩種都是物件,如果我們用typeof來看的話,就會呈現出來,不過實際上array是繼承自Array.prototyte,而object則是繼承自Object.prototyte,所以可以使用的api就不一樣了,那我們即然用typeof判斷不出來的話,我們得怎麼去察覺呢?可以用如下的方式。
var a = {
id: 1,
name: 'kin'
}
var b = [1, 2, 3]
console.log(a.constructor === Array)
console.log(b.constructor === Array)
如何deep clone一個by reference的物件呢
首先解釋一下什麼叫做by reference,看一下下面的程式碼
var a = {
id: 1,
name: 'kin'
}
var b = a
a.id = 2
console.log(a.id, b.id)
結果就是a和b的id都改成2了,因為共用同一份記憶體了,所以改a就等於改到b,那要怎麼clone一份物件出來呢?我們可以使用如下兩種方式
在es6或較新的瀏覽器,其實有兩種語法可以直接clone一個新的reference,如下方式
var b = Object.assign({},a)
var b = {...a}
相信如果有寫react的人對上述的語法一定不陌生,但是這個clone只是淺層的,深層的話還是沒有用的,有一種更簡單的方式,使用如下方式,先轉成字串再轉回來,就可以了
var b = JSON.stringify(a)
b = JSON.parse(b)
相信很常有看到程式是這樣寫如下的方式來指派this
var vm=this
先來看一下下面的情境
var a = {
id: 1,
name: 'kin',
getName: function () {
console.log(this)
}
}
這時候的this會是代表著a這個物件
但如果我們在getName裡面在寫一個方法並把this印出來會變成什麼狀況呢?
var a = {
id: 1,
name: 'kin',
getName: function () {
console.log(this)
function setName(){
console.log(this)
}
setName()
}
}
第二個this卻是指到了window物件了,所以我們必須把程式碼加上vm=this的語法,以讓setName裡面可以取到a物件
var a = {
id: 1,
name: 'kin',
getName: function () {
console.log(this)
var vm = this;
function setName() {
console.log(vm)
}
setName()
}
}
不過如果你是寫es6的話,善用arrow function的語法就不會遇到這個問題了
var a = {
id: 1,
name: 'kin',
getName() {
console.log(this)
const setName = () => {
console.log(this)
}
setName()
}
}
IIFE全名為(Immediately Invoked Functions Expressions),翻白話文就是建立function的時候馬上執行的意思,如下範例就會直接執行函式了
var test=function(){
console.log('test')
}()
如果我們想要可以傳參數的話,也可以這樣子寫
var test=function(msg){
console.log(msg)
}('test')
當我們使用這種語法來定義函數的話,就不能再呼叫了,不過蠻少看人會這樣子用的,但是我們常常會看到為了避免全局汙染,而使用如下的方式,這就是為何會有如下的語法出現囉
(function (msg) {
console.log(msg)
}('test'))
但是即然我們希望不要全局汙染,為何還要丟參數進來呢?有時候為了一些莫名奇妙的理由,所以需要把外面的東西丟進來讓裡面變更,比如丟一個對象進去,改完之後全局的變數也會跟著變更。
<body onload="init()">
<script>
var globalVar = {
id: 1,
name: 'kin',
getName() {
console.log(this)
const setName = () => {
console.log(this)
}
}
}
function init() {
(function (obj) {
obj.name = 'kinanson'
}(globalVar))
globalVar.getName()
}
</script>
</body>
這個是我記得以前有在javascript的考題看過,所以特別記錄下來的,這三個api都是在解決this這個bug,以之前this的例子來看,setName應該會是印出window而不是a這個物件
var a = {
id: 1,
name: 'kin',
getName: function () {
console.log(this)
function setName(){
console.log(this)
}
setName()
}
}
如果我們用bind來處理的話,就會這樣處理this的bug
var a = {
id: 1,
name: 'kin',
getName: function () {
console.log(this)
function setName() {
console.log(this)
}
var setUserName=setName.bind(a)
setUserName()
}
}
a.getName()
而call的話則是可以多放入方法的參數,並且直接呼叫此方法使用
var a = {
id: 1,
name: 'kin',
getName: function () {
console.log(this)
function setName(userName) {
this.name = userName
console.log(this)
}
setName.call(a, 'kinanson')
}
}
a.getName()
apply的話跟call很像,差異在後面的參數是用陣列帶入而已
setName.apply(a, ['kinanson'])
當然如果你寫的是es6的話,善用arrow function就不會再遇到this的坑了。
之前有提到,在javascript建立物件比較好的方法是使用如下語法
let obj={
id:1,
name:'anson'
}
但是這種方法卻是singleton的方法,也就是全局共用了這個物件的屬性,那我們如何建立一個全新的物件,然後不是全局共用的呢?其實使用function就可以了,請見如下例子
function obj() {
this.id = 1
this.name = 'kin'
}
var a = new obj()
var b = new obj()
b.id = 2
console.log(`a物件為${a.id},b物件為${b.id}` )
但是請注意一點很重要的事情,因為我們每次new一個物件,就是佔用了一個記憶體,所以如果我們在這個物件有共用方法的話,可以使用為物件的prototype的方式,讓每個new出來的物件,共享同一個記憶體的方法,以避免不必要的記憶體浪費
function obj() {
this.id = 1
this.name = 'kin'
}
obj.prototype.getProperty = function () {
console.log(this.id, this.name)
}
var a = new obj()
var b = new obj()
b.id = 2
a.getProperty()
b.getProperty()
而善用prototype的特性,我們就很容易替一個物件新增新特性,比如說vue我們想要新增一個全局的alert方法,我們就只要寫如下語法就行了
Vue.prototype.alert=()=>{
toastr('something')
}
當然es6已經有了跟我們寫class很類似的語法了,所以如果你是寫es6的話,還是直接善用新特性吧。
在javascript裡面,定義了var永遠都會被自動放在當前函數的最前面,但如下代碼會是undefind,因為使用了立即執行函數的原因
var v = "hello" ;
( function (){
console.log(v);
var v = "world" ;
})();
我們也可以先不使用var,然後直接給變數值,在後面才使用var,此答案為印出hello
( function (){
v="hello";
console.log(v);
var v = "world" ;
})();
但是執行函數則不行,我們可以使用一個變數代表一個方法,此段示例則會顯示報錯
( function (){
foo()
var foo=function(){
console.log('hi');
}
})();
有鑑於var有很多陷阱,所以在es6之後,我們應該使用let來代替var,以防止一些奇奇怪怪的bug問題
這篇並不能包含全部的問題,日後有發覺自己沒看到的,或者是有學習到新的再來補一下,有關於另外一些memory leak的問題,也可以看一下筆者以前的文章(https://dotblogs.com.tw/kinanson/2017/05/11/140022)