js基礎騷操作

null和undefined區別

  • 例子
// 在代碼中
Number(null); // 0
5 + Number(null); // 5
Number(undefined); // NaN

對於null和undefined,大致可以像下面這樣理解。

null表示空值,即該處的值現在爲空。調用函數時,某個參數未設置任何值,這時就可以傳入null,表示該參數爲空。比如,某個函數接受引擎拋出的錯誤作爲參數,如果運行過程中未出錯,那麼這個參數就會傳入null,表示未發生錯誤。

整數和浮點數

  • JavaScript 內部,所有數字都是以64位浮點數形式儲存,即使整數也是如此。所以,1與1.0是相同的,是同一個數。
1 === 1.0 // true
  • JavaScript 會自動把64位浮點數,轉成32位整數,然後再進行運算
  • js運算有效範圍在 即-2的53次方到2的53次方

數值的精度

(-1)^符號位  *  1.xx...xx  *  2^指數部分

上述

根據國際標準 IEEE 754,JavaScript 浮點數的64個二進制位,從最左邊開始,是這樣組成的。

  • 第1位:符號位,0表示正數,1表示負數

  • 第2位到第12位(共11位):指數部分

  • 第13位到第64位(共52位):小數部分(即有效數字)
    所以 js運算有效範圍在 即-2的53次方到2的53次方

  • 任何數都有正負數 , 包括0

  • 絕大多數的情況下 , 除了除法 , 其他運算和0運算都是一樣的 , 例子如下

1 / +0 // Infinity 正無窮

1 / -0 // -Infinity // 負無窮
1 / -0 === 1 / +0 // false

Infinity 特殊運算

5 * Infinity ;// Infinity
0 * Infinity; // NaN

Infinity - Infinity; // NaN
Infinity / Infinity; // NaN

  • Infinity與null計算時,null會轉成0,等同於與0的計算。Nnumber(null)輸出0
  • Infinity與undefined計算,返回的都是NaN

parseInt 和 parseFloat

  • parseInt 如果第二個參數不是數值,會被自動轉爲一個整數。這個整數只有在2到36之間,才能得到有意義的結果,超出這個範圍,則返回NaN。如果第二個參數是0、undefined和null,則直接忽略。
  • parseFloat除了不接受第二個參數和特定的進制外 , 其他使用和parseInt一樣

isNaN()

isNaN('Hello')
// 相當於
isNaN(Number('Hello'))

isFinite()

判斷是否爲一個正常數值

Base64 轉碼

  • btoa():任意值轉爲 Base64 編碼
  • atob():Base64 編碼轉爲原來的值

表達式還是語句?

