Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端面试基础 #2

Open
dravenww opened this issue Feb 8, 2021 · 0 comments
Open

前端面试基础 #2

dravenww opened this issue Feb 8, 2021 · 0 comments
Assignees
Labels
js javascript

Comments

@dravenww
Copy link
Owner

dravenww commented Feb 8, 2021

前端基础

浏览器

  • 浏览器的缓存机制:强缓存与协商缓存,以及其区别是什么?
  • 存储相关:localstorage、sessionStorage、cookie等分别是做什么用的,区别是什么?
  • 浏览器的network面板里面的东西,主要是timing下面的时间段代表的都是什么意思?TTFB是什么?
  • 浏览器的performance用过吗,是用来干什么的?
  • 跨域的原理,跨域的实现方式有哪几种?
  • 浏览器环境下的event loop是怎样的?其实也就是宏任务和微任务,可以看下这篇文章

JavaScript

基础数据类型和引用数据类型

  • 基础数据类型:Undefined、Null、Boolean、String、Number、Symbol
  • 引用数据类型:Object、Array、Date、RegExp、Function
  • 此处可能会考察,typeof、instanceof;包括手动实现以下typeof和instanceof
// 实现typeof
function type(obj) {
	return Object.prototype.toString.call(a).slice(8,-1).toLowerCase();
}
// 实现instanceof
function instance(left,right){
    left=left.__proto__
    right=right.prototype
    while(true){
       if(left==null)
       	  return false;
       if(left===right)
          return true;
       left=left.__proto__
    }
}

原型链

理解原型链是做什么的,也就是:实例.proto === 构造函数.prototype

Object.prototype.__proto__ === null // true
Function.prototype.__proto__ === Object.prototype // true
Object.__proto__ === Function.prototype // true

有个比较好的问题,可以思考下:

function F() {}
Object.prototype.b = 2;
F.prototype.a = 1;
var f = new F();
console.log(f.a) // 1
console.log(f.b) // 2
console.log(F.a) // undefined
console.log(F.b) // 2

上面代码,为什么F.a是undefined?

function F() {}
Object.prototype.b = 2;
Function.prototype.a = 1;
var f = new F();
console.log(f.a) // undefined
console.log(f.b) // 2
console.log(F.a) // 1
console.log(F.b) // 2

上面代码,为什么f.a是undefined?

function F() {}
F.prototype.a = 1;
var f1 = new F()
F.prototype = {
    a: 2
}
var f2 = new F()
console.log(f1.a) // 1
console.log(f2.a) // 2

继承

