你須要知道的 javascript 的細節

如今的前端框架層出不窮,3個月就要從新入門一次前端的現狀,讓咱們來不及學好基礎就開始上手框架。經常就由於這樣,咱們會很快到達基礎 基礎技術瓶頸,基礎是全部技術的核心,在跳槽季從新溫故了一遍 javascript 基礎,有收穫,整理出來分享給你們。

對象

變量能夠當對象使用

javascript 中全部的變量均可以當作對象使用,除了undefinednull ,咱們測試下javascript

false.toString() // "false"

[1,2,3].toString() //"1,2,3"

1..toString() //"1"

({a:'33'}).toString() //"[object Object]"
undefined.toString() //Uncaught TypeError

null.toString()   //Uncaught TypeError

數值和對象雖然能調用 toString 方法,可是在寫法上須要注意下html

number 調用時不能直接數值後面直接調用toString 方法,由於 js 會將點運算符解析爲數值的小數點前端

1.toString() //Uncaught SyntaxError

1..toString() //"1"

對象直接調用toString 方法時,須要用小括號包裹起來,否則js 會將對象的花括號識別成塊,從而報錯java

{a:'33'}.toString()  // Uncaught SyntaxError

({a:'33'}).toString() // "[object Object]"

對象刪除屬性

刪除對象的屬性惟一的方法是使用 delete 操做符,設置元素屬性爲 undefined 或則 null 並不能真正刪除,只是移除了屬性和值的關聯
var test = {
    name:'bbt',
    age:'18',
    love:'dog'
}

test.name = undefined
test.age = null
delete test.love

for (var i in test){
  console.log(i+':'+test[i])
}

運行結果es6

name:undefined
age:null
undefined

只有 love 被正則刪除,nameage 仍是能被遍歷到面試

構造函數

javascript 中,經過關鍵字 new 調用的函數就被認爲是構造函數,咱們能夠經過構造函數建立對象實例

可是在使用過程當中你必定發現了,每實例化一個對象,都會在實例對象上創造構造函數的方法和屬性。假若建立的實例比較多,重複建立同一個方法去開闢內存空間就會顯得十分浪費,咱們能夠經過把被常常複用的方法放在原型鏈上。編程

原型繼承

javascript 和一些咱們所瞭解的面向對象編程的語言不太同樣,在 es6 語法之前,咱們是經過原型鏈來實現方法和屬性的繼承
function Child(){
  this.name = 'bbt'
}

Child.prototype = {
    title:'baba',
    method: function() {}
};

function Grandson(){}

//設置 Grandson 的 prototype 爲 Child 的實例
Grandson.prototype = new Child()

//爲 Grandson 的原型添加添加屬性 age
Grandson.prototype.age = 40

// 修正 Grandson.prototype.constructor 爲 Grandson 自己
Grandson.prototype.constructor = Grandson;

var xiaomin = new Grandson()

//原型鏈以下
xiaomin // Grandson的實例
    Grandson.prototype // Child的實例
         Grandson.prototype //{title:'baba',...}
            Object.prototype
                {toString: ... /* etc. */};

對象的屬性查找,javascript 會在原型鏈上向上查找屬性,直到查到 原型鏈頂部,因此,屬性在原型鏈的越上端,查找的時間會越長,查找性能和複用屬性方面須要開發者本身衡量下。數組

獲取自身對象屬性

hasOwnProperty 方法可以判斷一個對象是否包含自定義屬性,而不是在原型鏈上的屬性前端框架

var test = {hello:'123'}

Object.prototype.name = 'bbt'

test.name  //'bbt'
test.hasOwnProperty('hello') //true
test.hasOwnProperty('name') //false

for in 循環能夠遍歷對象原型鏈上的全部屬性,如此咱們將 hasOwnProperty 結合循環for in 可以獲取到對象自定義屬性微信

var test = {hello:'222'}
Object.prototype.name = 'bbt'

for(var i in test){
  console.log(i) // 輸出兩個屬性,hello ,name
}


for(var i in test){
  if(test.hasOwnProperty(i)){
    console.log(i)//只輸出 hello
  }
}

除了上面的方法,getOwnPropertyNames Object.keys 方法,可以返回對象自身的全部屬性名,也是接受一個對象做爲參數,返回一個數組,包含了該對象自身的全部屬性名。

var test = {hello:'222'}
Object.prototype.name = 'bbt'

Object.keys(test) //["hello"]
Object.getOwnPropertyNames(test) //["hello"]

getOwnPropertyNamesObject.keys 的用法有什麼區別呢

Object.keys方法只返回可枚舉的屬性,Object.getOwnPropertyNames 方法還返回不可枚舉的屬性名。

var a = ['Hello', 'World'];

Object.keys(a) // ["0", "1"]

Object.getOwnPropertyNames(a) // ["0", "1", "length"]  // length 是不可枚舉屬性

函數

函數聲明的變量提高

咱們一般會使用函數聲明或函數賦值表達式來定義一個函數,函數聲明和變量聲明同樣都存在提高的狀況,函數能夠在聲明前調用,可是不能夠在賦值前調用

