常见面试问题

487 阅读14分钟

1、垂直水平居中

1、定位方式。 在之前的项目中大多数都是使用这种方式实现垂直水平居中。分为三种方式

  • 有固定宽高 固定偏移位置

    top 和 left 50% margin-top 和 margin-left 负宽高的一半

  • 有固定宽高 自适应 偏移

    top left right bottom 都是0 margin:auto

  • 自适应 高度

    transform:translate(-50%,-50%) 2、flex 弹性盒模型。

3、table 布局 (不常用)

2、盒模型

1、标准盒模型 box-sizing: content-box 内容就是实际的 宽高 不包含 padding和border

2、IE和模型 box-sizing: border-box

内容 包含 宽高 padding和border

为了在项目中方便开发我们都会使用基础 样式。 统一使用 border-box 方便处理宽高 。

3、CSS 布局方式

1、双飞翼布局 (左右固定 中间自适应)

定位 浮动 flex calc计算方式(性能影响)

4、移动端响应式布局

media媒体查询标签 rem flex (调整区间不大) 百分比布局 % vh vw

5、使用css 让一个div 消失在视野中

个人认为解决方案可以分为以下两大类:

  • 视野内隐藏
  • 移动到视野外

5.1、视野内隐藏

视野内隐藏就是就是让元素不可见包含一下几种情况:

5.1.1、元素的高宽为0

div { 
    height: 0;
    width: 0; 
}

元素不存在高宽即不可见。

5.1.2、设置透明度为0

div { 
    opacity: 0; 
}

5.1.3、 设置display,让其消失在渲染树中

div3 { 
    display: none; 
}

5.1.4、 设置visiblity来改变可见

div { 
    visibility: hidden; 
}

5.1.5、 缩放

div {
    transform: scale(0);
}

5.1.6、 旋转

div {
    transform: rotateX(90deg); /*X/Y均可*/
}
div {
    transform: skewX(90deg); /*倾斜X/Y均可*/ 
}
  • display: none; 直接将元素块在渲染树中删除,不进行渲染,后面的div会补上这一部分位置。
  • visibility: hidden; 只是将元素隐藏,但是所在位置还是被占着的,不会影响整体布局,无法触发点击事件。
  • opacity: 0; 设置透明度为0 实际元素还在那个位置,而且可点击。

5.2、移动到视野外

5.2.1、负margin

div {
    margin-top: -9999px; /*top/right/bottom/left均可*/
}

5.2.2、绝对/相对定位

div {
    poititon: absolute;
    top:-100%; /*top/right/bottom/left均可*/
}

5.2.3、transform

div {
    transform: translateX(-9999px); /*X/Y均可*/
}

6、说明z-index的工作原理,适用范围。

  1. z-index这个属性控制着元素在z轴上的表现形式。

  2. 仅适用于定位元素,即拥有relative,absolute,fixed属性的position元素。

  3. 堆叠顺序是当前元素位于z轴上的值,数值越大说明元素的堆叠1顺序越高,越靠近屏幕。

  4. 未定义时,后来居上,未定义z-index的属性,元素的堆叠顺序基于它所在的文档树。默认情况下,后来的元素的z-index的值越大。

  5. 使用范围:

  • 网页两侧的浮动窗口(播放器,指定按钮,广告等)
  • 导航栏浮动值顶
  • 隐藏div实现弹窗功能(通过设置div定位和z-index控制div的位置和出现隐藏)

注意:z-index有一个堆叠上下文规则,### 默认堆叠上下文是根元素,在同一堆叠上下文z-index越大越靠近屏幕(层级越高)。

当任何一个元素层叠另一个包含在不同堆叠上下文元素时,则会以堆叠上下文的层叠级别(stack level)来决定显示的先后情况。也就是说,在相同的堆叠上下文下才会用z-index来决定先后,不同时则由堆叠上下文的z-index来决定。例如:

定位元素A(z-index:100)里面有定位元素A1(z-index:300),而定位元素B和元素A兄弟关系(z-index:200)。你会发现无论A1的z-index是多大,也会被z-index是200的B所覆盖,因为A的z-index只有100。