继承的几种方式:

  • 原型链继承:
    function SuperType() {
      this.name = 'Yvette';
      this.colors = ['red', 'blue', 'green'];
    }
    SuperType.prototype.getName = function () {
        return this.name;
    }
    function SubType() {
        this.age = 18;
    }
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    
    let instance1 = new SubType();
    instance1.colors.push('yellow');
    console.log(instance1.getName());
    console.log(instance1.colors); // ['red', 'blue', 'green', 'yellow']
    
    let instance2 = new SubType();
    console.log(instance2.colors); // ['red', 'blue', 'green', 'yellow']
    
    缺点:
    • 通过原型来实现继承时,原型会变成另一个类型的实例,原先的实例属性变成了现在的原型属性,该原型的引用类型属性会被所有的实例共享。(引用类型值被所有实例共享)
    • 在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数
  • 构造函数继承:
    function SuperType(name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    function SubType(name) {
        SuperType.call(this, name);
    }
    let instance1 = new SubType('draven');
    instance1.colors.push('yellow');
    console.log(instance1.colors);  // ['red', 'blue', 'green', 'yellow']
    
    let instance2 = new SubType('ben');
    console.log(instance2.colors);  // ['red', 'blue', 'green']
    
    优点:
    • 可以向超类传递参数
    • 解决了原型中包含引用类型值被所有实例共享的问题 缺点:
    • 方法都在构造函数中定义,函数复用无从谈起。
    • 超类型原型中定义的方法对于子类型而言都是不可见的。
  • 组合继承:
    function SuperType(name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    SuperType.prototype.sayName = function () {
        console.log(this.name);
    }
    function SuberType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    SuberType.prototype = new SuperType()
    SuberType.prototype.constructor = SuberType
    
    let instance1 = new SuberType('draven', 25);
    instance1.colors.push('yellow');
    console.log(instance1.colors); // ['red', 'blue', 'green', 'yellow']
    instance1.sayName(); //draven
    
    let instance2 = new SuberType('ben', 22);
    console.log(instance2.colors);  // ['red', 'blue', 'green']
    instance2.sayName();//ben
    
    缺点:
    • 无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
      优点:
    • 可以向超类传递参数
    • 每个实例都有自己的属性
    • 实现了函数复用
  • 寄生组合式继承,寄生组合继承是引用类型最理性的继承范式,使用Object.create在组合继承的基础上进行优化:
    function SuperType(name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    SuperType.prototype.sayName = function () {
        console.log(this.name);
    }
    function SuberType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    SuberType.prototype = Object.create(SuperType.prototype)
    SuberType.prototype.constructor = SuberType
    let instance1 = new SuberType('draven', 25);
    instance1.colors.push('yellow');
    console.log(instance1.colors); //[ 'red', 'blue', 'green', 'yellow' ]
    instance1.sayName(); //draven
    
    let instance2 = new SuberType('ben', 22);
    console.log(instance2.colors); //[ 'red', 'blue', 'green' ]
    instance2.sayName();//ben
    
  • ES6继承:
    class SuperType {
        constructor(age) {
            this.age = age;
        }
    
        getAge() {
            console.log(this.age);
        }
    }
    
    class SubType extends SuperType {
        constructor(age, name) {
            super(age); // 调用父类的constructor(age)
            this.name = name;
        }
    }
    
    let instance = new SubType(18, 'draven');
    instance.getAge(); // 18
    
    • 类的内部所有定义的方法,都是不可枚举的。(ES5原型上的方法默认是可枚举的)

闭包:

  • 柯理化:
// 实现固定参数的curry
function add(a, b, c, d) {
    return a + b + c + d
}

function curry(fn) {
    const length = fn.length
    let params = []
    return function func() {
        params = params.concat([].slice.call(arguments))
        if (params.length === length) {
            const res = fn.apply(null, params);
            params = [];
            return res;
        } else {
            return func;
        }
    }
}

const addCurry = curry(add);
console.log(addCurry(1, 2)(3, 4)); // 10
console.log(addCurry(2)(3)(4)(5)); // 14
// 实现随意参数的柯理化
function add() {
    let params = [].slice.call(arguments);
    function func() {
        params = params.concat([].slice.call(arguments))
        return func;
    }
    func.toString = () => {
        return  params.reduce((a, b) => {
            return a + b;
        }, 0);
    }
    return func;
}

console.log(add(1, 2)(3, 4)); // 10
console.log(add(2)(3)(4)(5)); // 14
  • 防抖和节流:
    函数防抖和节流,都是控制事件触发频率的方法。
// 防抖
export function debounce(func, wait, immediate) {
    let timeout, args, context, timestamp, result;

    let nowTime = Date.now || function () {
        return new Date().getTime();
    };

    const later = function () {
        let last = nowTime() - timestamp;

        if (last < wait && last >= 0) {
            timeout = setTimeout(later, wait - last);
        } else {
            timeout = null;
            if (!immediate) {
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            }
        }
    };

    return function () {
        context = this;
        args = arguments;
        timestamp = nowTime();
        let callNow = immediate && !timeout;
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
            result = func.apply(context, args);
            context = args = null;
        }

        return result;
    };
};
// 节流
function throttle(fn, threshhold) {
    let timeout
    let start = new Date;
    threshhold = threshhold || 160
    return function () {
        const context = this, args = arguments, curr = new Date() - 0
        clearTimeout(timeout)//总是干掉事件回调
        if (curr - start >= threshhold) {
            fn.apply(context, args)
            start = curr
        } else {
            //让方法在脱离事件后也能执行一次
            timeout = setTimeout(function(){
                fn.apply(context, args)
            }, threshhold);
        }
    }
}

var/let/const

这部分主要考查对let和var的理解,变量提升等。

看下面这个代码的执行结果是什么?

var foo = {n: 1};
var bar = foo;
foo.x = foo = {n: 2};

bar = ?
foo = ?

上面的执行结果是:bar = {n:1,x:{n:2}}; foo={n:2};

a();
var a=3;
function a(){
alert(10)
}
alert(a)
a=6;
a()

上面的执行结果是:10 3 error;最后的error是因为a不是个function;

== 与 ===

隐式转换的步骤: 主要搞明白在强等和双等的时候做了什么事情,也就好理解了。

强等(===)会首先比较两边的类型是否相同,如果不同则直接返回false;如果类型相同的话,则是按照==来判断的,我们来看下==所引起的隐式转换。

双等号引起的隐式转换

一、首先看双等号前后有没有NaN,如果存在NaN,一律返回false。

二、再看双等号前后有没有布尔,有布尔就将布尔转换为数字。(false是0,true是1)

三、接着看双等号前后有没有字符串, 有三种情况:

1、对方是对象,对象使用toString()或者valueOf()进行转换;

2、对方是数字,字符串转数字;(前面已经举例)

3、对方是字符串,直接比较;

4、其他返回false

四、如果是数字,对方是对象,对象取valueOf()或者toString()进行比较, 其他一律返回false

五、null, undefined不会进行类型转换, 但它们俩相等

.toString()方法和.valueOf()方法数值转换

通常情况下我们认为,将一个对象转换为字符串要调用toString()方法,转换为数字要调用valueOf()方法,但是真正应用的时候并没有这么简单,看如下代码实例:

let obj = {
 name: "draven",
 age: 28
}
console.log(obj.toString()); //[object Object]

同理,我们再看valueOf()方法:

let arr = [1, 2, 3];
console.log(arr.valueOf());//[1, 2, 3]

从上面的代码可以看出,valueOf()方法并没有将对象转换为能够反映此对象的一个数字。相反,我们用toString()

let arr = [1, 2, 3];
console.log(arr.toString());//1,2,3

注:很多朋友认为,转换为字符串首先要调用toString()方法, 其实这是错误的认识,我们应该这么理解,调用toString()方法可以转换为字符串,但不一定转换字符串就是首先调用toString()方法。

我们看下下面代码:

let arr = {};
arr.valueOf = function () { return 1; }
arr.toString = function () { return 2; }
console.log(arr == 1);//true

let arr = {};
arr.valueOf = function () { return []; }
arr.toString = function () { return 1; }
console.log(arr == 1);//true

上面代码我们可以看出,转换首先调用的是valueOf(),假如valueOf()不是数值,那就会调用toString进行转换!

let arr = {};
arr.valueOf = function () { return "1"; }
arr.toString = function () { return "2"; }
console.log(arr == "1");//true

假如"1"是字符串,那么它首先调用的还是valueOf()。

let arr = [2];
console.log(arr + "1");//21

上面的例子,调用的是toString();因为arr.toString()之后是2。

转换过程是这样的,首先arr会首先调用valueOf()方法,但是数字的此方法是简单继承而来,并没有重写(当然这个重写不是我们实现),返回值是数组对象本身,并不是一个值类型,所以就转而调用toString()方法,于是就实现了转换为字符串的目的。

说明

大多数对象隐式转换为值类型都是首先尝试调用valueOf()方法。但是Date对象是个例外,此对象的valueOf()和toString()方法都经过精心重写,默认是调用toString()方法,比如使用+运算符,如果在其他算数运算环境中,则会转而调用valueOf()方法。

let date = new Date();
console.log(date + "1"); //Sun Apr 17 2014 17:54:48 GMT+0800 (CST)1
console.log(date + 1);//Sun Apr 17 2014 17:54:48 GMT+0800 (CST)1
console.log(date - 1);//1460886888556
console.log(date * 1);//1460886888557

举例巩固提高
下面我们一起来做做下面的题目吧!

let a;
console.dir(0 == false);//true
console.dir(1 == true);//true
console.dir(2 == {valueOf: function(){return 2}});//true

console.dir(a == NaN);//false
console.dir(NaN == NaN);//false

console.dir(8 == undefined);//false
console.dir(1 == undefined);//false
console.dir(2 == {toString: function(){return 2}});//true

console.dir(undefined == null);//true

console.dir(null == 1);//false

console.dir({ toString:function(){ return 1 } , valueOf:function(){ return [] }} == 1);//true

console.dir(1=="1");//true
console.dir(1==="1");//false

[] == 0 // true

上面的都可以理解了吗?最后一行代码结果是true的原因是什么?

es6

这部分考查对es6的掌握熟练度,新增的一些类型,语法,等等。推荐大家看一看阮一峰老师的es6的文章

手写实现

js实现bind

// 实现bind
Function.prototype.myBind = function (context,...args) {
    let self = this;
    let params = args;
    return function (...newArgs) {
        self.call(context, ...params.concat(...newArgs))
    }
}
var a = {
    name: 'this is a'
}

function sayName() {
    console.log(this.name, arguments)
}

let newfn = sayName.myBind(a, '1234', '5678')
newfn('1000', '2000')

js实现call

// 实现call
Function.prototype.myCall = function (context,...args) {
    context.fn = this;
    context.fn(...args)
    delete context.fn;
}
var a = {
    name: 'this is a'
}
function sayName() {
    console.log(this.name, arguments)
}
sayName.myCall(a, '1234', '5678')

js实现setInterval

// setTimeout 实现setInterval
function mySetInterval(fn, time) {
    let timer = {};
    function timeout() {
        timer.t = setTimeout(() => {
            fn();
            timeout()
        }, time)
    }
    timeout();
    return timer;
}


function clearMyInterval(timer) {
    clearTimeout(timer.t)
}

promise

promise考察点比较多,包括实现自己的promise和一些调用的知识点

推荐两篇文章:实现PromisePromise题

css

  • 盒模型,盒模型的margin、padding有什么特点?
  • flex布局的属性都有什么,都代表什么含义?
  • 左右居中布局、上下居中布局、上下左右居中布局,实现方式是什么?
  • 单行超出省略...,多行超出省略...
  • 自适应布局
  • 响应式布局
  • less、scss、stylus
  • rem、em、vw等
  • 移动端1px如何实现?
  • css如何实现三角形?
  • css的link和import区别是什么?

html

  • meta用来干嘛的
  • 块元素、行元素区别和举例
  • html5新增的标签有哪些?
  • video标签的使用,事件等
@dravenww dravenww added the js javascript label Feb 8, 2021
@dravenww dravenww self-assigned this Feb 8, 2021
@dravenww dravenww reopened this Mar 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
js javascript
Projects
None yet
Development

No branches or pull requests

1 participant