在三大框架盛行的時代, 基本上會個Vue
就能在小公司渾水摸魚。可是當想突破的時候就會意識到基礎的重要性。前端
JavaScript
中有不少重要特性及概念。好比原型,原型鏈,this,閉包,做用域,隱式轉換等等。若是不能熟練掌握,在進階中級前端開發工程師的道路上一定是困難重重。git
用一個小時把這些題作完。檢測一下你的基礎掌握程度。github
if(false){ var a = 1; let b = 2; } console.log(a); console.log(b);
var a = 1; if(true){ console.log(a); let a = 2; }
var a = {n: 1} var b = a a.x = a = {n: 2} console.log(a.n, b.n); console.log(a.x, b.x);
console.log(c); var c; function c(a) { console.log(a); var a = 3; function a(){ } } c(2);
var c = 1 function c(c) { console.log(c); var c = 3; } console.log(c); c(2);
var name = 'erdong'; (function () { if (typeof name === 'undefined') { var name = 'chen' console.log(name) } else { console.log(name) } })();
var a = 10; function test() { a = 100; console.log(a); console.log(this.a); var a; console.log(a); } test();
if (!(a in window)) { var a = 1; } console.log(a)
var a = 1 function c(a, b) { console.log(a) a = 2 console.log(a) } c()
var val=1; var obj={ val:2, del:function(){ console.log(this); this.val*=2 console.log(val) } } obj.del();
var name = "erdong" var object = { name: "chen", getNameFunc: function () { return function () { return this.name; } } } console.log(object.getNameFunc()());
var name = "erdong" var object = { name: "chen", getNameFunc: function () { var that = this; return function () { return that.name; } } } console.log(object.getNameFunc()());
(function() { var a = b = 3; })(); console.log(typeof a === 'undefined'); console.log(typeof b === 'undefined');
var a = 6; setTimeout(function () { a = 666; }, 0) console.log(a);
function fn1() { var a = 2 function fn2 () { a++ console.log(a) } return fn2 } var f = fn1() f() f()
var a = (function(foo){ return typeof foo.bar; })({foo:{bar:1}}); console.log(a);
function f(){ return f; } console.log(new f() instanceof f);
function A () { } A.prototype.n = 1 var b = new A(); A.prototype = { n: 2, m: 3 } var c = new A() console.log(b.n, b.m); console.log(c.n, c.m);
var F = function(){} var O = {}; Object.prototype.a = function(){ console.log('a') } Function.prototype.b = function(){ console.log('b') } var f = new F() F.a(); F.b(); O.a(); O.b();
function Person() { getAge = function () { console.log(10) } return this } Person.getAge = function () { console.log(20) } Person.prototype.getAge = function () { console.log(30) } var getAge = function () { console.log(40) } function getAge() { console.log(50) } Person.getAge(); getAge(); Person().getAge(); new Person.getAge(); getAge(); new Person().getAge();
console.log(false.toString()); console.log([1, 2, 3].toString()); console.log(1.toString()); console.log(5..toString());
console.log(typeof NaN === 'number');
console.log(1 + "2" + "2"); console.log(1 + +"2" + "2"); console.log(1 + -"1" + "2"); console.log(+"1" + "1" + "2"); console.log( "A" - "B" + "2"); console.log( "A" - "B" + 2);
var a = 666; console.log(++a); console.log(a++);
console.log(typeof a); function a() {} var a; console.log(typeof a);
var a; var b = 'undefined'; console.log(typeof a); console.log(typeof b); console.log(typeof c);
var x = 1; if(function f(){}){ x += typeof f; } console.log(x);
var str = "123abc"; console.log(typeof str++);
console.log('b' + 'a' + +'a'+'a');
var obj = {n: 1}; function fn2(a) { a.n = 2; } fn2(obj); console.log(obj.n);
var x = 10; function fn() { console.log(x); } function show(f) { var x = 20; f(); } show(fn);
Object.prototype.bar = 1; var foo = { goo: undefined }; console.log(foo.bar); console.log('bar' in foo); console.log(foo.hasOwnProperty('bar')); console.log(foo.hasOwnProperty('goo'));
Object.prototype.bar = 1; var foo = { moo: 2 }; for(var i in foo) { console.log(i); }
function foo1() { return { bar: "hello" }; } function foo2() { return { bar: "hello" }; } console.log(foo1()); console.log(foo2());
console.log((function(){ return typeof arguments; })());
console.log(Boolean(false)); console.log(Boolean('0')); console.log(Boolean('')); console.log(Boolean(NaN));
console.log(Array(3)); console.log(Array(2,3));
console.log(0.1 + 0.2 == 0.3);
var a=[1, 2, 3]; console.log(a.join());
var a = [3]; var b = [1]; console.log(a - b);
// 輸出 undefined ReferenceError: b is not defined
var
不會產生塊級做用域,let
會產生塊級做用域。數組
示例代碼至關於:閉包
var a = 1; if(false){ a = 1; let b = 2; } console.log(a); console.log(b);
// 輸出 ReferenceError: Cannot access 'a' before initialization
let
聲明的變量不會提高,而且會產生暫存死區。在let
聲明變量以前訪問變量會拋出錯誤。框架
// 輸出 2 1 undefined {n: 2}
var b = a,此時a和b指向同一個對象。 .運算符比 = 運算符高,先計算`a.x`,此時 b = { n:1, x:undefined } 至關於給對象添加了x屬性。 a.x = a = {n:2}; 計算完a.x,再計算 = ,賦值是從右向左,此時a指向一個新對象。 a = { n:2 } a.x已經執行過了,此時對象的x屬性賦值爲a,此時 對象 = { n:1, x:{ n:2 } } 即: a = { n:2 } b = { n:1, x:{ n:2 } }
查看運算符優先級函數
// 輸出 function c(){ console.log(a); var c = 3; function a(){ } } function a(){ }
變量提高也有優先級, 函數聲明 > arguments > 變量聲明學習
// 輸出 1 TypeError: c is not a function
因爲函數聲明會提高,當函數外的console.log(c)
執行時,c
已經被賦值爲1
。所以,執行c(2)
時會拋出TypeError
,由於1
不是函數。this
// 輸出 chen
自執行函數執行時,會先進行變量提高(這裏涉及到執行上下文不過多說,必定要搞懂執行上下文),在自執行函數執行時,僞代碼爲:spa
var name = 'erdong'; (function () { var name; // 變量name會提高到當前做用域頂部 if (typeof name === 'undefined') { name = 'chen' console.log(name) } else { console.log(name) } })();
因此會執行if
中的console.log(name)
// 輸出 100 10 100
test()
爲函數獨立調用,做用域中的this
綁定爲全局對象window
。
test
函數執行時,var a
被提高到了做用域頂部,所以函數做用域中存在一個變量a
。因此在函數中訪問的a
都是局部做用域中的a
。
// 輸出 undefined
因爲if
後的{}
不會產生塊級做用域(不包含let,const時),此時的僞代碼爲:
var a; if (!(a in window)) { a = 1; } console.log(a);
var a
至關於window.a
。所以!(a in window)
轉成布爾值爲false
,不會執行a = 1
。全部console.log(a)
輸出undefined
。
//輸出 undefined 2
跟第4題相似。
// 輸出 obj(指向的值) 1
當經過obj.del()
調用del
函數時,del
函數做用域中的this
綁定爲obj
。
在函數做用域中訪問val
時,因爲函數中並無變量val
,所以實際上訪問的是全局做用域中的val
,即 1
。
這裏考察的是this
的指向,必定要熟練掌握。
// 輸出 erdong
object.getNameFunc()()
,先執行object.getNameFunc()
返回一個函數:
function () { return this.name; }
返回的函數再執行,至關於
(function () { return this.name; })();
此時的this
綁定爲window
。所以輸出全局變量name
的值erdong
。
//輸出 chen
object.getNameFunc()
執行時,此時getNameFunc
中的this
綁定爲object
,所以that = object
。object.getNameFunc()
返回的函數再執行時,產生閉包,所以返回的函數也能訪問到外層做用域中的變量that
,所以object.name
爲object.name
,即 chen
。
// 輸出 true false
首先要明白a = b = 3
是怎樣執行的,僞代碼:
b = 3; var a = b;
所以在自執行函數執行時,b
因爲爲經var
等操做符聲明,由於爲全局變量。a
爲函數做用域中的變量。所以在外面訪問a
和b
時,其值分別爲ReferenceError: a is not defined
和3
。可是typeof
檢測未聲明的變量不會拋出錯誤,會返回'undefined'
。所以typeof a
和typeof b
分別返回'undefined'
和'number'
//輸出 6
setTimeout
爲宏任務。即便設置延遲爲0ms
,也是等待微任務執行完纔會執行。所以console.log(a)
輸出 6
// 輸出 3 4
因爲fn1
函數執行後返回函數fn2
,此時產生了閉包。所以fn2
中a
訪問的是fn1
做用域中的變量a
,所以第一次a++
,以後a
爲3
,第二次以後a
爲4
。
//輸出 undefined
實參foo
的值爲{foo:{bar:1}
,所以typeof foo.bar
爲undefined
。
typeof foo.foo.bar
爲number
。
//輸出 false
因爲構造函數f
的返回值爲f
。所以new f()
的值爲f
。因此console.log(new f() instanceof f)
爲console.log(f instanceof f)
,即 false
。
// 輸出 1,undefined 2,3
var b = new A();
實例化b
時,A
的prototype
爲
A.prototype = { constructor:A, n:1 }
當訪問b.n
和b.m
時,經過原型鏈找到A.prototype
指向的對象上,即b.n = 1
,b.m = undefined
。
var c = new A();
實例化c
時,A
的prototype
爲
A.prototype = { n: 2, m: 3 }
當訪問a.n
和a.m
時,經過原型鏈找到A.prototype
指向的對象上,此時A.prototype
重寫,所以a.n = 2
,b.m = 3
。
// 輸出 a b a TypeError: o.b is not a function
F
爲函數,它也能訪問Object
原型上的方法,O
爲對象,不能訪問Function
原型上的方法。
F
的原型鏈爲:
F => F.__proto__ => Function.prototype => Function.prototype.__proto__ => Object.prototype
因爲Object.prototype
在F
的原型鏈上,因此F
能訪問Object.prototype
上的屬性和方法。即: F.a()
,F.b()
能正常訪問。
O
的原型鏈爲:
O => O.__proto__ => Object.prototype
因爲Function.prototype
不在O
的原型鏈上,所以O
不能訪問Function.prototype
上的方法,即O.b()
拋出錯誤。
若是你對原型和原型鏈掌握的好,試着理解下面的示例:
console.log(Object instanceof Function); console.log(Function instanceof Object); console.log(Function instanceof Function);
// 輸出 20 40 10 20 10 30
Person.getAge();
此時執行的是Person
函數上getAge
方法。
Person.getAge = function () { console.log(20) }
因此輸出:20。
getAge();
此時執行的是全局中的getAge
方法。此時全局getAge
方法爲:
function () { console.log(40) }
因此輸出:40。
Person().getAge();
因爲Person()
單獨執行因此,做用域中的this
綁定爲window
,至關於window..getAge()
。同上,執行的都是全局getAge
方法,可是Person
執行時,內部執行了
getAge = function () { console.log(10) }
所以全局getAge
方法如今爲:
function () { console.log(10) }
因此輸出:10。
new Person.getAge();
此時至關於實例化Person.getAge
這個函數,僞代碼:
var b = Person.getAge; new b();
因此輸出:20
getAge();
執行全局getAge
方法,因爲在Person().getAge()
執行時把全局getAge
方法賦值爲:
function () { console.log(10) }
因此輸出:10。
new Person().getAge();
此時調用的是Person
原型上的getAge
方法:
Person.prototype.getAge = function () { console.log(30) }
因此輸出:30。
這裏要注意:1.變量提高及提高後再賦值。2.調用構造函數時,帶()
和不帶()
的區別。
// 輸出 'false' '1,2,3' Uncaught SyntaxError: Invalid or unexpected token '5'
當執行1.toString();
時,因爲1.
也是有效數字,所以此時變成(1.)toString()
。沒有用.
調用toString
方法,所以拋出錯誤。
正確的應該是:
1..toString(); 1 .toString(); (1).toString();
//輸出 true
NaN
爲不是數字的數字。雖然它不是數字,可是它也是數字類型。
//輸出 '122' '32' '02' '112' 'NaN2' NaN
首先要明白兩點:
+a
,會把a
轉換爲數字。-a
會把a
轉換成數字的負值(若是能轉換爲數字的話,不然爲NaN
)。console.log(1 + "2" + "2");
簡單的字符串拼接,即結果爲:'122'
。
console.log(1 + +"2" + "2");
這裏至關於console.log(1 + 2 + "2");
,而後再字符串拼接。即結果爲:'32'
。
console.log(1 + -"1" + "2");
這裏至關於console.log(1 + -1 + "2");
,而後再字符串拼接。即結果爲:'02'
。
console.log(+"1" + "1" + "2");
這裏至關於console.log(1 + "1" + "2");
,而後再字符串拼接。即結果爲:'112'
。
console.log( "A" - "B" + "2");
,因爲'A' - 'B' = NaN
,因此至關於console.log( NaN + "2");
, 而後再字符串拼接。即結果爲:'NaN2'
。
console.log( "A" - "B" + 2);
同上,至關於console.log(NaN + 2)
,因爲NaN
+任何值仍是NaN
,即結果爲:NaN
。
// 輸出 666 668
a++
先執行取值操做,在執行+1
。 此時輸出666
,隨後a
的值變爲667
。
++a
先執行+1
在執行取值操做。 此時a
的值爲667 + 1 = 668
。
--a
和a--
同理。
使用這類運算符時要注意:
1)這裏的++
、--
不能用做於常量。好比
1++; // 拋出錯誤
2)若是a
不是數字類型,會首先經過Number(a)
,將a
轉換爲數字。再執行++
等運算。
// 輸出 'function' 'function'
跟第4題相似。函數會優先於變量聲明提早。所以會忽略var a
。
// 輸出 'undefined' 'string' 'undefined'
a
爲聲明未賦值,默認爲undefined
,b
的值爲字符串'undefined'
,c
爲未定義。
typeof
一個未定義的變量時,不會拋出錯誤,會返回'undefined'
。注意typeof
返回的都是字符串類型。
//輸出 1undefined
function f(){}
當作if
條件判斷,其隱式轉換後爲true
。可是在()
中的函數不會聲明提高,所以f
函數在外部是不存在的。所以typeof f = 'undefined'
,因此x += typeof f
,至關於x = x + 'undefined'
爲'1undefined'
// 輸出 'number'
在24題解析時提到,使用++
運算符時(不管是前置仍是後置),若是變量不是數字類型,會首先用Number()
轉換爲數字。所以typeof str++
至關於typeof Number(str)++
。因爲後置的++
是先取值後計算,所以至關於typeof Number("123abc")
。即typeof NaN
,因此輸出'number'
。
// 輸出 baNaNa
'b' + 'a' + +'a'+'a'
至關於'ba' + +'a'+'a'
,+'a'
會將'a'
轉換爲數字類型,即+'a' = NaN
。因此最終獲得'ba' + NaN +'a'
,經過字符串拼接,結果爲:baNaNa
// 輸出 4 2 2
函數傳遞參數時,若是是基本類型爲值傳遞,若是是引用類型,爲引用傳遞。所以實參a
和obj
指向對象的一個引用。當執行a.n
,實際上共同引用的對象修改了,添加了個n
屬性,所以obj.n
爲2
。
// 輸出 10
JavaScript
採用的是詞法做用域,它規定了函數內訪問變量時,查找變量是從函數聲明的位置向外層做用域中查找,而不是從調用函數的位置開始向上查找。所以fn
函數內部訪問的x
是全局做用域中的x
,而不是show
函數做用域中的x
。
//輸出 1 true false true
in
操做符:檢測指定對象(右邊)原型鏈上是否有對應的屬性值。hasOwnProperty
方法:檢測指定對象自身上是否有對應的屬性值。二者的區別在於in
會查找原型鏈,而hasOwnProperty
不會。
示例中對象foo
自身上存在goo
屬性,而它的原型鏈上存在bar
屬性。
經過這個例子要注意若是要判斷foo
上是否有屬性goo
,不能簡單的經過if(foo.goo){}
判斷,由於goo
的值可能爲undefined
或者其餘可能隱式轉換爲false的值。
// 輸出 'moo' 'bar'
for...in...
遍歷對象上除了Symbol
之外的可枚舉屬性,包括原型鏈上的屬性。
// 輸出 { bar: "hello" } undefined
兩個函數惟一區別就是return
後面跟的值,一個換行一個不換行。
當咱們書寫代碼時忘記在結尾書寫;
時,JavaScript
解析器會根據必定規則自動補上;
。
return { bar: "hello" } => 會被解析成 return; { bar: "hello" };
所以函數執行後會返回undefined
。
// 輸出 'object'
arguments
爲類數組,類型爲object
。所以typeof arguments = 'object'
。
//輸出 false true false fasle
只有下面幾種值在轉換爲布爾值時爲false
:
+0,-0,NaN,false,'',null,undefined。
除此以外的值在轉換爲布爾值的時候所有爲true
。
// 輸出 [empty × 3] [2,3]
使用Array()
建立數組時,要注意傳入的值的類型和數量。
// 輸出 false
//輸出 1,2,3
join
方法若是省略參數,默認以,
分隔。
// 輸出 2
在執行a - b
時,a
和b
都要轉換爲數字。首先a
先轉換爲字符串,[3] => [3].toString() => '3'
,而後Number(3) => 3
。b
同理。所以轉換以後爲3 - 1 = 3
。
若是文中有錯誤,請務必留言指正,萬分感謝。
點個贊哦,讓咱們共同窗習,共同進步。