1. 程式人生 > >你不知道的js

你不知道的js

作用域 LHS RHS 區別

  1. 如果 RHS 查詢在所有巢狀的作用域中遍尋不到所需的變數,引擎就會丟擲 ReferenceError 異常。值得注意的是,ReferenceError 是非常重要的異常型別。
    相較之下,當引擎執行 LHS 查詢時,如果在頂層(全域性作用域)中也無法找到目標變數,
    全域性作用域中就會建立一個具有該名稱的變數,並將其返還給引擎,前提是程式執行在非 “嚴格模式”下。

  2. ReferenceError 同作用域判別失敗相關,而 TypeError 則代表作用域判別成功了,但是對 結果的操作是非法或不合理的。

  3. 在嚴格模式的程式中,eval(..) 在執行時有其自己的詞法作用域,意味著其 中的宣告無法修改所在的作用域

  4. 使用 let 進行的宣告不會在塊作用域中進行提升
    變數和函式宣告從它們在程式碼中出現的位置被“移動”到了最上面。這個過程就叫作提升。
    只有宣告本身會被提升,而賦值或其他執行邏輯會留在原地.並且函式會優先提升.

5.詞法作用域和動態作用域的區別. 好在 javascript並不具有動態作用域.
主要區別:詞法作用域是在寫程式碼或者說定義時確定的,而動態作用域是在執行時確定的。
詞法作用域關注函式在何處宣告,而動態作用域關注函式從何處呼叫。

this和物件原型

this的繫結規則

  1. 預設繫結.
    獨立呼叫函式時,會預設繫結到全域性物件. 嚴格模式下,全域性物件無法使用預設繫結,因此this會繫結到undefined
    注意: nodejs環境下的this指向的是 module.exports,預設為{}
    但是函式中預設的this,指向的是 global 物件.

  2. 隱式繫結
    函式呼叫的位置有上下文物件,this會繫結到這個上下文物件.
    主要注意的是要小心隱式丟失.

  3. 顯式繫結
    call apply 方法
    bind 方法

  4. new 繫結

優先順序是 4 > 3 > 2 > 1

javsscript裡面的型別

  1. js有7種內建型別
    string number boolean undefined object null symbol

typeof 'aaa' // string
typeof 3 // number
typeof true // boolean
typeof undefined //undefined

let a = {};
typeof a // object

注意:
typeof null // object

let func = function(){}
typeof func // function

  1. 內建物件
    String Number Boolean Object Function Array Date RegExp Error
    其實只是一些內建函式.

注意基本型別和這些內建物件的區別,如:
let str = 'I am a string'; // 又叫字串字面量
typeof str; // string
str instanceof String; // false

let str2 = new String('I am a string');
typeof str2; // object
str2 instanceof String; // true

檢查上面這些內建物件的方法:
Object.prototype.toString.call(str2); // [object String]

如下程式碼會自動把字面量轉換成String物件,所以可以訪問屬性和方法.
let str = 'I am a string';
console.log(str.length);
console.log(str.charAt(3)); // 'm'

string 對應的構造形式 String
number 對應的構造形式 Number
boolean 對應的構造形式 Boolean

null 和 undefined 沒有對應的構造形式.

  1. 屬性
    在物件中,屬性名永遠都是字串。如果你使用 string(字面量)以外的其他值作為屬性 名,那它首先會被轉換為一個字串。即使是數字也不例外,雖然在陣列下標中使用的的 確是數字,但是在物件屬性名中數字會被轉換成字串,所以當心不要搞混物件和陣列中 數字的用法.

es6添加了可計算屬性名,可以在文字形式中使用[]包裹一個表示式來當作屬性名.

