今天也有很努力的在赚猫粮钱
如若文章存在错误或不足,望理解并帮我指出,感激不尽
数据类型,存储方式
其中原始类型又分为七种类型,分别为:
boolean
number
string
undefined
null
symbol
bigint
对象类型分为两种,分别为:
Object
Function
原始类型存储在栈上,对象类型存储在堆上,但是它的引用地址还是存在栈上
更全面的:
基本类型:
Number
、Boolean
、String
、null
、undefined
、symbol
(ES6 新增的),BigInt
(ES2020)
引用类型:Object
对象子类型:Array
、Function
NaN
NaN表示“不是数字”,但是它的类型是Number,NaN和任何内容比较,甚至是自己,结果都是false.
typeof NaN === 'number'
symbol
-
可以用来表示一个独一无二的变量防止命名冲突
-
利用 symbol 不会被常规的方法(除了 Object.getOwnPropertySymbols 外)遍历到,所以可以用来模拟私有变量
-
主要用来提供遍历接口,布置了 symbol.iterator 的对象才可以使用 for···of 循环,可以统一处理数据结构。调用之后回返回一个遍历器对象,包含有一个 next 方法,使用 next 方法后有两个返回值 value 和 done 分别表示函数当前执行位置的值和是否遍历完毕。
假值
-
undefine
-
null
-
false
-
+0, -0, NaN
-
""
0.1 + 0.2 = ?
在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会发生无限循环,然后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度丢失。
var let const
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
var:
var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。变量可以重复定义。(现在在开发中已经很少会使用)。
let:
变量需要声明后才可以使用,声明的变量存在块级作用域。
let不允许在相同作用域内,重复声明同一个变量。
const:
const 声明之后必须马上赋值,否则会报错。
const 简单类型一旦声明就不能再更改,复杂类型(数组、对象等)指针指向的地址不能更改,内部数据可以更改。
let、const使用场景:
let使用场景:变量,用以替代var。
const使用场景:常量、声明匿名函数、箭头函数的时候。
this
this指的是调用函数的那个对象,一般情况下,this是全局对象Global,可以作为方法调用。this随着函数的使用场合的不同,this的值会发生变化。
this是谁调用就指向谁,在全局环境里,指向的是window对象。
作用域
-
全局作用域
-
函数作用域
-
块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。
第二种场景,用来计数的循环变量泄露为全局变量。
注意ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
作用域链
在javascript的学习中,执行环境、作用域是2个非常非常重要和基本的概念。
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象 (variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器会处理数据时会在后台使用它。
在代码在一个环境执行时,会创建变量对象的一个 作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。整个作用域链是由不同执行位置上的变量对象(Variable Object)按照规则所构建一个链表。
作用域链的前端,始终都是当前执行的代码所在环境的变量对象。下一个变量对象来自包含(外部)环境,而下一个变量对象则来自下一个包含对象。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链的最后一个对象。
原型 原型链
原型
每个实例对象都有一个 constructor
属性,指向它的构造函数。
每个函数对象(包括构造函数) 都有一个 prototype
属性,指向函数的原型对象。这个原型对象的 constructor
属性,指向函数本身。
每个对象都有一个 [[prototype]]
私有属性,指向它的构造函数的原型对象,但这个属性是不允许访问的。某些浏览器(例如 Chrome)提供 __proto__
属性用于访问 [[prototype]]
私有属性
构造函数的 constructor
属性都是指向 Function
,__proto__
属性都是指向 Function.prototype
。因为构造函数都是通过 new Function
来创建的,它们都是 Function
的实例对象,包括 Function
和 Object
除 Object
外,其它构造函数的 prototype
属性的 __proto__
属性都是指向 Object.prototype
。 而 Object
的 prototype
属性的 __proto__
属性指向 null
原型链
所有对象都有 __proto__
属性,并且这个 __proto__
属性指向一个原型对象。
因为原型对象也是对象,这个原型对象也有 __proto__
属性,我们把这种关系称为原型链
当需要访问一个对象的属性时,首先从该对象开始查找,如果能够找到,那么到此返回。
如果没有找到,就在该对象的 __proto__
属性指向的原型对象中继续查找,如果能够找到,那么到此返回。
如果没有找到,那么一直往上查找原型对象,直至 __proto__
属性指向 null,也就是原型链的顶端
若原型链上的所有原型对象都没有该属性,则返回 undefined。
OK,上面的话或许有点夸张,总结来说,原型链是以一个对象为基准,以 _proto_
为连接为链条一直到Object.prototype为止。
解决跨域问题 & 同源
明白跨域问题前,先了解一下同源策略,什么是同源,同源就是协议,域名,端口全都需要相同,有任何一项不同就会引起跨域问题:
-
Cookie、LocalStorage和IndexDB无法读取
-
DOM无法获取
-
AJAX请求不能发送
前端解决跨域的方式有很多,我常用如下1、2条:
-
nginx反向代理
反向代理的优点: 1. 保护网络安全,所有请求都先经过代理服务器。 2. 负载均衡,把请求转发到压力较小的服务器。(内置策略,扩展策略可以了解一下) 3. 可以做一些中间层设置,比如缓存静态资源。
-
config 中配置 proxyTable 中 changeOrigin : true
-
jsonp (只支持GET请求)
-
CORS
-
postMessage
-
node
-
webSocket
-
iframe
call apply bind
call()、apply()、bind()是Function.prototype上的方法,所有函数都可以调用,它们的第一个参数都是this的指向对象。
call方法可以将调用函数执行,并将this 指向第一个参数,后续其他参数将会全部传递给调用函数使用。
all()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
在非严格模式下,如果call()和apply()的第一个参数是null或者undefined,那么this的指向就是window全局变量。
bind返回的是方法。
总结:
call()、apply()和bind()都是可以用来改变this指向,可借助它们实现继承;
call()与apply()区别在于参数不一样,如果想一目了然表达形参和实参的对应关系,用call()比较合适;
bind()是返回一个新函数,可供以后调用,而apply()和call()是立即调用,并返回结果
箭头函数的特点
-
用 => 代替 function,语法简单
-
不绑定 this,箭头函数体内的 this 永远指向的是定义时所在的对象。 (建有函数中不会创建自己的this,而是从自己的作用域的上一层继承)
-
不支持call apply bind
-
不绑定arguments (普通函数通过arguments获取传入的参数值)
-
支持嵌套
-
不能使用箭头函数作为构造函数,没有prototype属性
JS常用内置对象和方法
Array数组对象、Math方法、String方法、Date日期对象方法
Array 数组对象
push()
在数组尾部添加一个或者多个元素,并且返回数组的新长度。
unshift()
在数组头部添加一个或者多个元素,并且返回数组的新长度
pop()
删除数组尾部的最后一个元素,并且将这个被删除的元素返回
shift()
删除数组的第一个元素,并且返回被删除的元素
concat() (可以但是没必要,可以了解一下解构赋值)
数组的合并,合并后会返回一个新数组,原来的两个数组不会变化
join()
将数组的每个元素以指定的字符连接形成新字符串返回
splice()
这个方法可以从指定的位置删除给定数量的元素,并且在这个位置插入需要的元素,并且返回被删除的元素组成的新数组
slice(start,end)
按指定位置截取复制数组的内容,返回新数组,不会改变原数组
indexOf(要查询得元素,从什么位置开始查询)
(要查询得元素,从什么位置开始查询) 位置就是下标
Array.from(类似于数组的列表)
转为数组
lastIndexOf(查找得元素,从什么位置开始查找)
从后向前查找
arr.sort()
排序,仅能10以内数字
forEach() map()
遍历数组
some()
判断数组中是否存在满足条件的元素,如果有就返回true,如果没有就返回false
every()
判断数组中是否每一个都满足条件,如果有一个不满足条件,直接跳出,所有都满足时返回为ture
filter()
过滤数组,返回符合要求的新数组
reduce()
从数组的第1位开始遍历,第0位没有遍历,下标从1开始
Array.isArray()
判断数组
Math方法
Math.floor()
向下取整
Math.ceil()
向上取整
Math.round()
四舍五入
Math.random()
随机数
Math.abs()
取绝对值
String 字符串方法
charAt()
获取下标位置的字符
charCodeAt()
将下标位置的字符转为Unicode编码
String.fromCharCode()
将Unicode编码转为字符串
str.concat()
连接字符串,效果等同于拼接符+
indexOf() 、lastIndexOf()
和数组中indexOf相同查找字符所在下标
replace()
替换,类似于数组中的splice(); replace不修改原字符的内容,会返回一个被修改后的新字符串 如果出现两个相同的元素,那么只修改第一次被查找到的元素
slice(start,end)
slice (从下标几开始,到下标几之前结束) 截取复制字符串 允许有负值,负值表示从后向前
substr (从下标几开始,截取长度);
split(分隔符)
将字符串以分隔符进行分割转换为数组
reverse();
数组元素倒序或者反转,这是一个数组方法
日期对象
javascript事件循环 / event Loop / JS执行机制 ; 宏任务、微任务
讲解事件循环前先提一下单线程和多线程
• 单线程:单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。
• 多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
然后了解一下宏任务微任务。在JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。
宏队列,macrotask。一些异步任务的回调会依次进入macro task queue,等待后续被调用:
-
setTimeout和setInterval
-
requestAnimationFrame
-
I/O
-
UI rendering
微队列,microtask。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用:
-
Promise.then
-
Object.observe
-
MutationObserver
执行顺序:
-
执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等),遇到异步代码根据上述任务划分到对应队列中;
-
全局Script代码执行完毕后,调用栈Stack会清空;
-
从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;
-
继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。(即清空微任务队列,注意:如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行)
-
microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
-
取出宏队列macrotask queue中位于队首的任务,放入Stack中执行;
-
执行完毕后,调用栈Stack为空;
-
重复第3-7个步骤;
-
...
简单来说,执行主线程同步代码遇到异步任务挂起划分到对应任务队列,主线程同步代码执行完毕后,清空微任务队列,此时微任务清空,执行栈清空,开始执行宏任务,执行完一个宏任务后查看微任务队列并执行清空,之后继续执行宏任务依次循环。
JavaScript是一个单线程的脚本语言,同一时间不能处理多个任务,于是就有了异步事件的概念 Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
浏览器组成原理
参考这篇文章 浏览器原理
HTTP HTTPS HTTP2
HTTP/1.1 缺点
-
对头阻塞(Head-of-line blocking)
HTTP/1.1协议虽然可以在同一个TCP连接上发送多个请求,但是这多个请求是有顺序的,必须处理完第一个请求才会响应下一个请求。如果第一个请求处理的特别慢,后面的所有请求就需要排队。
-
TCP 连接数限制
对于同一个域名,浏览器最多只能同时创建 6 ~ 8 个TCP连接。如果一个页面有十个请求同时发送,那么只能等第一次的 6 ~ 8 个请求都返回了才能继续接下来的 2 ~ 4 个请求。域名分片技术应运而生。就是把资源分配到不同的域名下(可以是二级子域名),这样就解决了限制,但是滥用域名分片技术也不行,因为每个TCP连接也是很费时的。
-
Header 内容繁多,有时有可能会超过响应内容,并且每次有许多字段都是重复传输。
-
HTTP/1.1是文本协议传输,不够安全。
http2相比于http/1.1的新特性包括:
-
多路复用 (MultiPlexing),单一长连接,二进制格式传输,请求优先级设置
-
头部header压缩
-
服务端推送Server Push
HTTPS
- 加密的HTTP
ajax axios
ajax :
-
异步的JavaScript 和 XML
-
核心使用XMLHttpRequest对象
-
多个请求如果出现先后顺序就会出现回调地狱
axios :
-
不是原生的
-
不仅可以在客户端使用,也可以在nodejs端使用
-
axios 也可以在请求和响应阶段进行拦截
-
基于 Promise 对象
javascript回收机制
垃圾回收有两种实现方式,分别是标记清除和引用计数
标记清除:当变量进入执行环境时标记为“进入环境”,当变量离开执行环境时则标记为“离开环境”,被标记为“进入环境”的变量是不能被回收的,因为它们正在被使用,而标记为“离开环境”的变量则可以被回收
引用计数:统计引用类型变量声明后被引用的次数,当次数为 0 时,该变量将被回收。
详细:戳
内存泄漏
内存泄露就是不再被需要的内存, 由于某种原因, 无法被释放。
造成内存泄漏的方式:
-
全局变量造成内存泄露
-
未销毁的定时器和回调函数造成内存泄露
-
闭包造成内存泄露
-
DOM引用造成内存泄露
判断数组的方法
-
object.prototype.toString.call()
无法判断自定义对象类型,可以采用 instanceof 区分
-
instanceof
通过判断对象的原型链中是不是能找到类型的prototype
-
Array.isArray()
数组去重
-
使用双重for循环去遍历数组,时间复杂度为O(n^2)
-
在ES6中我们可以使用
Set
数据结构来去重 -
使用filter,去重的本质就是按照数据唯一的特点筛选数组,filter刚好可以筛选,所以与去重的行为是一致的
-
使用reduce去重
-
使用filter
es6新特性
-
变量声明方式 const let 作用域块
-
箭头函数
-
模版字符串
-
类 class
-
对象和数组解构 (特别好用)
-
Spread / Rest 操作符
继承
原型链继承
构造函数、原型和实例之间的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。
继承的本质就是复制,即重写原型对象,代之以一个新类型的实例。
借用构造函数继承
使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)
缺点:
-
只能继承父类的实例属性和方法,不能继承原型属性/方法
-
无法实现复用,每个子类都有父类实例函数的副本,影响性能
组合继承
组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。
类型转换
将值从一种类型转换为另一种类型通常称为类型转换,这是显示的情况;隐式的情况称为强制类型转换.
JS中的强制类型转换总是返回标量基本类型值,如: 字符串、数字、布尔值,不会返回对象和函数。
强制类型转换又分为: 隐式强制类型转换 和 显示强制类型转换;
ToString
它负责处理非字符串到字符串的强制类型转换:
-
null ---> "null"
-
undefined ---> "undefined"
-
true ---> "true"
对于普通对象来说,除非自行定义,否则toString()(Object.prototype.toString())返回内部属性[[Class]]的值,如"[object Object]"; 但是当对象有自己的toString()方法, 字符串化时就会调用该方法并使用返回值。
JSON字符串化
-
工具函数JSON.stringify()在将JSON对象序列化为字符串时也用到了ToString
-
对于大多数简单值来说,JSON字符串化和toString()的效果基本相同
-
所有安全的JSON值都可以被JSON.stringify()字符串化。
-
不安全的JSON值有: undefined、function、symbol和包含循环引用的对象,其中 undefined、function、symbol在被stringify()时会被忽略,在数组中则会返回null。
-
如果要对含有非法 JSON 值的对象做字符串化,或者对象中的某些值无法被序列化时,就 需要定义 toJSON() 方法来返回一个安全的 JSON 值。
-
可以向JSON.stringify()传递一个可选参数replacer, 它可以是数组或者函数,用来指定对象序列化过程中哪些属性被处理;另一个可选参数space, 用来指定输出的缩进格式,为正整数时是指定 每一级缩进的字符数.
ToNumber
有时需要将非数字值当作数字来使用,比如数学运算;此时就用到了ToNumber;
其中 true 转换为 1, false 转换为 0,undefined 为 NaN, null 转换为 0
对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型 值,则再遵循以上规则将其强制转换为数字。
转换过程中会先检查该值是否有 valueOf() 方法。 如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。如果两者均不返回基本类型值,会产生TypeError错误
ToBoolean
JavaScript中的值可以分为以下两类:
-
可以被强制转换为false的值
-
其他
以下这些是假值:
-
undefined
-
null
-
false
-
+0、 -0、 NaN
-
""
理论上说除了假值列表以外的都是真值。
setInterval 和 setTimeout
在官方文档中的定义,setImmediate为一次Event Loop执行完毕后调用。
setTimeout则是通过计算一个延迟时间后进行执行。
但是同时还提到了如果在主进程中直接执行这两个操作,很难保证哪个会先触发。
因为如果主进程中先注册了两个任务,然后执行的代码耗时超过多少秒,而这时定时器已经处于可执行回调的状态了。
所以会先执行定时器,而执行完定时器以后才是结束了一次Event Loop,这时才会执行setImmediate。
forEach map filter
-
filter函数:主要是过滤作用,筛选符合条件的所有元素,为true就返回一个新的数组
-
map函数: 通过函数处理原数组中的每一个元素,并返回一个处理后新的数组。
-
forEach函数: 按升序为数组中含有效值的每一项执行一次 callback 函数,那些已删除或者未初始化的项将被跳 过
-
forEach 和 map 能实现的功能相似
-
forEach 、 map、filter 都能实现对原数组的修改
-
forEach 没有返回值,map 有返回值,filter 有返回值
闭包
- 《JavaScript高级程序设计》这样描述:
闭包是指有权访问另一个函数作用域中的变量的函数;
- 《JavaScript权威指南》这样描述:
从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。
- 《你不知道的JavaScript》这样描述:
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
闭包的应用:
-
实现公有变量
-
可以做缓存(存储结构)
-
可以实现封装,属性私有化
-
模块化开发,防止污染全局变量
闭包的缺陷:
闭包引用的变量在闭包存在时不会被回收 => 内存泄漏
捕获过程 事件冒泡
事件冒泡
事件流的执行顺序,捕获阶段 => 目标阶段 => 冒泡阶段。冒泡从里到外的执行。
<div><span>点我</span></div>
,在div上定义的事件,点击span的时候会触发span上面绑定的事件,之后也会触发外面div上面的事件,这就是冒泡。
冒泡阶段是从目标到window对象的过程。事件默认是冒泡的,当父元素添加监听事件,点击子元素后,父元素上的事件会被触发,这就是典型的冒泡。
捕获阶段
与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
webpack 打包原理
-
读取 webpack 的配置参数
-
启动 webpack ,创建 compiler 对象并开始解析项目
-
从入口文件(entry)开始解析,倒入依赖模块。递归遍历,形成依赖树
-
对不同文件类型对依赖模块文件使用对应的 Loader 进行编译,转成 JS 文件。
-
整个过程,webpack 会发布订阅模式,向外抛出 hooks,而 webpack 插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。