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