javascript基礎的查缺補漏

對象轉基本類型

let a = {
  valueOf() {
    return 0;
  },
  toString() {
    return '1';
  },
  [Symbol.toPrimitive]() {
    return 2;
  }
}
1 + a // => 3
'1' + a // => '12'

優先級: Symbol.toPrimitive>valueOf>toStringjavascript

'a'++'b'
由於+'b' 會被強制轉換成NaN

function Foo() {
    return this;
}
Foo.getName = function () {
    console.log('1');
};
Foo.prototype.getName = function () {
    console.log('2');
};

new Foo.getName();   // -> 1
new Foo().getName(); // -> 2
優先級的順序
new (Foo.getName());
(new Foo()).getName();
//new Foo() 是返回return的內容而不是去看構造函數的屬性

如何成爲全核工程師

先精後廣,一專多長css

[]==false, ![]==true

js中隱式強制類型轉換
轉化爲數字類型  false等於0, true等於1
若是是對象,先調用valueOf,若是沒有用調用toString,這個過程叫ToPrimitive()
NaN==NaN  false
首先分析[]==![]
Boolean() 判斷 0、-0、null、false、NaN、undefined、或者空字符串(""),則生成的 Boolean 對象的值爲 false,
![] 變成!Boolean([]) 也就是!true,也就是false

[] + {} 和 {} + []同樣嗎

當有一個操做數是字符串時html

  • 若是兩個操做數都是字符串,將兩個字符串拼接起來
  • 若是隻有一個操做符石字符串,則另外一個操做數轉換爲字符串(toString)

當有一個操做數是複雜數據類型(對象,數組)時,將兩個操做數都轉換爲字符串(toString)相加vue

有一個操做數是簡單數據類型(true/false, null,undefined) 時,同時不存在複雜數據類型和字符串,則將兩個操做數都轉換成數值(ToNumber)相加。java

[]+{}node

知足a有對象/數組,全部都轉換爲字符串拼接
""+"[object object]"="[object Object]"

1+{}react

知足第三條 a是對象/數組
"1[object Object]"

{}+[]git

"[object Object]"+toNumber([])
"[object Object]"+0

{}+{}github

"[object Object][object Object]"

函數的name屬性

經過構造函數方式建立的函數name都是 'anonymous'

使用bind方法獲得一個新的函數

function ff(){};
var f=ff.bind();
f.name 是bound ff

閉包

做用域是定義時的做用域,而不是執行時的做用域面試

閉包的使用場景

函數做爲返回值

function F1(){
  var a = 100
  //返回一個函數(函數做爲返回值)
  return function(){
      console.log(a)
  }
}

函數做爲參數傳遞

function F1(){
  var a = 100
  return function(){
      console.log(a)   //自由變量,父做用域尋找
  }
}

一個函數執行若是造成一個不銷燬做用域,保護裏面的私有變量或者存儲私有變量,可是閉包容易引發內存泄露

形成內存泄露的狀況:

  • 全局變量
  • 不銷燬做用域
function fn(){
    var a=1;
    return function(){
        a++
    }
}

做用域

全局:關閉瀏覽器的時候銷燬
私有: 看是否返回地址而且被佔用了,決定是否銷燬
var 是沒有塊級做用域的

做用域鏈

是一個查找過程: 在一個私有做用域下遇到變量了,先看是否是本身私有的,若是不是往上一級找,沒有繼續找一隻找到window下爲之,沒有就報錯

上一級做用域

看當前整個做用域對應個人地址是在哪個做用域下定義的,那個做用域就是當前這個做用域的上一級

塊級做用域

{}   if(){}    for(){}   while(){}

let 和const 定義的變量屬於一個私有做用域,變量是私有變量

實例便可以經過構造函數中this.屬性的方式獲得私有屬性還能夠經過__proto__拿到所屬類的原型的公有屬性

詞法做用域

常見的變量

javaScript 引擎

javaScript引擎是谷歌的v8引擎,這個引擎由兩個部分組成

  • 內存堆:這是內存分配發生的地方
  • 調用棧:這是你的代碼執行的地方

建立執行上下文有兩個階段

  • 建立階段
  • 執行階段

在建立階段會發生三件事

  • this值的決定,就是this綁定
  • 建立詞法環境組件
  • 建立變量組件

this的綁定

在全局執行上下文中,this的值指向全局對象,(在瀏覽器中,this引用window對象)

在函數執行上下文,this的值取決於該函數式如何被調用的,若是它被一個引用對象調用,那麼this會被設置成那個對象,不然this的值被設置全局對象或者undefined(在嚴格模式下)

詞法環境內部有兩個組件

  • 環境記錄器
  • 一個外部環境的引用

環境記錄器是存儲變量和函數聲明的實際位置

外部環境的引用意味着他能夠訪問其父級詞法環境(做用域)

詞法環境有兩種類型

全局環境(在全局執行上下文中)是沒有外部環境引用的詞法環境,

