在三大框架盛行的时代, 基本上会个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
。
若是文中有错误,请务必留言指正,万分感谢。
点个赞哦,让咱们共同窗习,共同进步。