timg (1).jpeg

数据类型

在js中,数据类型分为基本数据类型和引用数据类型
基本数据类型:string、number、boolean、symbol、null、undefined
引用数据类型:function、object
我们可以通过typeof简单的区分js数据类型,能够区分基本数据类型以及对象类型,对于string、number、boolean、symbol、undefined、function、object,而object类型有object、array、null,由于历史的问题,这里null被划分到了object。如果我们要获取具体的类型,我们可以使用Object.prototype.toString.call(obj)来获取更加准确的类型,对此可以进行函数封装

  1. /**
  2. * 获取准确数据类型
  3. * @param obj 要获取的数据类型的数据
  4. * @return string 数据类型,
  5. * 有 Null、Object、Array、Undefined、String、Boolean、Number、Function、Symbol
  6. */
  7. function getType(obj) {
  8. const baseType = Object.prototype.toString.call(obj);
  9. const startIndex = 8;
  10. const endIndex = baseType.length - 1;
  11. return baseType.substring(startIndex, endIndex);
  12. }

数组方法

改变原数组:push、pop、unshift、shift、splice

substr和substring的区别

  • substr(startIndex, length)

startIndex:起始位置
length:截取的长度

  • substring(startIndex, endIndex)

startIndex:起始位置
endIndex:结束位置(不包括在内,即区间是左闭右开)

原型链

每一个构造函数的实例都有一个隐式原型proto,每一个构造函数都有一个显示原型,实例的隐式原型指向构造函数的显示原型prototype,显示原型prototype的构造器指向构造函数自己,构造函数的构造器指向Function,这样层层向上,直到null为止,可以参考下图:
304846-f8d51c4770bca075.webp

instanceof原理

利用原型链判断实例是否出现在原型链上。其实我们可以思考一下,上面的结构是不是有点像数据结构的中链表,那么我们来改造一部分。
image.png
好了,这样是不是比较好理解了,这样实现模拟也就不难了。

  1. function _instanceof(o1, o2) {
  2. let current = o1.__proto__;
  3. while (current !== null) {
  4. if (current === o2.prototype) {
  5. return true;
  6. }
  7. current = current.__proto__;
  8. }
  9. return false;
  10. }

继承

作用域

在js中,作用域一共有3种,分别是:全局作用域、函数作用域和块级作用域,而闭包是作用域一种特殊的应用。

闭包

闭包就是函数A中嵌套函数B,函数B使用到了函数A中的变量,函数A返回函数B。闭包用来创建私有变量,闭包中的数据时不能被外部直接访问的,所以可能会造成内存泄漏,因此尽可能少用闭包。
应用:模拟ES6中Map

  1. function Map() {
  2. this.data = {};
  3. return (function(data) {
  4. return {
  5. add: function(key, value) {
  6. data[key] = value;
  7. },
  8. get: function(key) {
  9. return data[key];
  10. },
  11. clear: function() {
  12. data = {};
  13. },
  14. remove: function(key) {
  15. delete data[key];
  16. },
  17. getSize: function() {
  18. return Object.keys(data).length;
  19. }
  20. };
  21. })(this.data);
  22. }

var、let、const

用var声明的变量可以会变量提升,而let、const不会有变量提升,使用let和const会生成块级作用域,var和let都是声明变量,const是声明常量,此常量并不是说不可以修改,对于原始数据类型来说是不能修改的,但是如果是引用数据类型就可以修改该引用类型里面的属性值。let会存在暂时性死区。

for…in…和for…of…的区别

for…in…是ES5中的,for…of…是ES6中的。
for…in…输出的是key,for…of…输出的是value。
如果给遍历对象添加自定义属性,for…in…会遍历自身的自定义属性,for…of…不会。
遍历对象推荐使用for…in…,遍历数组推荐使用for…of…,且for…in..不能遍历对象。

使用单例模式实现Storage,并对localStorage进行封装设置值setItem(key,value)和getItem(key)

  1. class Storage {
  2. static getInstance() {
  3. if (!this.instance) {
  4. this.instance = new Storage();
  5. }
  6. return this.instance;
  7. }
  8. getItem(key) {
  9. return localStorage.getItem(key);
  10. }
  11. setItem(key, value) {
  12. localStorage.setItem(key, value);
  13. }
  14. }

浏览器存储

浏览器中的存储方式有3中,古老的cookie,现代的localStorage、sessionStorage

cookie

cookie是用来辨别身份,进行session跟踪而存储在用户本地终端上的数据,由客户机暂时保存。由于每次请求都会携带cookie,所以这就会浪费带宽,因为有时候并不需要cookie。

localStorage

本地永久性存储,只能被动删除。

sessionStorage

会话级别的存储,当浏览器标签页关闭后,数据就会被删除。