7、对象key 相关

    const a = {}, b= "0", c=0;
    a[b]= "风";
    a[c]="信子";
    console.log(a[b]) ; // 信子

    const a = {}, b = Symbol("0"), c = Symbol(0);
    a[b] = "风";
    a[c] = "信子";
    console.log(a[b]) ; // 风

    const a = {}, b = {n:0}, c = {m:0}
    a[b] = "风";
    a[c] = "信子";
    console.log(a[b]) ; // 信子

当key是字符串数字和数字是是不区分,都按数字处理。

image.png

Symbol类型的时候,是表示唯一,所以不会覆盖。

image.png

对象的key 引用类型都是字符串 [object Object]

image.png

8、闭包

能够读取其他函数内部变量的函数

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

    const test = (function(i){
        return function (){
            alert(i*=2)
        }
    })(2)
    test(5)  // “4”

自执行函数立即执行 将 返回给 test 函数 return 返回的函数并没有行参, 然后向上级作用域查询 2

    const a = 0 ,b = 1;
    function A(a){
        A=function(b){
            alert(a+b++)
        }
        alert(a++)
    }

    A(1); // 1
    A(2); // 4

A函数被重写 考察点 闭包 重写 和 a++

9、对象数组的 深浅拷贝

浅拷贝 只拷贝了第一层数据 浅拷贝的方式:

  1. 扩展运算符 ...
  2. Object.assign()

深拷贝方式

  1. 用 JSON.stringify 把对象转换成字符串,再用 JSON.parse 把字符串转换成新的对象。如果对象中包含 Symbol 、date、 function 或 RegExp 这些就不能用这种方法了
  2. Object.assign()拷贝。当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝
  3. 使用递归的方式实现深拷贝
  4. lodash.cloneDeep()实现深拷贝

10、同步异步 (宏任务和微任务)

    async function async1(){
        console.log(1);
        await async2();
        console.log(2)
    }

    async function async2(){
        console.log(3);
    }

    console.log(4);
    setTimeout(function(){
        console.log(5)
    },0)
    async1();
    new Promise(function(resolve){
        console.log(6)
        resolve();
        console.log(7)
    }).then(function(){
        console.log(8)
    })

    console.log(9)
	// 4 1 3 6 7 9 2 8 5 
  1. 创建函数 async1
  2. 创建函数 async2
  3. 输出 ==> 4
  4. 设置定时器 将定时器放入 宏任务
  5. 执行函数 async1(); 输出 ==> 1 ,执行 await async2()等待async2返回结果 。输出 ==>3。将返回结果加入微任务。
  6. 创建 Promise 并立即执行 Promise里内容, 输出 ==> 6, 7, resolve()加入微任务。
  7. 输出 ==> 9 。 主线程完成。
  8. 执行微任务 ==> 2, ==> 8。事件循环 先查找宏任务中的微任务。就是先执行微任务。
  9. 红任务 ==> 5。

11、类型判断 a 为 ? 时 执行到 “成功”

    const a = {
        i:0,
        toString(){
            return ++this.i;
        }
    };
    if(a == 1 && a == 2 && a == 3){
        console.log("成功")
    }

== 数据类型不一样, 对象 == 字符串 对象 tostring() 变为字符串 null == undefined 相等 但是和其他值比较就不再相等了 NaN == NaN 永远不相等 剩下的都是转为数字

数据劫持 defineProperty 进行监听

12、作用域

第一步: 创建全局(函数)执行上下文,并加入到栈顶。

第二步: 分析

  1. 找到所有的非函数中的var声明,
  2. 找到所有的顶级函数声明
  3. 找到顶级let const class 声明
  4. 找到块中声明的 函数名不与上述重复({}z中)

第三步: 名字重复处理 全局(函数)变量里如果有与顶级函数重名,则函数会覆盖。如果是是全局(函数)块中创建的函数会覆盖 如果是 let const class 重名 则报错。全局(函数)块中创建的则不做处理。

每一个块 都会有一个文本环境。

第四步: 创建绑定

  1. 登记并初始化var和undefined
  2. 顶级函数声明:登记function函数名,并初始化为新创建函数对象
  3. 块级中函数声明:登记名字 初始化为 undefined
  4. 登记let const class 但未初始化

第五步: 执行语句

块中创建的函数也会变量提升 会是undefined,全局会直接绑定初始化函数体。

13、面向对象

面向过程(pop)

优点:性能比面向过程高,适合跟硬件联系很紧密的东西

缺点:不易维护,不易复用,不易扩展

面向对象:

优点:易维护,易复用,易扩展,由于面向对象有封装继承多态的特性,可以设计出低耦合的系统,

缺点:性能没有面向过程高

类:抽象了对象的公共部分,是范指某一大类。 对象:特指某一个,通过类实例化一个具体的对象。

13.1、es6中创建类

class Name {

}

constructor();是类的构造函数,用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用该方法,如果没有 显示定义,则会自动会创建

extends 继承 就近原则,子类有直接用 ,没有向父类查找,知道找到,否则没有

super(); 调用父类的方法,它在这里表示父类的构造函数,用来新建父类的 this 对象。xsuper()相当于Parent.prototype.constructor.call(this),子类在构造函数中使用super,必须放在this前面(必须先调用父类的构造函数在使用子类的构造方法)

this和super的区别

  1. this关键词指向函数所在的当前对象
  2. super指向的是当前对象的原型对象
  3. constructor里边的this指向实例对象,方法里边的this指向这个方法的调用者。

13.2、构造函数和原型

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。我们可以把对象中的一些公共属性和方法抽取出来,然后封装到这个函数里。

构造函数的特点:

  1. 构造函数有原型对象prototype。
  2. 构造函数原型对象prototype里有 constructor 指向构造函数本身。
  3. 构造函数可以通过原型对象添加方法。
  4. 构造函数创建的实例对象有 proto 原型指向构造函数的原型对象。

13.2.1、new在执行时会做的几件事情

  1. 在内存中创建一个空对象。
  2. 让 this 指向这个新的对象。
  3. 执行构造函数里边的代码,给这个新对象添加属性和方法。
  4. 返回一个新的对象(构造函数如果没有return 和 返回的是基本类型则默认 当前构造函数的实例(当前的this),如果返回的是引用类型,则返回指定的数据)
  • 实例成员与静态成员

function Star(name){
    this.name = name;
}

const ss = new Star("风信子");
ss.name

实例成员就是构造函数内部通过this添加的成员(this.name),只能通过实例化的对象来访问(ss.name)。

Star.sex = "男";

静态成员 在构造函数身上直接添加的成员(Star.sex),只能通过构造函数方式访问(Star.sex)

13.2.2、原型对象 prototype

构造函数通过原型分配的函数是所有对象所共享的。

javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象,注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

Star.prototype.sing = function () {
      console.log(1) 
    }
    const fxz = new Star("风信子"); 
    const fxz2 = new Star("风信子2"); 
    console.dir(fxz.sing == fxz2.sing) // true

我们可以把哪些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。

13.2.3、对象原型 proto

对象都会有一个属性 proto 指向构造函数的 prototype原型对象,之所以我们的对象可以使用构造函数 prototype原型对象的属性和方法,就是因为对象有 proto 原型的存在。

console.log(fxz.__proto__ == Star.prototype) // true
  • proto 对象原型和原型对象prototype 是等价的。
  • proto 对象原型的意义就是在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此在开发中不能使用这个属性,它只是内部指向原型对象prototype。

image-20210727155828644.png

13.2.4、constructor 构造函数

对象原型(proto) 和构造函数原型对象(prototype)里都有一个constructor属性,constructor我们称为构造函数,因为它指向构造函数本身。

constructor主要用于记录该对象引用那个构造函数,它可以让原型对象重写指向原来的构造函数。

consol.log(Star.prototype.constructor == xz.__proto__.constructor ) // true

如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指向原来的构造函数。

Star.prototype={
  constructor:Star, // 重新指向构造函数
  sing: function(){
  },
}

13.2.5、构造函数、实例、原型对象三则之间的关系

image-20210727161928556.png

13.2.6、原型链

  1. 只要是对象就有__proto__ 原型,指向原型对象。
  2. 我们Star原型对象里边的__proto__原型指向的是 Object。prototype。
conso.log(Star.prototype.__proto__ === Object.prototype)
conso.log(Object.prototype.__proto__ === null ) // 顶层null

image-20210727164358481.png

13.2.7、成员查找机制(规则)

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性(方法)。
  2. 如果没有就差原型上是否存在(也就是__proto__指向的prototype原型对象)。
  3. 依次类推一直找到Object为止(null)

