JS底层基础踩坑20题

432 阅读8分钟

此文章包含精挑出来的踩坑题目,在业余时间还会不断精选,目标就是捉JS虫

1 对象键自动转换为字符串

const a = {};
const b = { key: "b" };
const c = { key: "c" };

a[b] = 123;
a[c] = 456;
console.log(a[b]);  

> 456

【解释】 当对象自动转换为字符串化时,它变成了[Object object]。 所以我们在这里说的是a["Object object"] = 123。 然后,我们可以尝试再次做同样的事情。 c对象同样会发生隐式类型转换。那么,a["Object object"] = 456。 然后,我们打印a[b],它实际上是a["Object object"]。 我们将其设置为456,因此返回456。

2 结构函数是否使用new

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

const person1 = new Person('h''hy')
const person2 = Person('h''hy')

console.log(person1)
console.log(person2)

>  Person {firstName"h"lastName"hy"} 
>  undefined

【解释】person1使用new,指向我们创建的空对象;person2没有使用new,this指向「全局对象」

3 给构造函数添加属性和原型添加属性

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const member = new Person("Lydia""Hallie");
Person.getFullName = function () {
  return `${this.firstName} ${this.lastName}`;
}

console.log(member.getFullName());
> TypeError

【解释】不能给构造函数直接添加属性,因为可以这样添加的话,将浪费大量内存空间,因为每个实例仍然具有该属性,这将占用每个实例的内存空间。

可以写在原型上

Person.prototype.getFullName = function () {
  return `${this.firstName} ${this.lastName}`;
}

4 除了【基本对象】base object),所有对象都有原型

console.log(Math.prototype);
> undefined

【解释】 JS基本对象:Function, Array, Boolean, Date, Math, Number, String, RegExp, Global

5 + 隐式类型转换

console.log(+'2'+1);
console.log(2+'1');
console.log('2'+1);  
> 3"21""21"

【解释】+两边都有数字,但任何一边有字符串会进行类型转换

6 函数参数用标记模板字面量

function getPersonInfo(one, two, three) {
  console.log(one)
  console.log(two)
  console.log(three)
}

const person = 'Lydia'
const age = 21

