對象轉基本類型
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屬性重新賦值,但也絲毫不影響之前賦值結果,按值傳遞
你傳進的一個對象 對象在內存地址中,即使你再外部賦值,但是內部 改變了,你外部就等於沒有賦值
當取值為百分比時,需要特別注意:百分比不是相對於父元素的高度的,而是相對於父元素的寬度的。height
、top
的百分比取值,總是相對於父元素的高度。
padding-top
、margin-top
、padding-bottom
、margin-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名
數組的方法
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的渲染
如果有多個設置了
defer
的script
標簽存在,則會按照順序執行所有的script
;defer
腳本會在文檔渲染完畢后,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
是通過setAttribute
和getAttribute
來設置的,使用的是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)則與無返回值相同,實際返回其實例化對象。
若返回值是引用類型,則實際返回值為這個引用類型。