當首行是大括號開頭時 , js引擎會解析成代碼塊 , 想要解析成對象 , 就可以使用以下方式`


({ foo: 123 }) // 正確
({ console.log(123) }) // 報錯

對象屬性操作

var obj = {};
delete obj.p // true

上面代碼中,對象obj並沒有p屬性,但是delete命令照樣返回true。因此,不能根據delete命令的結果,認定某個屬性是存在的。

屬性是否存在:in 運算符

var obj = { p: 1 };
'p' in obj // true'toString' in obj // true , 會自動往原型上面查找 , 所以得使用 hasOwnProperty 判斷是否自身

var obj = {};
if ('toString' in obj) {
  console.log(obj.hasOwnProperty('toString')) // false
}

for...in

for...in循環有兩個使用注意點。

  • 它遍歷的是對象所有可遍歷(enumerable)的屬性,會跳過不可遍歷的屬性。
  • 它不僅遍歷對象自身的屬性,還遍歷繼承的屬性。
    舉例來說,對象都繼承了toString屬性,但是for...in循環不會遍歷到這個屬性。
var obj = {};

// toString 屬性是存在的 , 但是默認是不可遍歷屬性
obj.toString // toString() { [native code] }

for (var p in obj) {
  console.log(p);
} // 沒有任何輸出
  • 應該結合使用hasOwnProperty方法,在循環內部判斷一下,某個屬性是否爲對象自身的屬性。

函數名的提升


f();

function f() {} // 不報錯


f();
var f = function (){};
// TypeError: undefined is not a function


// 例子1和例子2解釋瞭如果同時採用function命令和賦值語句聲明同一個函數,最後總是採用賦值語句的定義。

// 例子2和例子3解釋了 , 使用賦值語句聲明的函數 , 如果執行函數在前面那麼會執行同名的 function命令 聲明的函數
{
// 例子1
    var f = function () {
      console.log('1');
    }

    function f() {
      console.log('2');
    }

    f() // 1
}
{
// 例子2
    function f() {
      console.log('2');
    }
    var f = function () {
      console.log('1');
    }
    f() // 1
}

{
// 例子3
    f() // 輸出2
    var f = function () {
      console.log('1');
    }

    function f() {
      console.log('2');
    }
}

函數name屬性

var f3 = function myName() {};
f3.name // 'myName'


var f2 = function () {};
f2.name // "f2"

運算符

這裏特地說一下 + 運算符
對於引用類型相加
首先兩個引用類型相加 , 都會經過一系列的轉換纔會去相加 , 最後都會轉換成基本類型

var obj = {}
obj + 2 // "[object Object]2"

他是怎麼相加的呢
引用類型首先會調用對象的valueOf方法。再自動調用對象的toString方法 , 這就是他的原理
但是也有特殊情況

{} + [] // 0 , 這裏 , 前面的 {} 會被js引擎當成代碼塊 , 這裏實際只是 +[] 
[] + {} // "[object Object]"

還有一種特殊情況就是 , 當引用類型是function時 , 又會去會調用對象的valueOf方法。再自動調用對象的toString方法
例子

{}+ function a(){} // "[object Object]function a(){}"

還有一種特殊情況 , 引用類型爲Date時 , Date直接調用toString方法

var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' }; // 我們可以對toString直接改寫

obj + 2 // "hello2"

自增和自減運算符

執行該運算子之前 , 都會先對前面需要運算的函數或者值進行Number處理 , 而且會改變原始值
例如當 當前是引用類型錯誤的時候

var A = {}
A++ //NaNconsole.log(A) // NaNvar a = function(){}
a++  //NaNconsole.log(a) // NaN

指數運算符

這個指數運算符相當於使用Math.pow(x,y)
例子

2 ** 3// 跟下面相等Math.pow(2,3)

特殊情況 , 指數運算符是右結合,而不是左結合。即多個指數運算符連用時,先進行最右邊的計算

2 ** 3 ** 2// 512// 相當於 2 ** (3 ** 2)

比較運算符

比較大小的算法是

  1. 當兩個比較的運算子爲字符串的時候 按照字典順序比較(實際上是比較 Unicode 碼點)
  2. 其他情況 , 將兩個運算子都轉成數值,再比較數值的大小。

其他運算 ,
NaN , 所有類型的數值和NaN 比大小都是false
對象和引用類型的類型的值 , 會轉換成原始類型的值再做比較
先調用 valueOf() 再調用 toString() ,
還有其他比較 , 例如

false == 'false'    // falsefalse == '0'        // true

false == undefined  // falsefalse == null       // falsenull == undefined   // true

不相等運算符相等運算符有一個對應的“不相等運算符”(!=),它的算法就是先求相等運算符的結果,然後返回相反值。

1 != '1' // false

// 等同於
!(1 == '1')

布爾運算符

!!x
// 等同於Boolean(x)

短路運算
如下

if (i) {
  doSomething();
}

// 等價於

i && doSomething();

二進制位運算符

二進制位運算符用於直接對二進制位進行計算,一共有7個。

  1. 二進制或運算符(or):符號爲|,表示若兩個二進制位都爲0,則結果爲0,否則爲1。
  2. 二進制與運算符(and):符號爲&,表示若兩個二進制位都爲1,則結果爲1,否則爲0。
  3. 二進制否運算符(not):符號爲~,表示對一個二進制位取反。
  4. 異或運算符(xor):符號爲^,表示若兩個二進制位不相同,則結果爲1,否則爲0。
  5. 左移運算符(left shift):符號爲<<,詳見下文解釋。
  6. 右移運算符(right shift):符號爲>>,詳見下文解釋。
  7. 頭部補零的右移運算符(zero filled right shift):符號爲>>>,詳見下文解釋。

這些位運算符直接處理每一個比特位(bit),所以是非常底層的運算,好處是速度極快,缺點是很不直觀,許多場合不能使用它們,否則會使代碼難以理解和查錯

!!!注意點1 有一點需要特別注意,位運算符只對整數起作用
!!!注意點2 做位運算的時候,是以32位帶符號的整數進行運算的,並且返回值也是一個32位帶符號的整數
!!!注意點3 位運算只對整數做運算 , 且會自動去除小數點 , 且內部js引擎會調用Number()方法

// 利用這個特性,可以寫出一個函數,將任意數值轉爲32位整數function toInt32(x) {
  return x | 0;
}

異或運算符

異或運算(^)在兩個二進制位不同時返回1,相同時返回0
例子

// 表達式中,0(二進制00)與3(二進制11)進行異或運算,它們每一個二進制位都不同,所以得到11(即3)0 ^ 3 // 3

還有另外一個超實用的用法 , 就是不使用臨時變量的情況下 , 互換兩個變量的整數

var a = 10;
var b = 99;

a ^= b, b ^= a, a ^= b;

a // 99
b // 10

類型轉換

parseInt逐個解析字符,而Number函數整體轉換字符串的類型。
一般來說Number非數字字符串 , 就會返回NaN

Number原理

第一步,調用對象自身的valueOf方法。如果返回原始類型的值,則直接對該值使用Number函數,不再進行後續步驟。
第二步,如果valueOf方法返回的還是對象,則改爲調用對象自身的toString方法。如果toString方法返回原始類型的值,則對該值使用Number函數,不再進行後續步驟。
第三步,如果toString方法返回的是對象,就報錯。

String方法 , 原理

與Number方法基本相同,只是互換了valueOf方法和toString方法的執行順序。

  1. 先調用對象自身的toString方法。如果返回原始類型的值,則對該值使用String函數,不再進行以下步驟。
  2. 如果toString方法返回的是對象,再調用原對象的valueOf方法。如果valueOf方法返回原始類型的值,則對該值使用String函數,不再進行以下步驟。
  3. 如果valueOf方法返回的是對象,就報錯。

Boolean() 原理

Boolean()函數可以將任意類型的值轉爲布爾值。

它的轉換規則相對簡單:除了以下五個值的轉換結果爲false,其他的值全部爲true。

  1. undefined
  2. null
  3. 0(包含-0和+0)
  4. NaN
  5. ''(空字符串)

關於try .... catch .... finally

例子

try {
    throw '出錯了!';
  } catch(e) {
    console.log('捕捉到內部錯誤');
    throw e + '這裏是catch'; // 這句原本會等到finally結束再執行 , 但由於finally有return語句 , 就不會執行
  } finally {
    return false; // 直接返回
  }
  /* 輸出如下
  捕捉到內部錯誤 ------ > 這裏是catch執行的邏輯
 false ----> 這裏是finally return 返回的false
  */
  
  // 如果我們不在finally使用return語句 , 那麼就是執行catch裏面拋出錯誤的邏輯 , 例子如下
  try {
    throw '出錯了!';
  } catch(e) {
    console.log('捕捉到內部錯誤');
    throw e + '這裏是catch'; // 這句原本會等到finally結束再執行 , 但由於finally有return語句 , 就不會執行
  } finally {
    console.log('finally'); // 執行這裏會 , 返回catch裏面拋出錯誤
  }

編寫風格

對switch函數改寫

switch...case結構要求,在每一個case的最後一行必須是break語句,否則會接着運行下一個case。這樣不僅容易忘記,還會造成代碼的冗長。

而且,switch...case不使用大括號,不利於代碼形式的統一。此外,這種結構類似於goto語句,容易造成程序流程的混亂,使得代碼結構混亂不堪,不符合面向對象編程的原則。
可以使用以下改寫風格

// 原switch case

function doAction(action) {
  switch (action) {
    case 'hack':
      return 'hack';
    case 'slash':
      return 'slash';
    case 'run':
      return 'run';
    default:
      throw new Error('Invalid action.');
  }
}

// 改寫如下

function doAction(action) {
  var actions = {
    'hack': function () {
      return 'hack';
    },
    'slash': function () {
      return 'slash';
    },
    'run': function () {
      return 'run';
    }
  };

  if (typeof actions[action] !== 'function') {
    throw new Error('Invalid action.');
  }

  return actions[action]();
}

Object 對象

Object.prototype.toString.call(obj)判斷類型
不同數據類型的Object.prototype.toString.call(obj)方法返回值如下。

  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]。

this

  • 將this當作foreach方法的第二個參數,固定它的運行環境。
var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    }, this);
  }
}

o.f()

// hello a1// hello a2

這就是說,Object.prototype.toString可以看出一個值到底是什麼類型。

call

函數實例的call方法,可以指定函數內部this的指向(即函數執行時所在的作用域),然後在所指定的作用域中,調用該函數
簡單的例子

var obj = {};

var f = function () {
  return this;
};

f() === window // true  ----> this指向window
f.call(obj) === obj // true ---> this指向obj

call環境如果是null , undefined , 或者不傳參數 , 那麼就是指向window

var n = 123;
var obj = { n: 456 };

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

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456

如果call方法的參數是一個原始值,那麼這個原始值會自動轉成對應的包裝對象,然後傳入call方法。

例子

var f = function () {
  return this;
};

f.call(5)
// Number {[[PrimitiveValue]]: 5}

call第一個參數是作用域 , 其餘參數是對象接收的參數 , ,不返回新的函數
apply第一個參數是作用域, 第二個參數是數組作爲對象的參數傳進去 , 不返回新的函數
bind , 使用方式和call一樣 , 但是會返回新的函數

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章