getPersonInfo`${person} is ${age} years old`Array [""" is "" years old"]
> "Lydia"21

getPersonInfo(person,age)
> "Lydia"21undefined

【解释】如果使用标记模板字面量,第一个参数的值总是包含字符串的数组。其余的参数获取的是传递的表达式的值!

7 对象引用比较

function checkAge(data) {
  if (data === { age18 }) {
    console.log('You are an adult!')
  } else if (data == { age18 }) {
    console.log('You are still an adult.')
  } else {
    console.log(`Hmm.. You don't have an age I guess`)
  }
}

checkAge({ age18 })
> Hmm.. You don't have an age I guess

【解释】在测试相等性时,基本类型通过它们的值(value)进行比较,而对象通过它们的引用进行比较。
题中我们正在比较的两个对象不是同一个引用:作为参数传递的对象引用的内存位置,与用于判断相等的对象所引用的内存位置并不同。
这也是 { age: 18 } === { age: 18 } 和 { age: 18 } == { age: 18 } 都返回 false 的原因。

8 扩展运算符作为参数,并且只能作为最后一个参数

function getAge(...args) {
  console.log(typeof args)
  console.log( args)
}

getAge(21)
> "object"Array [21]

function getAge(a, ...args, b) {
  return a
}
> 报错

【解释】扩展运算符(...args)会返回实参组成的数组。而数组是对象,因此 typeof args 返回 "object"
...args只能作为最后一个参数

9 箭头函数返回对象,必须在圆括号之间编写它

const getUser = user => { name: user.name, age: user.age }
const user = { name: "Lydia", age: 21 }
console.log(getUser(user))

> undefined

【解释】对于箭头函数,如果只返回一个值,我们不必编写花括号。但是,如果您想从一个箭头函数返回一个对象,必须在圆括号之间编写它,否则不会返回任何值!下面的函数将返回一个对象:

const getUser = user => ({ name: user.name, age: user.age })

10 Symbol类型

const info = {
  [Symbol('a')]: 'b'
}

console.log(info)
console.log(Object.keys(info))
> {Symbol('a'): 'b'} 
> []

【解释】Object.keys方法返回对象上的所有可枚举的键属性。Symbol类型是不可见的,并返回一个空数组。 记录整个对象时,所有属性都是可见的,甚至是不可枚举的属性。
Symbol类型是不可枚举的;
Symbol代表完全唯一的值,防止对象意外名称冲突,例如当使用2个想要向同一对象添加属性的库时;
可以使用Object.getOwnPropertySymbols()方法访问 Symbol;

11 可以将类设置为等于其他类/函数构造函数

class Person {
  constructor() {
    this.name = "hhy"
    this.age = 18
  }
}

Person = class AnotherPerson {
  constructor() {
    this.name = "gh"
  }
}

const member = new Person()
console.log(member.name)
console.log(member.age)
> "gh"undefined

【解释】将类设置为等于其他类/函数构造函数,实例改变

12 JS引擎自动添加分号

function nums(a, b) {
  if
  (a > b)
  console.log('a is bigger')
  else 
  console.log('b is bigger')
  return   
  a + b
}

console.log(nums(42))
console.log(nums(12))
> a is bigger, undefined 
> b is bigger, undefined

【解释】return后面自动添加分号,所以代码规范最好带上分号

13 push返回数组长度

let newList = [123].push(4)
console.log(newList.push(5))
> Error

【解释】push方法返回数组的长度,而不是数组本身

14 this指向:函数内包含函数,this还是指向window

var obj1 = {
  foo1function () {
    console.log(this//obj1对象
    function inner () {
      console.log(this)  //window
      console.log(this.a)  //3
    }
    inner()  //函数内包含函数,this还是指向window
  }
}
var a = 3
obj1.foo1() 
> obj1对象
> window3

【解释】函数内包含函数,this还是指向window

15 变量提升

var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'hhy'; //变量提升
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();
> "Goodbye hhy"

 var str = 'World!';   
    (function (name) {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
    })(str);
> Hello World 

【解释】
第一题name变量提升了所以typeof name === 'undefined'成立;
第二题因为name已经变成函数内局部变量

16 const声明的变量,不能修改它的指针指向,但是可以改变其存储值

const obj = {
    name: '小辉辉'
};
obj = [1020]
obj.name = '辉太郎';
console.log(obj);
> Uncaught TypeError: Assignment to constant variable. 
> {name: '辉太郎'}

【解释】 报错的原因是:obj这个变量不能在和其它值进行关联了,也就是不能修改const声明变量的指向。
后面值对是因为可以在不改变指向的情况下,修改堆内存中的信息(这样也把值更改了);
所以记住:const声明的变量,不能修改它的指针指向,但是可以改变其存储值的

17 JS会认为大括号{}开头的是一个空的代码块

console.log({}+[]) 
> "[object Object]"

{}+[]   
> 0   控制台直接打印,不用console.log

【解释】
第一题+转换为字符串;
第二题{}被忽略了,直接执行了+[],结果为0。

18 作用域

var a = 0,  
    b = 0;
function A(a) {
    A = function (b) {
        console.log('inner:',a + b++)
    }
    console.log('outer:',a++)
}
A(1)
A(2)  //执行的全局A,也就是A里面的A,a已经变成2,b是2,2+2等于4"outer:" 1"inner:" 4

仅仅执行
A(2)
> "outer:" 2

【解释】
第一题A(2)执行的是A函数里面的A,外面的A先压入栈,里面的再压入A,所以第二次执行的是里面的 ;
第一题A(2)执行的是外面函数

19 JSON.stringify

JSON.stringify('ni')

> ""ni""

【解释】字符串再包字符串

20 forEach 和 map 区别

var array = [1,2,3,4,5];
 
var x = array.forEach(function(value,index){
 
    console.log(value);   //1 2 3 4 5  可遍历到所有数组元素  
 
    return value + 10
});
console.log(x);   //undefined    无论怎样,总返回undefined
 
var y = array.map(function(value,index){
 
    console.log(value);   //1 2 3 4 5 可遍历到所有数组元素
 
    return value + 10
});
console.log(y);   //[11, 12, 13, 14, 15]

【解释】 forEach() 方法对数组的每个元素执行一次提供的函数。总是返回undefined

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果返回值是一个新的数组