前端面试 —— JavaScript(一)

2021/10/9 12:48:34

本文主要是介绍前端面试 —— JavaScript(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

typeof 能判断哪些类型

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型(不可再细分)

何时使用 === 何时使用 ==

除了 == null 之外,其他一律用 ===

const obj = { x: 100 }
if (obj.a == null) {}
// 相当于
if (obj.a === null || obj.a === undefined) {}

值类型和引用类型的区别

  • 值类型存储在栈中,引用类型存储在堆中;
  • 值类型在栈中存放原始值,引用类型在栈中存放内存地址;(考虑到性能问题,值占用空间少)
const obj1 = { x: 100 }
const obj2 = obj1
let x1 = obj1.x
obj2.x = 101
x1 = 102
> obj1.x    // 101

if 语句和逻辑运算

  • truly 变量:!!a === true 的变量
  • falsely 变量:!!a === false 的变量

原型和原型链

  • 如何准确判断一个变量是不是数组?
  • 手写一个简单的 jQury,考虑插件和扩展性
  • class 的原型本质,怎么理解?
class Student {
    constructor(name, number) {
        this.name = name
        this.number = number
    }
    
    sayHi() {
        console.log(`name=${this.name} and number=${this.number}`)
    }
}

// 通过类 new 对象/实例
const charlotte = new Student('夏洛特', 1001)
> charlotte.name    // 夏洛特
> charlotte.number    // 1001
/**
 * 继承
**/
class People {
    constructor(name) {
        this.name = name
    }
    
    eat() {
        console.log(`${this.name} eat something`)
    }
}

class Student extends People {
    constructor(name, number) {
        super(name)
        this.number = number
    }
    
    sayHi() {
        console.log(`name=${this.name} and number=${this.number}`)
    }
}

class Teacher extends People {
    constructor(name, major) {
        super(name)
        this.major = major
    }
    
    teach() {
        console.log(`${this.name} teach ${this.major}`)
    }
}

const teacherShao = new Teacher('邵老师', 'computer')
> teacherShao.teach()

原型关系:隐式原型指向显式原型的 prototype charlotte.__proto__ === Student.prototype

// jQuery
class jQuery {
    constructor(selector) {
        const result = document.querySelectorAll(selector)
        for (let i = 0; i < result.length; i++) {
            this[i] = result[i]
        }
        this.length = resule.length
    }
    
    get(idx) {
        return this[idx]
    }
    
    map(fn) {
        for (let i = 0; i < this.length; i++) {
            const elem = this[i]
            fn(elem)
        }
    }
}

作用域和闭包

  • this 的不同应用场景,如何取值?

在函数执行的地方决定,而不是在定义的时候决定

  • 手写 bind 函数
Function.prototype.myBind = function () {
    // 将参数拆解为数组
    const args = Array.prototype.slice.call(arguments)

    // 获取 this(数组第一项)
    const t = args.shift()
    
    // fn1.bind(...) 中的 fn1
    const self = this
    
    // 返回一个函数
    return function () {
        return self.apply(t, args)
    }
}
  • 实际开发中闭包的应用场景,举例说明

在函数中的变量只能在函数内访问,自己来维护该变量。如果外部需要访问可以创建一些 api 。


作用域即变量的合法使用范围,分为全局作用域、函数作用域和块级作用域(ES6 新增)

闭包:访问一个变量时,是在函数定义的地方,向上级作用域查找,不是在执行的地方

  • 作用域应用的特殊情况
  • 函数作为参数被传递
  • 函数作为返回值被返回

为什么每个数点击都是 10?

let a, i
for (i = 0; i < 10; i++) {
    a = document.createElement('a')
    a.innerHTML = i + '<br>'
    a.addEventListener('click', function (e) {
        e.preventDefault()
        console.log(i)
    })
    document.body.appendChild(a)
}

因为 i 的声明是在 for 之外,这里是全局作用域。当点击事件发生的时候,i 的值以及变成了 10,因为循环已经结束,所以在访问 i 的时候永远都是显示 10;

let a
for (let i = 0; i < 10; i++) { ... }

这里 let 声明的变量都是块级作用域,因此会在每次循环中都声明一个新的 i 变量,也就不会导致刚才的问题。

单线程和异步

  • 手写 Promise 加载一张图片
const url = ''

function loadImage(src) {
    return new Promise((resolve, reject) => {
        const img = document.createElement('img')
        img.onload = () => {
            resolve(img)
        }
        img.onerror = () => {
            reject(new Error('图片加载失败 ${src}'))
        }
        img.src = src
    })
}

loadImage(url).then(img => {
    console.log(`${img.width}x${img.height}`)
}).catch(err => {
    console.log(err)
})

请描述 event loop 的机制

JS 是单线程运行的,异步要基于回调来实现,event loop 就是异步回调实现的原理。

var let const 的区别

  • 作用域

如果在函数最外层声明变量,他们都可以在全局被访问到;

let 和 const 是块级作用域,在 let 和 const 中声明的变量仅可在该块中使用。比如 if while for 。

var 只有函数和全局作用域,不存在块级作用域

  • 用途

var 变量可以重新声明和修改,并且不会出错;

let 变量可以被修改,但在作用域内不能被重新声明;

const 常量不能被修改且不能被重新声明;

  • 变量提升

var 声明的变量会被提升到作用域的顶部,并使用 undefined 对其初始化

let 声明的变量会被提升到作用域的顶部,不会对值进行初始化。所以如果尝试在声明前访问 let 变量会出错

const 声明的常量也会被提升到作用域顶部,但是必须要初始化

JS 数据类型

  • 划分

基本数据类型:number、bigint、string、boolean、null、undefined、symbol

引用数据类型:object

  • 类型判断

typeof:识别所有基本类型,判断是否为引用类型,识别函数

instanceof:判断两边对象是否属于实例关系

原型和原型链

  • 原型

JavaScript 是基于原型的,我们创建的每个函数都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象(原型对象),这个对象存放可以让所有实例共享的属性和方法。

原型对象默认拥有一个 constructor 属性,指向指向它的那个构造函数

每个对象都拥有一个隐藏的属性 __proto__ ,指向它的原型对象

1.jpg

实例自身属性会屏蔽原型上面的同名属性,实例上没有的属性回去原型上面找

2.jpg

在已经创建了实例的情况下重写原型,会切断现有实例与新原型之间的联系

重写原型对象,会导致原型对象的 constructor 属性指向 Object ,原型链关系混乱。所以我们应该在重写原型对象的时候指定 constructor

People.prototype = {
    constructor: People,
    // ...
}
  • 原型链

JavaScript 中所有的对象都是由它的原型对象继承而来的。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链

3.png

所有原型链的终点都是 Object 函数的 prototype 属性

Object.prototype 指向的原型对象同样拥有原型,不过它的原型是 null,而 null 没有原型

讲一讲浅拷贝和深拷贝

浅拷贝和深拷贝只针对对象和数组这样的引用数据类型。

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;

深拷贝会创建一个新的一模一样的对象,新对象跟原对象不共享内存,修改新对象不会影响原对象

  • 浅拷贝实现方式
// Object.assign()
// 当 object 只有一层的时候,是深拷贝
const user = {
    name: 'thinc',
    age: 23
}
const user2 = Object.assign({}, user)
user2.age = 3
console.log(user)    // {name: 'thinc', age: 23}

// Array.prototype.concat()
const arr = [1, 2, { name: 'thinc' }]
const arr2 = arr.concat()
arr2[0] = 111
console.log(arr)    // [111, 2, { name: 'thinc' }]

// Array.prototype.slice()
const arr = [1, 3, { name: 'Thinc' }]
const arr2 = arr.slice()
arr2[1] = 233
console.log(arr)    // [1, 233, { name: 'Thinc' }]

补充说明:Array 的 slice 和 concat 方法不修改原数组,只是返回了一个浅复制原数组中元素的新数组。如果元素是引用类型,只会复制对象的引用(指针);如果是基本数据类型,会把具体的值拷贝过来。

  • 深拷贝实现方式
  1. JSON.stringify 将对象转换成 JSON 字符串,再用 JSON.parse 把字符串解析成对象。不能处理函数
// JSON.parse(JSON.stringify())
let user1 = {
  age: 23,
  city: {
    now: 'Langfang',
    ago: 'Wenzhou'
  }
}

let user2 = JSON.parse(JSON.stringify(user1))

user2.age = 24
user2.city.now = 'Beijing'

console.log(user1)    // { age: 23, city: { now: 'Langfang', ago: 'Wenzhou' }}
console.log(user2)    // { age: 24, city: { now: 'Beijing', ago: 'Wenzhou' }}
  1. 函数库 lodash
const _ = require('lodash')
const obj1 = {
    a: 1,
    b: {
        f: {
            g: 2
        }
    }
}
const obj2 = _.cloneDeep(obj1)

obj2.a = 111
obj2.b.f.g = 222

console.log(obj1)    // { a: 1, b: { f: { g: 2 } } }
console.log(obj2)    // { a: 111, b: { f: { g: 222 } } }
  1. 手撕递归
/**
 * 深拷贝
 * @param {Object} obj
 */
function deepClone(obj = {}) {
  if (typeof obj !== 'object' || obj == null) {
    return obj
  }

  // 初始化返回结果
  let result
  if (obj instanceof Array) {
    result = []
  } else if {
    result = {}
  }

  for (let key in obj) {
    // 保证 key 不是原型的属性
    if (obj.hasOwnProperty(key)) {
      // 递归!!!
      result[key] = deepClone(obj[key])
    }
  }

  return result
}



这篇关于前端面试 —— JavaScript(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程