区别

cookie由于古老,所以没有友好的api,而localStorage、sessionStorage有便捷的api,如getItem、setItem

取出url参数

方法1:分割

  1. // http://www.baidu.com?name=xx&age=22
  2. function getParams(url) {
  3. const urls = url.split('?');
  4. const params = urls[1].split('&');
  5. const res = {};
  6. params.forEach(item => {
  7. const _p = item.split('=');
  8. res[_p[0]] = _p[1]
  9. });
  10. return res;
  11. }

方法2:URLSearchParams

利用URLSearchParams对象

  1. const p = new URLSearchParams(url);
  2. p.has(key)
  3. p.append(key, value)
  4. p.getAll()
  5. p.get(key)
  6. p.set(key, value)
  7. p.keys()
  8. p.values()
  9. p.sort()

类数组转换成数组

ES5方法

  1. Array.prototype.slice.call(arguments)

ES6方法

  1. Array.from(arguments)

模块化

模块化有AMD、CMD、CommonJS、ES6

CommonJS

使用同步方式加载模块,Node.js是commonJS的主要实践者,它有4个重要的环境变量为模块化提供支持:module、exports、require、global。实际使用时,用module.exports对外暴露(不推荐使用exports),用require来引入模块。

AMD

采用异步方式加载模块,模块的加载不影响它后面的语句的执行,所有依赖这个模块的语句,都定义在一个回调函数中,等加载完成之后,这个回调函数才会执行。

CMD

CMD和AMD很相似,不同在于AMD推崇依赖前置、提前执行,而CMD推崇依赖就近、延迟执行

ES6

使用export暴露模块,使用import导入模块

CommonJS和ES6的区别

CommonJS模块输出是一个值的拷贝,ES6模块输出是值得引用。
CommonJS是运行时加载,ES6是编译时加载

ajax

  1. function ajax(opts) {
  2. return new Promise((resolve, reject) => {
  3. const xhr = new XMLHttpRequest();
  4. xhr.open(opts.method, opts.url, opts.async);
  5. xhr.onreadystatechange = function(res) {
  6. if (
  7. res.target.readyState === 4 &&
  8. res.target.status === 200
  9. ) {
  10. resolve(JSON.parse(res.target.responseText));
  11. } else {
  12. reject(res);
  13. }
  14. };
  15. xhr.send(opts.data);
  16. });
  17. }

call、apply、bind的区别以及如何模拟

区别

都是用来改变this的指向,call和apply是立即执行函数,而bind不是;call和bind传参都是以任意参数传递,而apply是以列表来传递参数

模拟

  1. Function.prototype._call = function(ctx, ..args) {
  2. // 把当前this复制给ctx的_fn函数
  3. ctx._fn = this;
  4. // 执行ctx的_fn函数,这样_fn的this就是ctx
  5. ctx._fn(...args)
  6. delete ctx._fn
  7. }
  8. Function.prototype._apply = function(ctx, ..args) {
  9. ctx._fn = this;
  10. ctx._fn(...args[0])
  11. delete ctx._fn
  12. }
  13. Function.prototype._bind = function(ctx, ...args) {
  14. return () => {
  15. ctx._fn = this;
  16. ctx._fn(...args)
  17. delete ctx._fn
  18. }
  19. }

数组去重

传统方法

  1. function unique(arr) {
  2. let _arr = [];
  3. arr.filter(item => _arr.indexof(item) === -1 && _arr.push(item))
  4. return _arr;
  5. }

ES6

  1. Array.from(new Set(arguments))

数组扁平化

  1. function flat(arr) {
  2. const isDeep = arr.some(item => item instanceof Array);
  3. if (!isDeep) {
  4. return arr;
  5. }
  6. const res = Array.prototype.concat.apply([], arr);
  7. return falt(res);
  8. }

浅拷贝和深拷贝

浅拷贝就是只拷贝一层;深拷贝就是不管有几层,全部拷贝,不能存在引用关系

浅拷贝

  1. function clone(obj) {
  2. if (typeof obj !== 'object' || typeof obj === null) {
  3. return obj;
  4. }
  5. let res;
  6. if (obj instanceof Array) {
  7. res = [];
  8. res = obj.concat();
  9. } else {
  10. res = {};
  11. Object.assign(res, obj)
  12. }
  13. return res;
  14. }

深拷贝

  1. function deepClone(obj) {
  2. if (typeof obj !== 'object' || typeof obj === null) {
  3. return obj;
  4. }
  5. let res;
  6. if (obj instanceof Array) {
  7. res = [];
  8. } else {
  9. res = {};
  10. }
  11. const keys = Object.keys(obj);
  12. for(let key in keys) {
  13. res[key] = clone(obj[key])
  14. }
  15. return res;
  16. }

