数据类型
数据类型
基本类型 Boolean,object, number, null, undefined, string, symbol (es6)
Symbol值通过Symbol( )生成,只要属性名是属于Symbol类型的,就是独一无二的,可以保证不会与其他属性名产生冲突。
引用类型: 数组,函数,对象
判断变量的类型
typeof 返回的是字符串,描述数据类型。需要注意的是,对于null,object,function, array都返回object,并不会明确告诉是哪一种对象
instanceof及原理 ,判断摸个对象是否是另一个对象的实例,返回布尔值
Object.prototype.toString.call() 判断对象属于哪种内置类型
深拷贝 浅拷贝
浅拷贝拷贝的是内存地址,两者指向同一块内存空间,改变其中一个对象的值,另一个对象的值也会随之变化。Object.assign() 方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。它将返回目标对象
深拷贝是将一个对象从内存中完整的拷贝出来,并开辟一个新的内存空间储存,修改器其中一个对象的值不会影响别的对象。JSON.parse(JSON.stringify()),对带函数的对象只能用递归的方法实现
-
手写深拷贝
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="js" cid="n26" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-color: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}</pre>
数据类型转换
相等==和全等===
强制转换和隐式转换
作用域
变量提升
- js引擎会先对文件进行预编译,其中一部分工作边便是将函数和变量的声明提升到其对应作用域的顶部。全局变量提升到全局作用域顶部,函数内声明的变量提升到函数作用域的顶部。其中变量会被先定义为undifined。es6中引用的块作用域的概念。 let 和const不允许有这样的操作,let 声明以后不允许多次声明同一名称的变量,这样避免了引起混乱。let只在let所在的代码域内有效,const声明一个只读的常量,一旦声明常量的值无法改变
闭包
闭包的形成:函数里面再定义一个函数,内部函数可以访问外部函数作用域的变量。如果外部函数不暴露这个内部函数的话,外界就不知道这个内部函数。这个内部函数和函数所能访问的变量的总和被称为闭包。
闭包的作用:闭包常常用来隐藏变量,给内部函数的变量设置里只读属性,外部只能返回他的值,而不能修改他的值,从而起到了保护作用。但是闭包会造成某一块内存长期被占用,消耗内存
作用域链
全局作用域 :
- 浏览器打开一个页面时,会给js代码提供一个全局的运行环境,这个环境就是全局作用域。一个页面只有一个全局作用域,这个全局作用域下有window对象,全局作用域下的变量和函数都会储存在window下。
函数作用域:
- 函数作用域指声明在函数内部的变量,函数作用域包含在全局作用域下,全局作用域下可以有多个函数作用域
块级作用域 var/let/const
- 块级作用域可通过let和const声明,所声明的变量在指定块的作用域外无法被访问,在一个函数内部或一个代码块内部被创建。const和let的作用域是一致的,不同的是const声明一个只读的常量,常量一旦声明不允许改变 let的特点:1. 变量提升不会提升到代码块的顶部,所以let一定要先声明再使用,需要手动放置到作用域顶部。2. var可以在同一个作用域内重复声明,let禁止在相同的作用域内重复声明。
作用域链是什么
- 自由变量指当前作用域中没有定义的变量。 作用域链指由子作用域层层向父作用域中找自用变量的关系。
作用域和执行上下文的区别
- javascript属于解释性语言,js的执行分为两个阶段:解释阶段和执行阶段。作用域在解释阶段,在函数定义时已经确定,但是执行上下文在函数执行之前才被创建。 执行上下文可能随时会变,但时作用域在定义时就被确定,不会变。同一个作用域下,不同的调用会产生不同的执行上下文。
原型
原型和原型链
举例:描述构造函数、实例和原型之间的关系
原型指将所有对象的公共属性储存到一个对象中,然后让每一个对象的protp记录这个对象的地址,而这个公共属性就是原型。
protp和constructor是对象的属性,prototype是函数独有的属性。 proto 是一个对象指向另一个对象,指向它的原型对象。prototype是给其他对象提供共享属性的对象,所有的对象都可以作用另一个对象的prototype来用。函数创建的对象.protp === 函数.protptype。 constructor指向该对象的构造函数。函数.protptype.constructor === 该函数本身
原型减少了不必要的内存消耗。
原型链指通过proto不断向上(父类对象)查找原型对象的方式,要是找到Object的父类对象null, 还是没找到想到的属性或者方法则会返回undefined
js创建对象的几种方式(常见)
工厂模式
构造函数模式
原型模式
组合模式
寄生构造模式
继承
js如何实现继承
js如何实现一个类
js实现继承的多种方式
call/apply/bind
call、apply和bind都用来改变this的指向
call和apply都是对函数直接的调用,bind()返回一个函数,需要对函数进行调用
call和bind可以一个个传入,apply要以数组的格式传参
new, this
new操作符
new的模拟实现
this对象的理解
this指向
this永远指向运行时最后调用它的那个对象
在全局变量中this等价于window对象,var声明变量赋值给window,未声明的变量直接复制给window
在构造函数中this指向new的对象
改变this的指向:箭头函数,call,bind,apply, new实例化一个对象
事件
DOM
- Document Object Model 文档对象模型
DOM事件
- 事件是用户或者浏览器执行某种动作,是文档或者浏览器发生交互的一瞬间,不如点击一下鼠标(click)。html和js之间的交互是通过事件实现的。
DOM事件流
- DOM是一个树型结构,当html产生事件时,该事件就会在dom树中的根节点和元素节点之间传播,这种事件的传递被称为事件流。
DOM diff
用一个对象来描述DOM树即为虚拟DOM。
比对(diff算法)虚拟DOM和真实DOM的差异,产生差异补丁对象,再将差异补丁对象应用到真实的dom节点上。因为操作dom的代价是昂贵的,虚拟dom将多次更新的diff内容保存到一个对象中,这样可以尽可能地减少dom操作。
事件的三个阶段
捕获阶段、目标阶段、冒泡阶段
在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果, 对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件。
Event loop
进程与线程
- 线程是一个程序执行的最小单位,一个进程可由一个或多个线程组成。不同的进程互相独立,但是不同的线程之间共享内存空间。
执行栈
- 任务进入执行栈后分为同步任务和异步任务。同步任务进入主线程开始执行。异步任务进入Event Table并注册函数,然后Event Table会将这个函数移入Event Queue。当主线程任务执行完毕,会去Event Queue调取对应的函数并放入主线程执行。上述过程不断重复就是事件循环。
为什么js是单线程不是多线程
- 因为作为浏览器的脚本语言,js主要功能是与用户交互和操作DOM,多线程会带来很多问题,比如在其中一个线程修改了DOM树,另一个线程不变,浏览器不知道以哪个线程为准。
微任务/宏任务
宏任务 js代码、setTimeout、setInterval、setImmediate
微任务 Promise
事件循环的顺序:进入宏任务后开始第一次循环,接着执行所有的微任务。然后再次从宏任务开始。
浏览器与node.js的事件循环
-
process.nextTick(callback)
类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。
Promise
Promise的基本用法
他是ES6中新增加的一个类, 目的是为了管理JS中的异步编程的。
Promise 有三个状态:pending(准备状态:初始化成功、开始执行异步的任务)、fullfilled(成功状态)、rejected(失败状态)。pending是初始状态,可以转化为fullfilled和rejected,转化后状态不可改变。
Promise函数天生有两个参数,resolve(当异步操作执行成功,执行resolve方法),rejected(当异步操作失败,执行reject方法) then()方法中有两个函数onFulfilled和onRejected,第一个传递的函数是resolve,第二个传递的函数是reject。
-
https://juejin.cn/post/6844903625769091079#heading-9
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="js" cid="n185" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-color: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">class Promise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
catch(fn){
return this.then(null,fn);
}
}
function resolvePromise(promise2, x, resolve, reject){
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if(called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if(called)return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if(called)return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
//resolve方法
Promise.resolve = function(val){
return new Promise((resolve,reject)=>{
resolve(val)
});
}
//reject方法
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}</pre><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="js" cid="n187" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-color: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">//race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
</pre><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="js" cid="n190" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-color: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}</pre><pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="js" cid="n208" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-color: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; background-position: initial initial; background-repeat: initial initial;">function getData(url) {
var xhr = new XMLHttpRequest(); // 创建一个对象,创建一个异步调用的对象
xhr.open('get', url, true) // 设置一个http请求,设置请求的方式,url以及验证身份
xhr.send() //发送一个http请求
xhr.onreadystatechange = function () { //设置一个http请求状态的函数
if (xhr.readyState == 4 && xhr.status ==200) {
console.log(xhr.responseText) // 获取异步调用返回的数据
}
}
}
Promise(getData(url)).resolve(data => data)
AJAX状态码:0 - (未初始化)还没有调用send()方法
1 - (载入)已调用send方法,正在发送请求
2 - (载入完成呢)send()方法执行完成
3 - (交互)正在解析相应内容
4 - (完成)响应内容解析完成,可以在客户端调用了
</pre>减少http请求数,合理设置http缓存
合并css图片,压缩资源
多图片的页面使用懒加载
减少闭包的使用
减少对dom的操作
使用svg图片而不是png图片
前端有哪些页面优化的方法
Set本身结构类似于数组,但成员的值唯一。Set利用set构造函数生成set数据结构,常用来数组去重。add 添加元素,delete 删除元素,has 判断元素,clear 清除所有元素
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6 提供了 Map 数据结构,使各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。keys()返回所有键名的遍历器,values()返回所有键值得遍历器,entries()返回所有成员的遍历器,forEach()返回map中所有成员
Map与Set
es6 module
Promise
它的语法标志是 ...
以数组为例,扩展运算符可以拆开这个数组,变成一个元素集合。 扩展剩余运算符和扩展运算符的区别就是,剩余运算符会收集这些集合,放到数组中。
剩余/扩展运算符
箭头函数
let/const
es6
写一个简单的ajax请求
缺点 1.无法使用回退按钮 2.不利于网页的SEO 3.不能发送跨域请求
ajax的优势 1.无刷新页面请求,使产品更快,更小更友好 2.服务器端的任务转嫁到客户端处理 3.减轻浏览器负担,节约带宽 4.基于标准化对象,不需要安装特定的插件 5.彻底将页面与数据分离
创建一个ajax对象 ,使用XMLHttpRequest这个函数创建一个实例对象。
告诉ajax对象要发送的请求地址以及请求方式 ,调用xhr下面的open方法
发送请求,使用send方法。
获取服务器响将要响应到客户端的数据,使用xhr下的resopnsText属性就是服务器响应给客户端的数据。
AJAX实现
AJAX
async await
Promise.all
promise.race