函數環境中,函數內部用戶定義的變量存儲在環境記錄器中,而且引用的外部環境多是全局環境,或者任何包含此內部函數的外部函數。

簡而言之

  • 在全局環境中,環境記錄器是對象環境記錄器
  • 在函數環境中,環境記錄器是聲明式環境記錄器

值類型

// 值類型:Number、string、bollean、undefined
var a = 100
var b = a
a = 200
console.log(b) // 100 保存與複製的是值自己
typeof abc      //"undefined"
typeof null    //"object"
爲何要說呢?由於我錯了好屢次
typeof區分不了引用類型(除了函數)
用instanceof 來區分引用類型
alert(person instanceof Object); // 變量 person 是 Object 嗎?

alert(colors instanceof Array); // 變量 colors 是 Array 嗎?

alert(pattern instanceof RegExp); // 變量 pattern 是 RegExp 嗎?

引用傳值

值傳遞和引用傳遞

function addNum(num)
{ 
 num+=10; 
 return num; 
} 
var num=10; 
var result=addNum(num); 
console.log(num);//10
console.log(result);//20
當爲函數傳遞參數的時候,是將此值複製一份傳遞給函數,因此在函數執行以後,num自己的值並無改變,函數中的被改變的值僅僅是副本而已

function mutate(obj) {
  obj.a = true;
}

const obj = {a: false};
mutate(obj)
console.log(obj.a); // 輸出 true

在值傳遞的場景中,函數的形參只是實參的一個副本(至關於a拷貝了一份),當函數調用完成後,並不改變實參
在引用傳遞的場景中,函數的形參和實參指向同一個對象,當參數內部改變形參的時候,函數外面的實參也被改變

function setName(obj){
    obj.name = '嘉明';
    obj = new Object();
    obj.name = '龐嘉明';
}
var person = new Object();
setName(person);
console.log(person.name); // '嘉明',爲啥不是'龐嘉明'呢?
新建的對象 obj = new Object()
它本身空間保存的地址將會被新的對象的存儲地址所覆蓋,由於是傳值不是引用,因此它不會影響到student空間所保存的地址,故最後雖然對obj的name屬性從新賦值,但也絲絕不影響以前賦值結果,按值傳遞
你傳進的一個對象 對象在內存地址中,即便你再外部賦值,可是內部 改變了,你外部就等於沒有賦值

當取值爲百分比時,須要特別注意:百分比不是相對於父元素的高度的,而是相對於父元素的寬度的heighttop的百分比取值,老是相對於父元素的高度

padding-topmargin-toppadding-bottommargin-bottom取值爲百分比時,是相對於父元素的寬度

fixed問題

一提到position:fixed,天然而然就會想到:相對於瀏覽器窗口進行定位。

但其實這是不許確的。若是說父元素設置了transform,那麼設置了position:fixed的元素將相對於父元素定位,不然,相對於瀏覽器窗口進行定位。

JavaScript 的怪癖

隱式轉換

隱式轉換爲Boolean

if語句

字符串的隱式轉換

加運算符(+) ,可是隻要其中一個操做數是字符串,那麼它執行鏈接字符串的操做

爲何 ++[[]][+[]]+[+[]] = 10?

拆分
++[[]][+[]]
+
[+[]]
繼續拆分

++[[]][0]
+
[0]

繼續拆分

+([] + 1)
+
[0]