实现一个equal函数,只要值一样,那么两个对象就相等

  1. function isObject(obj) {
  2. return typeof obj === 'object' && obj !== null
  3. }
  4. function equal(obj, compare) {
  5. if (!isObject(obj) || !isObject(compare)) {
  6. return obj === compare;
  7. }
  8. if (obj === compare) {
  9. return true;
  10. }
  11. const keys = Object.keys(obj);
  12. const compareKeys = Object.keys(compare);
  13. if (keys.length !== compareKeys.length) {
  14. return false;
  15. }
  16. for (let key in obj) {
  17. let res = equal(obj[key] === compare[key]);
  18. if (!res) {
  19. return false;
  20. }
  21. }
  22. return true;
  23. }

自定义事件

目前js中,主要通过Event()和CustomEvent()来实现自定义事件。

Event()

创建简单的自定义事件

  1. let event = new Event(eventName, options)

eventName:事件名称
options 配置项,包括

字段名称 说明 类型 默认值
bubbles 该事件是否冒泡 Boolean false
cancelable 该事件能否被取消 Boolean false
composed 该事件是否会在影子DOM根节点之外触发侦听器 Boolean false

示例:

  1. // 创建一个支持冒泡且不能被取消的 pingan 事件
  2. let myEvent = new Event("pingan", {"bubbles":true, "cancelable":false});
  3. document.dispatchEvent(myEvent);
  4. // 事件可以在任何元素触发,不仅仅是document
  5. testDOM.dispatchEvent(myEvent);

CustomEvent()

用于创建需要传参的自定义事件

  1. let event = new CustomEvent(eventName, options)

eventName:事件名称
options 配置项,包括

字段名称 说明 类型 默认值
bubbles 该事件是否冒泡 Boolean false
cancelable 该事件能否被取消 Boolean false
detail 表示该事件中需要被传递的数据,在 EventListener 获取。 Any null

示例:

  1. // 创建事件
  2. let myEvent = new CustomEvent("ce", {
  3. detail: { name: "xxx" }
  4. });
  5. // 添加适当的事件监听器
  6. window.addEventListener("ce", e => {
  7. alert(`ce事件触发,是 ${e.detail.name} 触发。`);
  8. });
  9. document.getElementById("leo").addEventListener(
  10. "click", function () {
  11. // 派发事件
  12. window.dispatchEvent(myEvent);
  13. }
  14. )

使用场景

场景1:单个目标对象发生改变,需要通知多个观察者一同改变。

场景2:解耦多模块开协作。

事件流

事件流分为两种,分别是冒泡事件流和捕获事件流。

  • 冒泡事件流:存在于普通浏览器中,从具体的元素向不具体元素冒泡。
  • 捕获事件流:存在于IE浏览器中,从不具体的元素向具体的元素捕获。

DOM事件流:捕获阶段 -> 目标阶段 -> 冒泡阶段

什么是事件委托

事件委托利用事件冒泡机制,将原本子元素要干的事交给父级元素来干。

EventLoop

js是单线程的,所有的代码默认存在于宏任务中,如果遇到异步任务,则放入微任务中,首先去执行宏任务,如果宏任务为空,则执行微任务,微任务执行完后,再去判断是否有宏任务,这样反复循环,就形成了EventLoop。
宏任务:普通的代码
微任务:setTimeout/setInterval中的回调函数、process.nextTick、promise的then回调

下面代码的结果是什么?

  1. console.log(1)
  2. setTimeout(function() {
  3. console.log(2)
  4. })
  5. process.nextTick(function() {
  6. console.log(3)
  7. })
  8. let p = new Promise((resolve, reject) => {
  9. console.log(4);
  10. resolve()
  11. });
  12. p.then(() => {
  13. console.log(5)
  14. })
  15. console.log(6)

答案:1 4 6 5 3 2

实现一个Promise

Promise是目前js中异步编程最流行的解决方案。

同源策略

对于浏览器来说,只有同源的数据可以访问,即协议、域名、端口一致,而js、css、img属于资源文件,所以就可以跨域。

跨域的方法

JSONP
CROS
window.postMessage
WebSocket

JSONP

概念

jsonp是一种跨域的方案,由于script可以跨域的,所以通过动态创建script标签来实现跨域

手写Promise版JSONP

  1. function jsonp(options) {
  2. return new Promise((resolve, reject) => {
  3. function clean(script, script) {
  4. document.body.remove(script);
  5. window[options.cbName] = null;
  6. }
  7. function concatUrl(url, params) {
  8. let _url = url + "?";
  9. for (let key in params) {
  10. _url += key + "=" + params[key] + "&";
  11. }
  12. return _url;
  13. }
  14. if (!options.url) {
  15. reject({ msg: "url不能为空" });
  16. }
  17. if (!options.cbName) {
  18. reject({ msg: "cbName不能为空" });
  19. }
  20. window[options.cbName] = function(res) {
  21. resolve(res);
  22. };
  23. let url = concatUrl(options.url, options.params);
  24. const _script = document.createElement("script");
  25. _script.src = url;
  26. document.body.appendChild(_script);
  27. _script.onload = function() {
  28. clean(_script, options.cbName);
  29. };
  30. _script.onerror = function(err) {
  31. clean(_script, options.cbName);
  32. reject({ msg: err });
  33. };
  34. });
  35. }
  36. // 示例
  37. jsonp({
  38. url: "http://localhost:5500/user.js",
  39. cbName: "",
  40. params: {
  41. age: 22
  42. }
  43. })
  44. .then(res => {
  45. console.log(res);
  46. })
  47. .catch(err => {
  48. console.log(err);
  49. });

用户从输入URL回车后到浏览器呈现页面发生了什么

image.png
用户输入url回车后,浏览器就会开启一个线程来处理了这个请求,首先去DNS缓存中寻找url对应的ip,如果没找到就去系统的hosts文件中找,hosts文件也没找到就会去路由器缓存中找,都没找到就使用DNS解析出ip,然后建立TCP/IP连接,向服务器发起请求,服务器做出处理并响应数据给浏览器,浏览器拿到数据后就开始解析;首先htmlParser来解析DOM生成DOMTree,同时cssParser来解析CSS生成CSSOMTree,两者都解析完了,就会合并成renderTree。在renderTree这里会有reflow和repaint,最后调用paint方法将页面绘制在浏览器上。要注意的是如果在接下DOM的时候,遇到了script脚本,那么就会阻塞DOM的解析,因为js是单线程的,如果DOM解析和script同时进行,那么浏览器到底该听谁的。对于script有三种状态:async、defer、无async和defer属性。

  • async:当script使用async时,DOM的解析和js的解析是同步的,当js解析完后,js会立刻执行同时DOM解析会被停止,js执行完后,才会继续进行DOM解析
  • defer:当script使用async时,DOM的解析和js的解析是同步的,当js解析完后,js不会立即执行,而DOM会继续解析,等到DOM全部解析完后,js才会执行
  • 无async和defer属性:当script不使用任何属性时,遇到js时,DOM解析就会暂停,等到js解析并执行完毕后DOM才会继续解析。

    性能优化

    性能优化就可以根据上面一提来看

    性能监控

    异常监控

    对于代码错误,可以使用window.onerror

    节流

    节流就是在一定周期内,该函数只执行一次。
  1. function throttle(fn, delay = 100) {
  2. let timer;
  3. return () => {
  4. if (timer) {
  5. return;
  6. }
  7. timer = setTimeout(() => {
  8. fn.call(this);
  9. timer = null;
  10. }, delay)
  11. }
  12. }

防抖

防抖就是一段时间后,如果没有再次触发事件,则执行该事件。

  1. function debounce(fn, delay = 300) {
  2. let timer;
  3. return () => {
  4. if (timer) {
  5. clearTimeout(timer)
  6. }
  7. timer = setTimeout(() => {
  8. fn.call(this, arguments);
  9. timer = null;
  10. }, delay)
  11. }
  12. }

web安全

XSS攻击

XSS攻击(Cross-site scripting)即跨站脚本攻击,是一种针对网站应用程序的安全漏洞的攻击,是代码注入的一种,它允许用户将恶意的代码注入到网页,其他用户访问网页时就会受到影响。
防御措施:

  • 过滤特殊字符。

举例:攻击者在评论区提交获取cookie的代码,其它用户访问该评论所在网页就会获取到cookie信息。

CSRF攻击

CSRF(Cross-site request forgery)攻击即跨站请求伪造,是一种挟持用户在当前已登录的web应用程序上执行非本意的操作的攻击方法。
防御措施:

  • 验证token
  • 使用referer字段
  • 写操作用post
  • 禁用跨域请求

    webpack

    webpack是前端自动化构建工具,主要由入口、出口、loaders、plugins组成

    构建优化

  • 减少编译体积 ContextReplacementPugin、IgnorePlugin、babel-plugin-import、babel-plugin-transform-runtime。

  • 并行编译 happypack、thread-loader、uglifyjsWebpackPlugin开启并行
  • 缓存 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin开启缓存、babel-loader开启缓存
  • 预编译 dllWebpackPlugin && DllReferencePlugin、auto-dll-webapck-plugin

    性能优化

  • 1、减少编译体积 Tree-shaking、Scope Hositing。

  • 2、hash缓存 webpack-md5-plugin
  • 3、拆包 splitChunksPlugin、import()、require.ensure