函數聲明

foo(); // 正常運行,由於foo在代碼運行前已經被建立
function foo() {}

函數表達式

foo; // 'undefined'
foo(); // 出錯:TypeError
var foo = function() {};

變量提高是在代碼解析的時候進行的,foo() 方法調用的時候,已經在解析階段將 foo 定義過了。賦值語句只在代碼運行時才進行,因此在賦值前調用會報錯

一種比較少用的函數賦值操做,將命名函數賦值給一個變量,此時的函數名只對函數內部可見

var test = function foo(){
  console.log(foo) //正常輸出
}

console.log(foo) //Uncaught ReferenceError

this 的工做原理

javascript 中 , this 是一個比較難理解的點,不一樣的調用環境會致使 this 的不一樣指向,可是惟一不變的是 this 老是指向一個對象

簡單的說,this 就是屬性和方法當前所在的對象(函數執行坐在的做用域),平時使用的 this 的狀況能夠大體分爲5種

調用方式 指向
1. 全局範圍調用 指向 window 全局對象
2. 函數調用 指向 window 全局變量
3. 對象的方法調用 指向方法調用的對象
4. 構造函數調用 指向構造函數建立的實例
5. 經過,call ,apply ,bind 顯示的指定 this指向 和傳參有關

Function.call

語法:function.call(thisArg, arg1, arg2, …), thisArg表示但願函數被調用的做用域, arg1, arg2, …表示但願被傳入函數額參數 , 若是參數爲空、 nullundefined,則默認傳入全局對象。

代碼示例

var name = 'xiaomin'
var test = {name : 'bbt'}

function hello( _name ){
  _name ?console.log(this.name,_name): console.log(this.name)
}

hello() //xiaomin
hello.call(test) //bbt
hello.call(test,'xiaohong') //bbt xiaohong
hello.call() //xiaomin
hello.call(null) //xiaomin
hello.call(undefined) //xiaomin

Function.apply

語法和 call 方法相似,不一樣的是,傳入調用函數的參數變成以數組的形式傳入,即 func.apply(thisArg, [argsArray])

改造上面的示例就是

hello.apply(test,['xiaomin'])

Function.bind

bind方法用於將函數體內的 this綁定到某個對象,而後返回一個新函數。
var d = new Date();
d.getTime()

var print = d.getTime; //賦值後 getTime 已經不指向 d 實例
print() // Uncaught TypeError

解決方法

var print = d.getTime.bind(d)

容易出錯的地方

容易出錯的地方,函數調用,this 老是指向 window 全局變量,因此在對象的方法裏若是有函數的調用的話(閉包的狀況),this 是會指向 全局對象的,不會指向調用的對象,具體示例以下

var name = 'xiaomin'
var test = {
  name : 'bbt'
}
test.method = function(){
  function hello(){
      console.log(this.name)
    }
    hello()
}

// 調用
test.method() // 輸出 xiaomin

若是須要將 this 指向調用的對象,能夠將對象的 this 指向存儲起來,一般咱們使用 that 變量來作這個存儲。改進以後的代碼

var name = 'xiaomin'
var test = {
  name : 'bbt'
}
test.method = function(){
  var that = this
  function hello(){
      console.log(that.name)
    }
    hello()
}

// 調用
test.method() // 輸出 bbt

閉包和引用

閉包咱們能夠理解成是在函數內部定義的函數

javascript 中,內部做用域能夠訪問到外部做用域的變量,可是外部做用域不能訪問內部做用域,須要訪問的時候,咱們須要經過建立閉包,來操做內部變量

function test(_count){
  var count = _count

  return {
    inc:function(){
      count++
    },
    get:function(){
      return count
    }
  }
}

var a = test(4)
a.get()//4
a.inc()
a.get()//5

閉包中常會出錯的面試題

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 0);
}

不少同窗會以爲,上面的代碼會正常輸出0到9,可是實際是輸出十次10。遇到這個題目,除了閉包的概念要理解清楚,你還須要知道,setTimeout 內的代碼會被異步執行,代碼會先執行全部的同步代碼,即上面的這段代碼會先將 for 循環執行,此時 i 的值爲 10,console.log(i) 一直引用着全局變量的 i 因此會輸出十次 10

改進代碼,咱們在 for 循環裏建立一個閉包,把循環自增的 i 做爲參數傳入

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);
        }, 1000);
    })(i);
}

setTimeout && setInterval

javascript 是異步的單線程運行語言,其餘代碼運行的時候可能會阻塞 setTimeout && setInterval 的運行
console.log(1)
setTimeout(function(){
  console.log(2)
}, 0);
console.log(3)

輸出結果: 1,3,2  //setTimeout 被阻塞

處理阻塞的方法是將setTimeoutsetInterval放在回調函數裏執行

function test(){
      setTimeout(function(){
          console.log(2)
    }, 0);
}

setTimeoutsetInterval 被調用時會返回一個 ID 用來清除定時器

手工清除某個定時器

