javascript基礎的查缺補漏


對象轉基本類型

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

優先級: Symbol.toPrimitive>valueOf>toString

'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的內容而不是去看構造函數的屬性	

如何成為全核工程師

先精后廣,一專多長

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

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

當有一個操作數是字符串時

  • 如果兩個操作數都是字符串,將兩個字符串拼接起來
  • 如果只有一個操作符石字符串,則另一個操作數轉換為字符串(toString)

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

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

[]+{}

滿足a有對象/數組,所有都轉換為字符串拼接
""+"[object object]"="[object Object]"

1+{}

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

{}+[]

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

{}+{}

"[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 ,所以21為false

'2'true '2' 隱式轉化為2 2true 為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)則與無返回值相同,實際返回其實例化對象。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM