关于前端:字节前端必会面试题

37次阅读

共计 9403 个字符,预计需要花费 24 分钟才能阅读完成。

哪些状况会导致内存透露

1、意外的全局变量:因为应用未声明的变量, 而意外的创立了一个全局变量, 而使这个变量始终留在内存中无奈被回收
2、被忘记的计时器或回调函数:设置了 setInterval 定时器,而遗记勾销它,如果循环函数有对外部变量的援用的话,那么这个变量会被始终留在内存中,而无奈被回收。3、脱离 DOM 的援用:获取一个 DOM 元素的援用,而前面这个元素被删除,因为始终保留了对这个元素的援用,所以它也无奈被回收。4、闭包:不合理的应用闭包,从而导致某些变量始终被留在内存当中。

—- 问题知识点分割线 —-

如何取得对象非原型链上的属性?

应用后 hasOwnProperty() 办法来判断属性是否属于原型链的属性:

function iterate(obj){var res=[];
   for(var key in obj){if(obj.hasOwnProperty(key))
           res.push(key+':'+obj[key]);
   }
   return res;
} 

—- 问题知识点分割线 —-

伪元素和伪类的区别和作用?

  • 伪元素:在内容元素的前后插入额定的元素或款式,然而这些元素实际上并不在文档中生成。它们只在内部显示可见,但不会在文档的源代码中找到它们,因而,称为“伪”元素。例如:
p::before {content:"第一章:";}
p::after {content:"Hot!";}
p::first-line {background:red;}
p::first-letter {font-size:30px;}
  • 伪类:将非凡的成果增加到特定选择器上。它是已有元素上增加类别的,不会产生新的元素。例如:
a:hover {color: #FF00FF}
p:first-child {color: red}

总结: 伪类是通过在元素选择器上加⼊伪类扭转元素状态,⽽伪元素通过对元素的操作进⾏对元素的扭转。

—- 问题知识点分割线 —-

head 标签有什么作用,其中什么标签必不可少?

标签用于定义文档的头部,它是所有头部元素的容器。中的元素能够援用脚本、批示浏览器在哪里找到样式表、提供元信息等。

文档的头部形容了文档的各种属性和信息,包含文档的题目、在 Web 中的地位以及和其余文档的关系等。绝大多数文档头部蕴含的数据都不会真正作为内容显示给读者。

上面这些标签可用在 head 局部:<base>, <link>, <meta>, <script>, <style>, <title>

其中 <title> 定义文档的题目,它是 head 局部中惟一必须的元素。

—- 问题知识点分割线 —-

深拷贝

实现一:不思考 Symbol

function deepClone(obj) {if(!isObject(obj)) return obj;
    let newObj = Array.isArray(obj) ? [] : {};
    // for...in 只会遍历对象本身的和继承的可枚举的属性(不含 Symbol 属性)for(let key in obj) {// obj.hasOwnProperty() 办法只思考对象本身的属性
        if(obj.hasOwnProperty(key)) {newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj;
}

实现二:思考 Symbol

// hash 作为一个查看器,防止对象深拷贝中呈现环援用,导致爆栈
function deepClone(obj, hash = new WeakMap()) {if(!isObject(obj)) return obj;
    // 查看是有存在雷同的对象在之前拷贝过,有则返回之前拷贝后存于 hash 中的对象
    if(hash.has(obj)) return hash.get(obj);
    let newObj = Array.isArray(obj) ? [] : {};
    // 备份存在 hash 中,newObj 目前是空对象、数组。前面会对属性进行追加,这里存的值是对象的栈
    hash.set(obj, newObj);
    // Reflect.ownKeys 返回一个数组,蕴含对象本身的(不含继承的)所有键名,不论键名是 Symbol 或字符串,也不论是否可枚举。Reflect.ownKeys(obj).forEach(key => {
        // 属性值如果是对象,则进行递归深拷贝,否则间接拷贝
        newObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key];
    });
    return newObj;
}

—- 问题知识点分割线 —-

对 sticky 定位的了解

sticky 英文字面意思是粘贴,所以能够把它称之为粘性定位。语法:position: sticky; 基于用户的滚动地位来定位。

粘性定位的元素是依赖于用户的滚动,在 position:relativeposition:fixed 定位之间切换。它的行为就像 position:relative; 而当页面滚动超出指标区域时,它的体现就像 position:fixed;,它会固定在指标地位。元素定位体现为在逾越特定阈值前为绝对定位,之后为固定定位。这个特定阈值指的是 top, right, bottom 或 left 之一,换言之,指定 top, right, bottom 或 left 四个阈值其中之一,才可使粘性定位失效。否则其行为与绝对定位雷同。

—- 问题知识点分割线 —-

继承

原型继承

核心思想:子类的原型成为父类的实例

实现

function SuperType() {this.colors = ['red', 'green'];
}
function SubType() {}
// 原型继承要害: 子类的原型成为父类的实例
SubType.prototype = new SuperType();

// 测试
let instance1 = new SubType();
instance1.colors.push('blue');

let instance2 = new SubType();
console.log(instance2.colors);  // ['red', 'green', 'blue']

原型继承存在的问题

  1. 原型中蕴含的援用类型属性将被所有实例对象共享
  2. 子类在实例化时不能给父类构造函数传参

构造函数继承

核心思想:在子类构造函数中调用父类构造函数

实现

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green'];
    this.getName = function() {return this.name;}
}
function SubType(name) {
    // 继承 SuperType 并传参
    SuperType.call(this, name);
}

// 测试
let instance1 = new SubType('instance1');
instance1.colors.push('blue');
console.log(instance1.colors); // ['red','green','blue']

let instance2 = new SubType('instance2');
console.log(instance2.colors);  // ['red', 'green']

构造函数继承 的呈现是为了解决了原型继承的援用值共享问题。长处是能够在子类构造函数中向父类构造函数传参。它存在的问题是:1)因为办法必须在构造函数中定义,因而办法不能重用。2)子类也不能拜访父类原型上定义的办法。

组合继承

核心思想:综合了原型链和构造函数,即,应用原型链继承原型上的办法,而通过构造函数继承实例属性。

实现

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {console.log(this.name);
}
function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name);
    // 实例属性
    this.age = age;
}
// 继承办法
SubType.prototype = new SuperType();

// 测试
let instance1 = new SubType('instance1', 1);
instance1.sayName();  // "instance1"
instance1.colors.push("blue");
console.log(instance1.colors); // ['red','green','blue']

let instance2 = new SubType('instance2', 2);
instance2.sayName();  // "instance2"
console.log(instance2.colors); // ['red','green']

组合继承 存在的问题是:父类构造函数始终会被调用两次:一次是在创立子类原型时 new SuperType() 调用,另一次是在子类构造函数中 SuperType.call() 调用。

寄生式组合继承(最佳)

核心思想:通过构造函数继承属性,但应用混合式原型继承办法,即,不通过调用父类构造函数给子类原型赋值,而是获得父类原型的一个正本。

实现

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {console.log(this.name);
}
function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name);
    this.age = age;
}
// 继承办法
SubType.prototype = Object.create(SuperType.prototype);
// 重写原型导致默认 constructor 失落,手动将 constructor 指回 SubType
SubType.prototype.constructor = SubType;

class 实现继承(ES6)

核心思想:通过 extends 来实现类的继承(相当于 ES5 的原型继承)。通过 super 调用父类的构造方法 (相当于 ES5 的构造函数继承)。

实现

class SuperType {constructor(name) {this.name = name;}
    sayName() {console.log(this.name);
    }
}
class SubType extends SuperType {constructor(name, age) {super(name);  // 继承属性
        this.age = age;
    }
}

// 测试
let instance = new SubType('instance', 0);
instance.sayName();  // "instance"

尽管类继承应用的是新语法,但背地仍旧应用的是原型链。

—- 问题知识点分割线 —-

为什么有时候⽤ translate 来扭转地位⽽不是定位?

translate 是 transform 属性的⼀个值。扭转 transform 或 opacity 不会触发浏览器从新布局(reflow)或重绘(repaint),只会触发复合(compositions)。⽽扭转相对定位会触发从新布局,进⽽触发重绘和复合。transform 使浏览器为元素创立⼀个 GPU 图层,但扭转相对定位会使⽤到 CPU。因而 translate()更⾼效,能够缩短平滑动画的绘制工夫。⽽ translate 扭转地位时,元素仍然会占据其原始空间,相对定位就不会发⽣这种状况。

—- 问题知识点分割线 —-

New 操作符做了什么事件?

1、首先创立了一个新对象
2、设置原型,将对象的原型设置为函数的 prototype 对象
3、让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)4、判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象

—- 问题知识点分割线 —-

对浏览器的了解

浏览器的次要性能是将用户抉择的 web 资源出现进去,它须要从服务器申请资源,并将其显示在浏览器窗口中,资源的格局通常是 HTML,也包含 PDF、image 及其他格局。用户用 URI(Uniform Resource Identifier 对立资源标识符)来指定所申请资源的地位。

HTML 和 CSS 标准中规定了浏览器解释 html 文档的形式,由 W3C 组织对这些标准进行保护,W3C 是负责制订 web 规范的组织。然而浏览器厂商纷纷开发本人的扩大,对标准的遵循并不欠缺,这为 web 开发者带来了重大的兼容性问题。

浏览器能够分为两局部,shell 和 内核。其中 shell 的品种绝对比拟多,内核则比拟少。也有一些浏览器并不辨别外壳和内核。从 Mozilla 将 Gecko 独立进去后,才有了外壳和内核的明确划分。

  • shell 是指浏览器的外壳:例如菜单,工具栏等。次要是提供给用户界面操作,参数设置等等。它是调用内核来实现各种性能的。
  • 内核是浏览器的外围。内核是基于标记语言显示内容的程序或模块。

—- 问题知识点分割线 —-

如何提取高度嵌套的对象里的指定属性?

有时会遇到一些嵌套水平十分深的对象:

const school = {
   classes: {
      stu: {
         name: 'Bob',
         age: 24,
      }
   }
}

像此处的 name 这个变量,嵌套了四层,此时如果依然尝试老办法来提取它:

const {name} = school

显然是不见效的,因为 school 这个对象自身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象外面。要想把 name 提取进去,一种比拟笨的办法是逐层解构:

const {classes} = school
const {stu} = classes
const {name} = stu
name // 'Bob'

然而还有一种更规范的做法,能够用一行代码来解决这个问题:

const {classes: { stu: { name} }} = school

console.log(name)  // 'Bob'

能够在解构进去的变量名右侧,通过冒号 +{指标属性名}这种模式,进一步解构它,始终解构到拿到指标数据为止。

—- 问题知识点分割线 —-

Promise.all 和 Promise.race 的区别的应用场景

(1)Promise.all Promise.all能够将多个 Promise 实例包装成一个新的 Promise 实例。同时,胜利和失败的返回值是不同的,胜利的时候返回的是 一个后果数组 ,而失败的时候则返回 最先被 reject 失败状态的值

Promise.all 中传入的是数组,返回的也是是数组,并且会将进行映射,传入的 promise 对象返回的值是依照程序在数组中排列的,然而留神的是他们执行的程序并不是依照程序的,除非可迭代对象为空。

须要留神,Promise.all 取得的胜利后果的数组外面的数据程序和 Promise.all 接管到的数组程序是统一的,这样当遇到发送多个申请并依据申请程序获取和应用数据的场景,就能够应用 Promise.all 来解决。

(2)Promise.race

顾名思义,Promse.race 就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])外面哪个后果取得的快,就返回那个后果,不论后果自身是胜利状态还是失败状态。当要做一件事,超过多长时间就不做了,能够用这个办法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

—- 问题知识点分割线 —-

实现 JSONP 跨域

JSONP 外围原理script 标签不受同源策略束缚,所以能够用来进行跨域申请,长处是兼容性好,然而只能用于 GET 申请;

实现

const jsonp = (url, params, callbackName) => {const generateUrl = () => {
        let dataSrc = "";
        for(let key in params) {if(params.hasOwnProperty(key)) {dataSrc += `${key}=${params[key]}&`
            }
        }
        dataSrc += `callback=${callbackName}`;
        return `${url}?${dataSrc}`;
    }
    return new Promise((resolve, reject) => {const scriptEle = document.createElement('script');
        scriptEle.src = generateUrl();
        document.body.appendChild(scriptEle);
        window[callbackName] = data => {resolve(data);
            document.removeChild(scriptEle);
        }
    });
}

—- 问题知识点分割线 —-

对事件委托的了解

(1)事件委托的概念

事件委托实质上是利用了 浏览器事件冒泡 的机制。因为事件在冒泡过程中会上传到父节点,父节点能够通过事件对象获取到指标节点,因而能够把子节点的监听函数定义在父节点上,由父节点的监听函数对立解决多个子元素的事件,这种形式称为事件委托(事件代理)。

应用事件委托能够不必要为每一个子元素都绑定一个监听事件,这样缩小了内存上的耗费。并且应用事件代理还能够实现事件的动静绑定,比如说新增了一个子节点,并不需要独自地为它增加一个监听事件,它绑定的事件会交给父元素中的监听函数来解决。

(2)事件委托的特点

  • 缩小内存耗费

如果有一个列表,列表之中有大量的列表项,须要在点击列表项的时候响应一个事件:

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>

如果给每个列表项一一都绑定一个函数,那对于内存耗费是十分大的,效率上须要耗费很多性能。因而,比拟好的办法就是把这个点击事件绑定到他的父层,也就是 ul 上,而后在执行事件时再去匹配判断指标元素,所以事件委托能够缩小大量的内存耗费,节约效率。

  • 动静绑定事件

给上述的例子中每个列表项都绑定事件,在很多时候,须要通过 AJAX 或者用户操作动静的减少或者去除列表项元素,那么在每一次扭转的时候都须要从新给新增的元素绑定事件,给行将删去的元素解绑事件;如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和指标元素的增减是没有关系的,执行到指标元素是在真正响应执行事件函数的过程中去匹配的,所以应用事件在动静绑定事件的状况下是能够缩小很多反复工作的。

// 来实现把 #list 下的 li 元素的事件代理委托到它的父层元素也就是 #list 上:// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
  // 兼容性解决
  var event = e || window.event;
  var target = event.target || event.srcElement;
  // 判断是否匹配指标元素
  if (target.nodeName.toLocaleLowerCase === 'li') {console.log('the content is:', target.innerHTML);
  }
});

在上述代码中,target 元素则是在 #list 元素之下具体被点击的元素,而后通过判断 target 的一些属性(比方:nodeName,id 等等)能够更准确地匹配到某一类 #list li 元素之上;

(3)局限性

当然,事件委托也是有局限的。比方 focus、blur 之类的事件没有事件冒泡机制,所以无奈实现事件委托;mousemove、mouseout 这样的事件,尽管有事件冒泡,然而只能一直通过地位去计算定位,对性能耗费高,因而也是不适宜于事件委托的。

当然事件委托不是只有长处,它也是有 毛病 的,事件委托会影响页面性能,次要影响因素有:

  • 元素中,绑定事件委托的次数;
  • 点击的最底层元素,到绑定事件元素之间的 DOM 层数;

在必须应用事件委托的中央,能够进行如下的解决:

  • 只在必须的中央,应用事件委托,比方:ajax的部分刷新区域
  • 尽量的缩小绑定的层级,不在 body 元素上,进行绑定
  • 缩小绑定的次数,如果能够,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行散发。

—- 问题知识点分割线 —-

渲染过程中遇到 JS 文件如何解决?

JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行结束,浏览器再从中断的中央复原持续解析文档。也就是说,如果想要首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都倡议将 script 标签放在 body 标签底部的起因。当然在当下,并不是说 script 标签必须放在底部,因为你能够给 script 标签增加 defer 或者 async 属性。

—- 问题知识点分割线 —-

link 和 @import 的区别

两者都是内部援用 CSS 的形式,它们的区别如下:

  • link 是 XHTML 标签,除了加载 CSS 外,还能够定义 RSS 等其余事务;@import 属于 CSS 领域,只能加载 CSS。
  • link 援用 CSS 时,在页面载入时同时加载;@import 须要页面网页齐全载入当前加载。
  • link 是 XHTML 标签,无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不反对。
  • link 反对应用 Javascript 管制 DOM 去扭转款式;而 @import 不反对。

—- 问题知识点分割线 —-

如何⽤ webpack 来优化前端性能?

⽤ webpack 优化前端性能是指优化 webpack 的输入后果,让打包的最终后果在浏览器运⾏疾速⾼效。

  • 压缩代码:删除多余的代码、正文、简化代码的写法等等⽅式。能够利⽤ webpack 的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩 JS ⽂件,利⽤ cssnano(css-loader?minimize)来压缩 css
  • 利⽤ CDN 减速: 在构建过程中,将引⽤的动态资源门路批改为 CDN 上对应的门路。能够利⽤ webpack 对于 output 参数和各 loader 的 publicPath 参数来批改资源门路
  • Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。能够通过在启动 webpack 时追加参数 –optimize-minimize 来实现
  • Code Splitting: 将代码按路由维度或者组件分块(chunk), 这样做到按需加载, 同时能够充沛利⽤浏览器缓存
  • 提取公共第三⽅库: SplitChunksPlugin 插件来进⾏公共模块抽取, 利⽤浏览器缓存能够⻓期缓存这些⽆需频繁变动的公共代码

正文完
 0