+([] + 1) === +("」 + 1),而且 
+("」 + 1) === +("1"),而且 
+("1") === 1 

最後簡化爲
1+[0]
[]==''  [0]=='0', [1]=='1'
因此 1+'0' ==='10'

2==true爲何是false

由於==是比較值類型是否相等,true轉換爲數字是1 ,因此2==1爲false

'2'==true '2' 隱式轉化爲2 2==true 爲false

null >=0 ? true:false

null == 0  // false  null在設計上,在此處不嘗試轉型. 因此 結果爲false. 
null > 0  // false   null 嘗試轉型爲number , 則爲0 . 因此結果爲 false, 
null >= 0  // true 
null<=0     //true
那麼你看
-null == 0  // true
+null == 0 // true
Number(null) == 0  // true

你不知道的JavaScript續集

數組

使用delete運算符能夠將單元從數組中刪除,可是請注意,單元刪除後,數組的length屬性並不會發生變化。
數組經過數字進行索引,但有趣的是它們也是對象,因此也能夠包含字符串鍵值和屬性(但這些並不計算在數組長度內)

var a = [ ];
a[0] = 1;
a["foobar"] = 2;
a.length; // 1
a["foobar"]; // 2
a.foobar; // 2
若是字符串鍵值可以被強制類型轉換爲十進制數字的話,它就會被當作數字索引來處理
var a = [ ];
a["13"] = 42;
a.length; // 14

類數組
var a = { '0': 1, '1': 2, '2': 3, length: 3 };
function foo() {
    var arr = Array.prototype.slice.call(arguments);
    arr.push("bam");
    console.log(arr);
}
foo("bar", "baz"); // ["bar","baz","bam"]
同時用Array.from() 也能實現一樣的功能
對僞數組或可迭代對象(包括arguments Array,Map,Set,String…)轉換成數組對象

字符串

JavaScript中字符串是不可變的,而數組是可變的。

特殊的數字

var a=2/'foo';
a==NaN //false 
由於NaN===NaN  //false
可使用全局isNaN() 來判斷一個值是不是NaN
可是 isNaN('abc')  //true
ES6開始咱們使用Number.isNaN()
var a = 2 / "foo";
var b = "foo";
Number.isNaN( a ); // true
Number.isNaN( b ); // false——好!

無窮數
var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity

零值    常規的0(也叫+0)和-0
var a = 0 / -3; // -0
var b = 0 * -3; // -0
從字符串轉換爲數字
+"-0"; // -0
Number( "-0" ); // -0
JSON.parse( "-0" ); // -0
咱們爲何須要負零呢
數字的符號位用來表明其餘信息(好比移動的方向)
此時若是一個值爲0的變量失去了它的符號位,它的方向信息就會丟失。

值和引用

var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// 而後
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
因爲引用指向的是值自己而非變量,因此一個引用沒法更改另外一個引用的指向

function foo(x) {
    x.push(4);
    x; // [1,2,3,4]
    // 而後
    x = [4, 5, 6];
    x.push(7);
    x; // [4,5,6,7]
}
var a = [1, 2, 3];
foo(a);
a; // 是[1,2,3,4],不是[4,5,6,7]
咱們沒法自行決定使用值複製仍是引用複製,一切由值得類型來決定。
若是經過值複製的方式來傳遞複合值(如數組),就須要爲其建立一個複本,這樣傳遞的就再也不是原始值
foo(a.slice())

若是要將標量基本類型值傳遞到函數內並進行更改,就須要將該值封裝到一個複合值(對象、數組等)中,而後經過引用複製的方式傳遞。
function foo(wrapper) {
    wrapper.a = 42;
}
var obj = {
    a: 2
};
// var obj=new Object();  obj.a=2;
foo(obj);
obj.a; // 42
與預期不一樣的是,雖然傳遞的是指向數字對象的引用複本,但咱們並不能經過它來更改其中的基本類型值

function foo(x) {
    x = x + 1;
    x; // 3
}
var a = 2;
var b = new Number(a); // Object(a)也同樣
foo(b);
console.log(b); // 是2,不是3
只是多數狀況下咱們應該優先考慮使用標量基本類型

封裝對象包裝

var a=Boolean(false);
var b=new Boolean(false);
if (!b) {
console.log( "Oops" ); // 執行不到這裏
}
咱們爲false建立了一個封裝對象,然而該對象是真值,因此這裏使用封裝對象獲得的結果和使用false截然相反
若是想要自行封裝基本類型值,可使用Object(...)函數(不帶new關鍵字)
var a = "abc";
var b = new String( a );
var c = Object( a );
typeof a; // "string"
typeof b; // "object"
typeof c; // "object"
b instanceof String; // true
c instanceof String; // true

拆封

可使用ValueOf()函數
var a = new String( "abc" );
var b = new Number( 42 );
var c = new Boolean( true );
a.valueOf(); // "abc"
b.valueOf(); // 42
c.valueOf(); // true

使用隱式拆封
var a = new String( "abc" );
var b = a + ""; // b的值爲"abc"
typeof a; // "object"
typeof b; // "string"

原生函數做爲構造函數

關於數組(array)、對象(object)、函數(function)和正則表達式,咱們一般喜歡以常量的形式來建立它們。實際上,使用常量和使用構造函數的效果是同樣的(建立的值都是經過封裝對象來包裝)

var a = new Array( 1, 2, 3 );
a; // [1, 2, 3]
var b = [1, 2, 3];
b; // [1, 2, 3]
構造函數Array(..)不要求必須帶new關鍵字。不帶時,它會被自動不上。所以Array(1,2,3)和new Array(1,2,3)的效果是同樣的。
Array構造函數只帶一個數字參數的時候,該參數會被做爲數組的預設長度(length),而非只充當數組中的一個元素

表達式的反作用

var a = 42;
var b = a++;
a; // 43
b; // 42 這是a++的反作用

========================
function foo() {
    a = a + 1;
}
var a = 1;
foo(); // 結果值:undefined。反作用:a的值被改變

你懂 JavaScript 嗎?

toJSON

var obj = {
    key: 'foo',
    toJSON: function () {
        return 'bar';
    }
};
var ret = JSON.stringify(obj);
console.log(ret);
區別很是明顯,toJSON 的返回值直接代替了當前對象

Number

undefined   ---> NaN
null        ---> 0
boolean 的true爲1  false便是0
string  -->數字或NaN
object      若定義valueOf優先用,其次toString

Number(undefined) // NaN
Number(null) // 0
Number(true) // 1
Number(false) // 0
Number('12345') // 12345
Number('Hello World') // NaN
Number({ name: 'Jack' }}) // NaN

const a = {
  name: 'Apple',
  valueOf: function() {
    return '999'
  }
}

Number(a) // 999

const a = new String('');
const b = new Number(0);
const c = new Boolean(false);

!!a // true
!!b // true
!!c // true

parseInt

參數:第一個參數是string 第二個參數是介於2和36之間的整數,一般默認爲10,也就是咱們一般使用的十進制轉換,若是是5就是5進制,超出這個範圍,則返回NaN。若是第二個參數是0、undefined和null,則直接忽略
* 將字符串轉爲整數
* 若是字符串頭部有空格,空格會被自動去除
* 若是參數不是字符串,先轉爲字符串再轉換
parseInt('12px')  若是遇到不能轉爲數字的字符,就再也不進行下去,返回轉好的部分
若是字符串的第一個字符不能轉化爲數字(後面跟着數字的正負號除外),返回NaN。
若是開頭是0x按照16進制轉換,若是是0按照10進制轉換

又犯錯了一次

const a = true;
const b = 123;

a === b // false
a == b // false
true強制轉換爲1

const a = '1,2,3';
const b = [1,2,3];
a === b // false
a == b // true
在a == b當中,陣列a因爲沒有valueOf(),只好使用toString()取得其基型值而獲得字串'1,2,3',此時就可比較'1,2,3' == '1,2,3',所以是相等的(true)。

Object(null) 和Object(undefined) 等同於Object()也就是{}
var a = null;
var b = Object(a); // 等同於 Object()
a == b; // false

var c = undefined;
var d = Object(c); // 等同於 Object()
c == d; // false

var e = NaN;
var f = Object(e); // 等同於 new Number(e)
e == f;//false

避免修改原型的valueOf
Number.prototype.valueOf = function() {
  return 3;
};

new Number(2) == 3; // true

抽象的關係運算符

  • a <= b實際上是!(b > a),所以!false獲得true。
  • a >= b實際上是b <= a也就是!(a > b)等同於!false獲得true
const a = { b: 12 };
const b = { b: 13 };

a < b // false,'[object Object]' < '[object Object]'
a > b // false,其實是比較 b < a,即 '[object Object]' < '[object Object]'
a == b // false,其實是比較兩物件的 reference

a >= b // true
a <= b // true
`[]==[]`  false 由於兩個的地址不是同樣的

`'ab' < 'cd' // true `  以字典的字母順序形式進行比較

'Hello World' > 1 // false,字串 'Hello World' 沒法轉化爲數字,變成了NaN
NaN 不大於、不小於、不等於任何值,固然也不等於本身

圖解構造器Function和Object的關係

//①構造器Function的構造器是它自身
Function.constructor=== Function;//true

//②構造器Object的構造器是Function(由此可知全部構造器的constructor都指向Function)
Object.constructor === Function;//true

//③構造器Function的__proto__是一個特殊的匿名函數function() {}
console.log(Function.__proto__);//function() {}

//④這個特殊的匿名函數的__proto__指向Object的prototype原型。
Function.__proto__.__proto__ === Object.prototype//true

//⑤Object的__proto__指向Function的prototype,也就是上面③中所述的特殊匿名函數
Object.__proto__ === Function.prototype;//true
Function.prototype === Function.__proto__;//true

Function instanceof Object 和 Object instanceof Function 運算的結果固然都是true啦

全部的構造器的constructor 都指向Function

Function 和prototype指向一個特殊匿名函數,而這個特殊匿名函數的 __proto__ 指向 Object.prototype

### Array.of

Array.of方法用於將一組值,轉換爲數組。
Array.from()
Array.from方法用於將兩類對象轉爲真正的數組:相似數組的對象和可遍歷(iterator)的對象(包括Map和Set)

建立包含N個空對象的數組

Array(3).fill().map(()=>({}))
Array.apply(null,{length:3}).map(()=>({}))

函數表達式

函數表達式,則必須等到解析器執行到它所在的代碼行,纔會真正被解析。
console.log(sum(10 , 10)); //TypeError: sum is not a function
var sum = function(num1 , num2){
    return num1 + num2;
}

Javascript中Y組合子

遞歸就是函數不斷調用自身
階乘
let factorial=n=>n?factorial(n-1)*n:1;

const factorial = n => n === 1 ? 1 : n * factorial(n - 1)
優化
尾遞歸: 調用自身函數,計算僅用常量棧空間
const factorial = (n, total) => n === 1 ? total : factorial(n - 1, n * total)
優化
柯里化,將尾遞歸變爲只接受單個參數的變量
const fact = (n, total = 1) => n === 1 ? total : fact(n - 1, n * total)

Lambda函數(匿名函數)
// ES5
var f = function (x) {
  return x;
};

// ES6
const f = x => x
lambda表達式寫出遞歸(匿名函數遞歸)
將lambda表達式做爲參數之一傳入其身
const factorial= (f,n) => n===1 ? 1 : n*f(f,n-1);
factorial(factorial,6)
//這個也太難看了,解決方案柯里化
// 這塊不怎麼好懂我就忽略了

Lambda演算
Lambda演算中全部函數式匿名的,它們沒有名稱,只接受一個輸出變量,即獨參函數
構建一個高階函數,它接受一個函數做爲參數,並讓這個函數將自身做爲參數調用其自身:
const invokeWithSelf = f => f(f)
寫個遞歸
const fact = (n, total = 1) => n === 1 ? total : fact(n - 1, n * total)
拿到前面的進行優化
const fact = f => (total = 1) => n => n === 1 ? total : f(f)(n * total)(n - 1)
const factorial = fact(fact)()

factorial(6) // => 720
構建Y
const fact = f => (total = 1) => n => n === 1 ? total : f(n * total)(n - 1)

const Y = f => (x => f(v => x(x)(v)))
               (x => f(v => x(x)(v))) // 瞧,這不就是黑魔法Y組合子嘛

const factorial = Y(fact)()

factorial(6) // => 720

尾調用優化

尾調用時指在函數return的時候調用一個新的函數,因爲尾調用的實現須要存儲到內存中,在一個循環體中,若是存在函數的尾調用,你的內存可能爆滿或溢出。
尾調用實際用途——遞歸函數優化
在ES5時代,咱們不推薦使用遞歸,由於遞歸會影響性能。

可是有了尾調用優化以後,遞歸函數的性能有了提高。
const factorial = (n, total) => n === 1 ? total : factorial(n - 1, n * total)

let

let和const都可以聲明塊級做用域,let的特色是不會變量提高,而是被鎖在當前塊中。
 function test() {
        if(true) {
          console.log(a)//TDZ,俗稱臨時死區,用來描述變量不提高的現象
          let a = 1
        }
    }
    test()  // a is not defined

    function test() {
        if(true) {
          let a = 1
        }
        console.log(a)
    }    
    test() // a is not defined
    
 臨時死區的意思是在當前做用域的塊內,在聲明變量前的區域叫作臨時死區。

Object.is()

用來解決JavaScript中特殊類型 == 或者 === 異常的狀況。
Object.is()來處理2個值的比較。
    console.log(Object.is(NaN, NaN)) // true
    console.log(Object.is(+0, -0)) // false
    console.log(Object.is(5, "5")) //false

解構賦值

function test(value) {
    console.log(value);
}
test({a=1,b=2}={a:2,b:3});

yield使用限制

yield只能夠在生成器函數內部使用,若是在非生成器函數內部使用,則會報錯。
   function *createIterator(items) {
        //你應該在這裏使用yield
      items.map((value, key) => {
        yield value //語法錯誤,在map的回調函數裏面使用了yield
      })
    }
    const a = createIterator([1, 2, 3]);
    console.log(a.next()); //無輸出
    
在對象中添加生成器函數
    const obj = {
      a: 1,
      *createIterator() {
        yield this.a
      }
    }
    const a = obj.createIterator();
    console.log(a.next());  //{value: 1, done: false}

函數的caller

caller : 當前這個函數在哪一個函數調用的
function fn(){
    console.log(fn.caller);
}
function ff() {
    fn();
}
ff();//[Function: ff]
arguments.callee  就是當前函數自己
function fn(){
    console.log(argument.callee) 
}
fn.prototype.constructor===fn;//true  ,也表明的是函數自己

捕獲和冒泡

xxx.onclick=function(){} //DOM0事件綁定,給元素的事件行爲綁定方法,這些方法在事件傳播的冒泡階段(或者目標階段)執行的
xxx.addEventListener('xxx',function(){},false)
//第三個參數false也是控制綁定的方法在事件傳播的冒泡階段執行,可是在捕獲階段執行沒有實際意義,默認是false,能夠不寫

DOM0和DOM2的運行機制

DOM0事件綁定的原理:就是給元素的某一個事件私有屬性賦值(瀏覽器會創建監聽機制,當咱們出發元素的某個行爲,瀏覽器會本身把屬性中賦的值去執行)

DOM0事件綁定:只容許給當前元素的某個事件行爲綁定一個方法,屢次綁定後面的內容會替換前面綁定的,以最後一次綁定的方法爲主

DOM0事件綁定和DOM2事件綁定的區別

機制不同

  • DOM0採用的是私有屬性賦值,全部只能綁定一個方法
  • DOM2採用的是事件池機制,因此能綁定屢次方法

移出的操做

let list = document.querySelector('#list');
    list.addEventListener('click',function (ev) {
        console.log(ev.target.innerHTML);
    })
    list.addEventListener('click',function () {
        console.log(2);
    })

    box.onclick=function(){}
    box.onclick=null// DOM0的移出(不須要考慮綁定的是誰)

//DOM2移出的時候
    function f3() {
        console.log(2);
    }
    list.addEventListener('click',f3);
    list.removeEventListener('click',f3);
        //DOM2移出的時候,必要清除移出的是哪一個方法技巧(不要綁定匿名函數,都綁定實名函數)

DOM0和DOM2是能夠同時使用,由於是瀏覽器的兩個運行機制,執行順序和編寫順序有關

mouseenter和mouseover的區別

1. over屬於滑過事件,從父元素進入子元素,屬性離開父親,會觸發父元素的out,觸發子元素的over
enter屬於進入,從父元素進入子元素,並不算離開父元素,不會觸發父元素的leave,觸發子元素的enter
2. enter和leave阻止了事件的冒泡傳播,而over和out還存在冒泡傳播的

全部對於父元素嵌套子元素的這種狀況,咱們用enter的使用會比over多一些

事件委託(事件代理)

給容器的click綁定一個方法,經過事件的冒泡傳播機制,把容器的click行爲觸發,根據事件對象中的事件源(ev.target)來作不一樣業務處理

<ul id="list">
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
    <li>item n</li>
</ul>
<script>
    let list = document.querySelector('#list');
    list.onclick=function (ev) {
        let target=ev.target||window.event.target;
        console.log(target.innerHTML);
    }
</script>

JQ的事件綁定

on/off : 基於DOM2事件綁定實現事件的綁定和移除

one:只綁定一次,第一次執行完成後,會把綁定的方法移出(基於on/off完成)

click/ mouseenter/... jq提供的快捷綁定方法,可是這些方法都是基於on/off完成的

delegate 事件委託方法(在1.7之前用的是live方法)

$(document).on('click',fn)
$(document).off('click',fn)
$(document).one('click',fn)
$(document).click(fn)

自執行匿名函數

任何消除函數聲明和函數表達式間歧義的方法,均可以被解析器正確識別

針對這些一元運算符,到底用哪一個好呢,測試發現()的性能最優越

(function(){ /* code */ }());
!function(){alert('iifksp')}()        // true
+function(){alert('iifksp')}()        // NaN
-function(){alert('iifksp')}()        // NaN
~function(){alert('iifksp')}()        // -1

發佈訂閱設計模式(觀察者模式)

思想:準備一個容器,把到達指定時候要處理的事情,事先一一增長到容器中(發佈計劃,而且向計劃表中訂閱方法),當到達指定時間點,通知容器中的方法依次執行

文章

forEach和map的區別

相同點

  • forEach和map方法裏每次執行匿名函數都支持3個參數,參數分別是item(當前每一項)、index(索引值)、arr(原數組)

map

返回一個新數組,不會對空數組進行檢測,不會該變原有數組

forEach

讓數組每一項作一件事

空數組就不會執行回調函數

Promise A+規範

class Promise {
    constructor(excutorCallBack) {
        this.status = 'pending';
        this.value = undefined;
        this.fulfilledAry = [];
        this.rejectedAry = [];
        let resolveFn = result => {
            let timer=setTimeout(()=>{
                clearTimeout(timer);
                if (this.status !== 'pending') return;
                this.status = 'fulfilled';
                this.value = result;
                this.fulfilledAry.forEach(item=>item(this.value))
            })
        };
        let rejectFn = reason => {
            let timer=setTimeout(()=>{
                if (this.status !== 'pending') return;
                this.status = 'rejected';
                this.value = reason;
                this.rejectedAry.forEach(item => item(this.value));
            })
        };
        try{
            excutorCallBack(resolveFn, rejectFn());
        }catch(err){
            // 有異常信息按照rejected狀態處理
            rejectFn(err);
        }
        excutorCallBack(resolveFn, rejectFn);
    }

    then(fulfilledCallBack, rejectedCallBack) {
        //處理不傳遞的情況
        typeof fulfilledCallBack!=='function'?fulfilledCallBack=result=>result:null;
        typeof rejectedCallBack!=='function'?rejectedCallBack=reason=>{
            throw new Error(reson.message);
        }:null;
        //返回一個新的promise實例
        return new Promise((resolve,reject)=>{
            this.fulfilledAry.push(()=>{
                try{
                    let x=fulfilledCallBack(this.value);
                    x instanceof Promise?x.then(resolve,reject):resolve(x);
                    // if(x instanceof Promise){
                    //     x.then(resolve, reject);
                    //     return;
                    // }
                    // resolve(x);
                }catch(err){
                    reject(err)
                }
            });
            this.rejectedAry.push(()=>{
                try{
                    let x=rejectedCallBack(this.value);
                    x instanceof Promise?x.then(resolve,reject):resolve(x);
                    // resolve(x);
                }catch(err){
                    reject(err)
                }
            });
        });
        // this.fulfilledAry.push(fulfilledCallBack);
        // this.rejectedAry.push(rejectedCallBack);
    }

    catch(rejectedCallBack) {
        return this.then(null,rejectedCallBack)
    }

    static all(promiseAry=[]){
        return new Promise((resolve, reject)=>{
            //index:記錄成功的數量 result記錄成功的結果
            let index=0,
                result=[];
            for (let i = 0; i <promiseAry.length; i++) {
                //promiseAry[i] 每個須要處理的promise實例
                promiseAry[i].then(val=>{
                    index++;
                    result[i]=val;
                    //索引須要和promiseAry對應,保證結果的順序和數組的順序一致
                    if (index === promiseAry.length) {
                        resolve(result);
                    }
                }, reject);
            }
        });
    }
}
module.exports = Promise;

call,apply,bind串聯起來理解

cat.call(dog, a, b) = cat.apply(dog, [a, b]) = (cat.bind(dog, a, b))() = dog.cat(a, b)

本地存儲和服務器存儲

用到本地存儲的地方:

  • 頁面之間的信息通訊
  • 性能優化

session和cookie

session是服務器存儲

  • 不兼容IE8及如下
  • 也有存儲的大小限制,一個源下最多隻能存儲5MB內容
  • 本地永久存儲,只要你不手動刪除,永久存儲在本地(可是咱們能夠基於API removeItem/clear手動清除)
  • 殺毒軟件或者瀏覽器的垃圾清除暫時不會清除localStorage(新版本谷歌會清除localStorage)
  • 在隱私或者無痕瀏覽下,是記錄localStorage
  • localStorage和服務器沒有半毛錢關係

cookie是客戶端存儲

  • 兼容全部的瀏覽器
  • 有存儲的大小限制,通常一個源只能存儲4kb內容
  • cookie有過時時間(當前咱們本身能夠手動設置這個時間)
  • 殺毒軟件或者瀏覽器的垃圾清理均可能會把cookie信息強制掉
  • 在隱私或者無痕瀏覽器模式下,是不記錄cookie的
  • cookie不是嚴格的本地存儲,由於要和服務器之間來回傳輸
localStorage.gsetItem([key],[value])//[value]必須是字符串格式(即便寫的不是字符串,也會默認轉換爲字符串)
localStorage.getItem([key]) //經過屬性名獲取存儲的信息
localStorage.removeItem([key])//刪除指定的存儲信息
localStorage.clear()//清除當前域下存儲的全部信息
localStorage.key(0)//基於索引獲取指定的key名

設置cookie

數組的方法

flex

文檔

須要一個容器 display:flex
flex-direction (元素排列方向)
    row, row-reverse, column, column-reverse
flex-wrap (換行)
    nowrap, wrap, wrap-reverse
flex-flow (以上二者的簡寫)
    flex-direction || flex-wrap
justify-content (水平對齊方式)
    flex-start, flex-end, center, space-between, space-around
align-items (垂直對齊方式)
    stretch, flex-start, flex-end, center, baseline
align-content (多行垂直對齊方式)
    stretch, flex-start, flex-end, center, space-between, space-around

遞歸的本質是棧的讀取

在算法中咱們會遇到不少遞歸實現的案例,全部的遞歸均可以轉換成非遞歸實現,

其轉換的本質是:遞歸是解析器(引擎)來幫咱們作了棧的存取,非遞歸是手動建立棧來模擬棧的存取過程

遞歸組件能夠轉換成扁平數組來實現:

更改DOM結構成平級結構,點擊節點以及節點的視覺樣式經過操做總的list數據區實現

而後使用虛擬長列表來控制vue組組建實例建立的數量

性能優化

減小DNS查找,避免重定向

DNS:負責將域名URL轉化爲服務器主機IP
DNS查找流程:首先查看瀏覽器緩存是否存在,不存在則訪問本機DNS緩存,再不存在則訪問本地DNS服務器。因此DNS也是開銷,一般瀏覽器查找一個給定URL的IP地址要花費20-120ms,在DNS查找完成前,瀏覽器不能從host那裏下載任何東西。 
當客戶端的DNS緩存爲空時,DNS查找的數量與WEB頁面中惟一主機名的數量相等,因此減小惟一主機名的數量就能夠減小DNS查找的數量

資源async defer

defer

若是script設置了該屬性,則瀏覽器會異步的下載該文件,而且不會影響後續DOM的渲染

若是有多個設置了deferscript標籤存在,則會按照順序執行全部的scriptdefer腳本會在文檔渲染完畢後,DOMContentLoaded事件調用前執行。

<script defer src='1.js'></script>
<script>
    window.addEventListener('DOMContentLoader',function(){
    console.log('DOMContentLoader')
})
 </script>

async

async的設置,會使得script腳本異步的加載並在容許的狀況下執行 async的執行,

並不會按着script在頁面中的順序來執行,而是誰先加載完誰執行。

推薦使用場景

defer 若是你的腳本代碼依賴於頁面中的DOM元素(文檔是否加載解析完畢),或者被其餘腳本文件依賴

  • 評論框 代碼語法高亮

頁面靜態直出

  • 就是瀏覽器直接輸出渲染好數據的html頁面(簡稱直出)
  • 直出就是須要node.js的支持,服務器上的瀏覽器渲染好的東西,直接輸出給客戶端的瀏覽器
  • 簡單來講,就是直接把配件選好,讓店家幫忙組裝器,一次性發過來,就是直出這個道理

重讀《JavaScript高級程序設計》

arguments對象是類數組

apply()方法接受兩個參數:一個是運行函數的做用域,另外一個是參數數組,這個參數數組能夠是Array實例,也能夠是arguments對象(類數組對象)

function sum(num1 , num2){
    return num1 + num2;
}
function callSum1(num1,num2){
    return sum.apply(this,arguments); // 傳入arguments類數組對象
}
function callSum2(num1,num2){
    return sum.apply(this,[num1 , num2]); // 傳入數組
}
console.log(callSum1(10 , 10)); // 20
console.log(callSum2(10 , 10)); // 20

Object.create()和new object()和{}的區別

Object.create()

  • Object.create(null) 建立的對象是一個空對象,在該對象上沒有繼承 Object.prototype 原型鏈上的屬性或者方法

  • Object.create()方法接受兩個參數:Object.create(obj,propertiesObject) ;

    obj:一個對象,應該是新建立的對象的原型。

    propertiesObject:可選。該參數對象是一組屬性與值,該對象的屬性名稱將是新建立的對象的屬性名稱,值是屬性描述符(這些屬性描述符的結構與Object.defineProperties()的第二個參數同樣)。注意:該參數對象不能是 undefined,另外只有該對象中自身擁有的可枚舉的屬性纔有效,也就是說該對象的原型鏈上屬性是無效的。

    var o = Object.create(Object.prototype, {
      // foo會成爲所建立對象的數據屬性
      foo: { 
        writable:true,
        configurable:true,
        value: "hello" 
      },

面試

跨標籤頁通信??

瀏覽器下事件循環

事件循環是指:執行一個宏任務,而後執行清空微任務列表,循環再執行宏任務,再清微任務列表

從輸入 url 到展現的過程

  • DNS解析

  • TCP三次握手

  • 發送請求,分析url,設置請求報文(頭,主體)

  • 服務器返回請求的文件(html)

  • 瀏覽器渲染

    • html parse==>DOM Tree

      標記化算法,進行元素狀態的標記

      dom樹構建

    • css parser==>Styel tree

      解析css代碼,生成樣式樹

    • attachment==>Render Tree

      結合dom樹與style樹,生成渲染樹

    • layout:佈局

    • GPU painting:像素繪製頁面

內存泄露

  • 意外地全局變量,沒法被回收
  • 定時器:未被正確關閉,致使所引用的外部變量沒法被釋放
  • 事件監聽:沒有正確銷燬
  • 閉包:會致使父級中的變量沒法被釋放
  • dom引用: dom元素被刪除時,內存中的引用未被正確清空

劍指offer??

描述建立一個對象的過程

  • 新生成了一個對象
  • 連接到原型
  • 綁定this
  • 返回新對象

JQ的attr和prop的區別

從源碼來看:

  • attr是經過setAttributegetAttribute來設置的,使用的是DOM屬性節點
  • prop是經過document.getElementById(el)[name]=vlaue來設置的,是轉化爲js對象的屬性
  • 經過設置checked,selected,readonly,disabled等的時候使用prop效果更好,減小了訪問dom屬性節點的頻率。
  • 通常若是是標籤自身自帶的屬性,咱們用prop方法來獲取;若是是自定義的屬性,咱們用attr方法來獲取。

DOM節點的attr和property有何區別

  • property只是一個JS對象的屬性的修改
  • Attribute是對html標籤屬性的修改

獲取當前時間

new Date().toISOString().slice(0,10)

toLocaleString()

方法返回一個字符串表示數組中的元素,數組中的元素使用各自toLocaleString方法轉成字符串,這些字符串將使用一個特定語言環境的字符串

var number = 1337;
var date = new Date();
var myArr = [number, date, "foo"];
var str = myArr.toLocaleString(); 
console.log(str); 
// 輸出 "1,337,2019/2/15 下午8:32:24,foo"

let a=3500
a.toLocaleString() //3,500

寫React/Vue項目時爲何要在組件中寫key,其做用是什麼

key的做用是爲了在diff算法執行時更快的找到對應的節點,提升diff速度

undefined.toString()

和null.toString()同樣也是報錯的,原始數據類型存儲的是值,是沒有函數能夠調用的

構造函數的返回值

  • 沒有返回值則按照其餘語言同樣返回實例化對象

  • 如有返回值則檢查其返回值是否爲引用類型。若是是非引用類型,如基本類型(string,number,boolean,null,undefined)則與無返回值相同,實際返回其實例化對象。

  • 若返回值是引用類型,則實際返回值爲這個引用類型。

相關文章
相關標籤/搜索