var prefix = "foo";
var myObject = {
    [prefix + "bar"]:"hello", 
    [prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world

Object.assign 只會進行淺拷貝.

  1. 屬性描述符
var myObject = {
    a:2
};
let pd = Object.getOwnPropertyDescriptor( myObject, "a" );
// pd:
// {
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true 
// }

使用 Object.defineProperty(..)來新增一個新的屬性或者修改一個已有的屬性.

a. writable
如果設定成 false,則不能修改值了.

b. configurable
如何 configurable 為false, 則後面就不能將這個屬性的 configurable 設定成true了,
並且這個屬性也不能被刪除. 但是可以被修改.
同時, 只能將 writable 的狀態從 true改成false,不能由 false 改成 true.

c. enumerable
設定成false,則不會出現在for..in迴圈中. 但是還是可以正常訪問.
propertyIsEnumerable 這個方法可以判斷屬性名是否可列舉,並且不會檢查原型鏈

  1. 屬性不變性

a. 將屬性的 writabel 和 configurable 都設定成false即可.
那麼這個屬性就不可被修改和重定義或者刪除.

b. 禁止擴充套件
Object.preventExtensions(..); 只是不能新增新的屬性,但是可以修改屬性的值,
還可以刪除屬性.

c. 密封
Object.seal(..), 這個方法會呼叫Object.preventExtensions,然後將現有屬性的configurable設定成false.
但是可以修改屬性的值,不能刪除屬性.

d.凍結
Object.freeze(..),
這個方法實際會呼叫Object.seal(),並且把所有的屬性的writable設定成false, 這是最高級別的不變性.

  1. [[Get]]/[[Put]]
    set get的定義.

  2. 存在性
var myObject = { 
    a:2
};
 ("a" in myObject); // true
 ("b" in myObject); // false
 myObject.hasOwnProperty( "a" ); // true
 myObject.hasOwnProperty( "b" ); // false

in 操作符會檢查屬性是否在物件及其原型鏈中(不管列舉屬性是否為true,都會判斷).
hasOwnProperty 則不會檢查原型鏈.

Object.keys(..) 會返回一個數組,包含所有可列舉屬性,
Object.getOwnPropertyNames(..) 會返回一個數組,包含所有屬性,無論它們是否可列舉。

in 和 hasOwnProperty(..) 的區別在於是否查詢 [[Prototype]] 鏈,
然而,Object.keys(..) 和 Object.getOwnPropertyNames(..) 都只會查詢物件直接包含的屬性。

8.遍歷
for..in 用來遍歷物件的可列舉屬性(包含原型鏈,要求列舉屬性必須為true,注意和 in 操作符的區別).
但是如果要遍歷值,則使用 foreach every some 等.

es6 的 for..of 可以用來遍歷陣列的值.
for..of 還可以用來遍歷實現了 iterator 的物件.

混合物件"類"

  1. 顯示混入
function mixin(sourceObj,targetObj){
    for(let k in sourceObj){
        if(!(k in targetObj)){
            targetObj[k] = sourceObj[k];
        }
    }
    return targetObj;
};

這種是淺複製,如果有陣列或者物件或者函式(函式也是物件),則會同時影響sourceObj和targetObj.

  1. 寄生繼承

  2. 隱式混入

  3. 原型鏈 屬性遮蔽
let another = { a : 2};
let myobj =  Object.create(another);

console.log(myobj); // {}
console.log(myobj.hasOwnProperty("a"));  // false
myobj.a = 5;   
console.log(myobj.hasOwnProperty("a")); // true
console.log(myobj);  {a:5}

屬性遮蔽規則:
如:myobj.a = 5;
a.如果myobj包含a屬性,則不管原型鏈上是否有a屬性, 都會直接應用到myobj物件上,而不會影響原型鏈上的a.

b.如果myobj本身不包含a屬性,則會遍歷原型鏈,如果原型鏈上也沒有a屬性,則a會被新增到myobj物件上.

c.如果myobj本身不包含a屬性,但是原型鏈上有a屬性,並且a屬性的 writable為true,則會直接新增到myobj物件上,但是不會影響原型鏈上的a(屬性遮蔽),如果原型鏈上的a屬性的writable為false,非嚴格模式下不會產生效果,嚴格模式下報錯.

d.如果myobj本身不包含a屬性,但是原型鏈上a是一個setter,則會呼叫這個setter,而a屬性不會被新增myobj上.

總結: 如果要給myobj新增a的屬性去遮蔽原型鏈上的屬性a, 則使用Object.defineProperty(..)
注意 隱式遮蔽. 如:
myobj.a++,相當於 myobj.a = myobj.a + 1;

function Foo(){}
let foo = new Foo();

使用 new 來呼叫函式,或者說發生建構函式呼叫時,會自動執行下面的操作。

  1. 建立(或者說構造)一個全新的物件。
  2. 這個新物件會被執行[[原型]]連線。
  3. 這個新物件會繫結到函式呼叫的this。
  4. 如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件。