13.2.8、 构造函数的this指向

  1. 在构造函数中,里边的this指向的是对象实例。
  2. 原型对象的this指向的是实例对象。

13.2.9、扩展内置对象

可以通过原型对象,对原来的内置对象扩展自定义方法,也可以修改内置方法。

Array.prototype.xxx = function(){}

13.3、面向对象试题

function var 都具有变量提升作用

    // 1
    function Foo(){
        getName = function(){
            console.log(1)
        }
        return this;
    }

    // 2
    Foo.getName = function(){
        console.log(2);
    }

    // 3
    Foo.prototype.getName = function(){
        console.log(3);
    }

    // 4
    var getName = function(){
        console.log(4);
    }

    // 5
    function getName(){
        console.log(5);
    }

    Foo.getName(); //  2
    getName(); // 4
    Foo().getName(); // 1
    getName(); // 1
    new Foo.getName(); // 2
    new Foo().getName(); // 3
    new new Foo().getName(); // 3

代码编译(声明)阶段:

  1. 定义 Foo 内存地址中存放 函数体和 (2)号getName 属性,以及原型上的(3)getName 。
  2. 定义全局的 (5)getName,(4)声明的变量没有实体内容,被(5)号覆盖

执行阶段:

  1. Foo.getName() 结果为 2 直接访问的是Foo getName 属性 所以执行的是(2)
  2. getName() 函数调用,结果是 4(4)(5)存在异议,在在是执行函数表达式赋值 (4)。函数声明是在声明阶段就会赋值,此处有函数表达式,覆盖原先的(5)。
  3. Foo().getName() 构造函数当普通函数使用,函数的调用。首先调用 Foo函数,并修改全局getName,当再次执行getName 结果为 1
  4. getName 此时的getName 已被修改 结果同上 1。 以下涉及到运算符优先级问题。 developer.mozilla.org/zh-CN/docs/…
  5. new Foo.getName() ,成员访问和函数调用的优先级大于new参数列表,先执行 Foo.getName() 然后在 new。结果为 2
  6. new Foo().getName(); new有参数列表和成员访问和函数调用的优先级 相等,左向右执行,先进行 new Foo(),实例化时候自身属性没有,调用原型上的 getName,(1)的getName 属于window上的。
  7. new new Foo().getName() 先执行 new Foo() 得到实例 xxxx ,然后 new xxxx.getName() 执行原型上的结果 3

13.4、继承

在e s6之前并没有给我们提供extends继承,我们可以通过构造函数+原型对象模拟实现继承,被称为 组合继承。

13.4.1、借用父构造函数继承属性

// 1.父构造函数
function Father(name,age){
	// this指向父构造函数的对象实例
	this.name = name;
	this.age = age;
}
// 子构造函数
fuction Son(name,age,score){
	// this指向子构造函数的对象实例
	Father.call(this,name,age) // 修改父this继承属性
  this.score = score;
}

const son = new Son('风信子',18,100 );
console.log(son);

13.4.2、借用原型对象继承父类方法

// 1.父构造函数
function Father(name,age){
	// this指向父构造函数的对象实例
	this.name = name;
	this.age = age;
}

// 父原型上添加方法
Father.prototype.money = function(){
  console.log(100000);
}

// 子构造函数
fuction Son(name,age,score){
	// this指向子构造函数的对象实例
	Father.call(this,name,age) // 修改父this
  this.score = score;
}

// Son.prototype = Father.prototype // 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会一起变化的

Son.prototype = new Father();// 继承父 
// 如果利用对象的形式修改了原型对象,别忘了利用 constructor 指回原来的构造函数。
Son.prototype.constructor = Son;

Son.prototype.exam = function(){
  console.log("考试")
}

const son = new Son('风信子',18,100 );
console.log(son);



13.4.3、通过类实现面向对象编程

类的本质其实还是一个函数,我们可以理解为类是构造函数的另一种写法。

类的特征:

  1. 类有原型对象 prototype。
  2. 类的原型对象prototype里有 constructor 指向类本身。
  3. 类可以通过原型对象添加方法。
  4. 类创建的实例对象有 proto 原型指向类的原型对象。

所以ES6的类的绝大部分功能,都可以实现ES5继承,新的class写法只是让对象原型的写法更清晰,ES6的class其实就是语法糖。