var id = setTimeout(foo, 1000);
clearTimeout(id);

清楚全部的定時器

var lastId = setTimeout(function(){
  console.log('11')
}, 0);

for(var i=0;i<lastId;i++;){
  clearTimeout(i);
}

獲取最後一個定時器的id,遍歷清除定時器,能夠清除全部的定時器。

類型

包裝對象

數值、字符串、布爾值——在必定條件下,也會自動轉爲對象,也就是原始類型的「包裝對象」。

咱們能夠經過構造函數,將原始類型轉化爲對應的對象即包裝對象,從而是原始類型可以方便的調用某些方法

數值,字符串,布爾值的類型轉換函數分別是 Number,String,Boolean,在調用的時候在函數前面加上New 就變成了構造函數,可以蔣對應的原始類型轉化爲「包裝對象」

var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);

typeof v1 // "object"
typeof v2 // "object"
typeof v3 // "object"

v1 === 123 // false
v2 === 'abc' // false
v3 === true // false

類型轉換

類型轉換分爲強制類型轉換和自動轉換,javascript 是動態類型語言,在到嗎解析運行時,須要的數據類型和傳入的數據類型不一致的時候,javascript 會進行自動類型轉化。固然,你也能夠經過類型轉換方法進行強制類型裝換。

平常開發中,咱們最經常使用的數據類型自動轉換不過就下面三種狀況

不一樣數據類型之間相互運算

'2'+4 // '24'

對非布爾值進行布爾運算

if('22'){
  console.log('hello')
}

對非數據類型使用一元運算符

+'12'  //12

咱們也經過 Number ,String,Boolean 來進行強制數據類型轉換。強制類型轉化的規則有點複雜,咱們來了解一下。

Number 轉換 引用阮老師的詳細解釋

第一步,調用對象自身的valueOf方法。若是返回原始類型的值,則直接對該值使用Number函數,再也不進行後續步驟。

第二步,若是 valueOf 方法返回的仍是對象,則改成調用對象自身的 toString 方法。若是 toString 方法返回原始類型的值,則對該值使用 Number 函數,再也不進行後續步驟。

第三步,若是 toString 方法返回的是對象,就報錯。

String 轉換方法一樣也是經過調用原對象的 toString 方法和 valueOf 方法,可是不一樣的是 String 函數會先調用 toString 方法進行轉換

Boolean 的轉換規則會相對簡單一些,除了幾個特殊的值,都會被轉化爲 true

undefined
null
+0或-0
NaN
''(空字符串)

可是要注意

Boolean('false') //true

typeof

typeof 操做符返回數據類型,可是因爲 javascript 設計的歷史緣由, typeof 現已經不能知足咱們如今對於類型判斷的要求了
Value Class Type
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function functio
/abc/g RegExp object (function in Nitro/V8)
new RegExp("meow") RegExp object (function in Nitro/V8)
{} Object object
new Object() Object object
null null object

咱們能夠看到,typeof 不能區分對象的數組和日期,還會把 null 判斷成對象,那咱們通常是何時用 typeof 呢。咱們能夠用來判斷一個已經定義的變量是否被賦值。

var a
if(typeof a == 'undefined'){
  console.log('a 已經被定義')
}

instanceof

instanceof 操做符一般用來判斷,一個對象是否在另外一個對象的原型鏈上,須要注意的是 instanceof 的左值是對象,右值是構造函數
// defining constructors
function C() {}
function D() {}

var o = new C();

// true, because: Object.getPrototypeOf(o) === C.prototype
o instanceof C;

// false, because D.prototype is nowhere in o's prototype chain
o instanceof D;

#### Object.prototype.toString

那麼咱們有沒有能夠用來區分變量數據類型的方法呢,有, Object.prototype.toString

一些原始數據類型也有 toString 方法,可是一般他們的 toString 方法都是改造過的,不能進行 數據類型判斷,因此咱們須要用 Object 原型鏈上的 toString 方法

var a = 1234
a.toString() // '1234'

Object.prototype.toString.call(a) // "[object Number]"

不一樣類型返回的結果以下:

1. 數值 [object Number]
 2. 字符串 [object String]
 3.布爾值 [object Boolean]
 4.undefined [object undefined]
 5.null  [object Null]
 6.數組 [object Array]
 7.arguments [object Arguments]
 8.函數 [object function]
 9.Error [object Error]
 10.Date [object Date]
 11.RegExp [object RegExp]
 12.其餘對象 [object object]

那麼咱們就可以經過 Object.prototype.toString 方法,封裝一個能夠判斷變量數據類型的函數了

function type(obj) {
    return Object.prototype.toString.call(obj).slice(8, -1);
}

type(function(){}) //"Function"
此次咱們從對象、函數、類型三方面入手瞭解了 javascript 中容易被忽視或則說比較難理解的地方,我會繼續將我在學習中積累的內容分享給你們,若是你們以爲文章有須要改進或則有其餘想要了解的內容的,歡迎私信,評論或則微信我,646321933
相關文章
相關標籤/搜索