基础知识https://zhuanlan.zhihu.com/p/28415923
在 JS 中共有 8
种基础的数据类型,分别为: Undefined
、 Null
、 Boolean
、 Number
、 String
、 Object
、 Symbol
、 BigInt
。
区别
- 值类型是直接存储在**栈(stack)**中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用类型存储在**堆(heap)**中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;
数据类型的判断
object
。 console.log(typeof undefined); // undefined
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof Symbol("foo")); // symbol
console.log(typeof 2172141653n); // bigint
console.log(typeof function () {}); // function
// 不能判别
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
console.log(typeof undefined); // undefined
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof Symbol("foo")); // symbol
console.log(typeof 2172141653n); // bigint
console.log(typeof function () {}); // function
// 不能判别
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
Object.prototype.toString.call(2); // "[object Number]"
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(function () {}); // "[object Function]"
如何判断变量是否为数组
Array.isArray(arr); // true
arr.__proto__ === Array.prototype; // true
arr instanceof Array; // true
Object.prototype.toString.call(arr); // "[object Array]"
- indexOf 循环去重
- ES6 Set 去重;Array.from(new Set(array))
- Object 键值对去重;把数组的值存成 Object 的 key 值,比如 Object[value1] = true, 在判断另一个值的时候,如果 Object[value2]存在的话,就说明该值是重复的。
什么是闭包
闭包是指有权访问另外一个函数作用域中的变量的函数。 闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。闭包就是 就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈 上分配而是在堆上分配。当在一个函数内定义另外一个函数就会产生闭包。
为什么要用闭包
匿名自执行函数:我们知道所有的变量,如果不加上 var 关键字,则默认的会添加到全 局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误 用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链 上遍历的)。除了每次使用变量都是用 var 关键字外,我们在实际情况下经常遇到这样一 种情况,即有的函数只需要执行一次,其内部变量无需维护,可以用闭包。 结果缓存:我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象, 每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函 数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如 果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部 的引用,从而函数内部的值可以得以保留。 封装:实现类和继承等。
应用
函数作为参数被传递
function print(fn) {
const a = 200;
fn();
}
const a = 100;
function fn() {
console.log(a);
}
print(fn); // 100
函数作为返回值被返回:
function create() {
const a = 100;
return function () {
console.log(a);
};
}
const fn = create();
const a = 200;
fn(); // 100
数组的浅拷贝: 如果是数组,我们可以利用数组的一些方法,比如 slice,concat 方法返回一个新数组的 特性来实现拷贝,但假如数组嵌套了对象或者数组的话,使用 concat 方法克隆并不完整, 如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或数组,就会只拷 贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化,我们 把这种复制引用的拷贝方法称为浅拷贝, 深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也互相分离,修改一个对象 的属性,不会影响另一个 如何深拷贝一个数组 1、这里介绍一个技巧,不仅适用于数组还适用于对象!那就是:
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse( JSON.stringify(arr) );
console.log(new_arr);
原理是 JOSN 对象中的 stringify 可以把一个 js 对象序列化为一个 JSON 字符串,parse 可 以把 JSON 字符串反序列化为一个 js 对象,通过这两个方法,也可以实现对象的深复制。 但是这个方法不能够拷贝函数 浅拷贝的实现: 以上三个方法 concat,slice ,JSON.stringify 都是技巧类,根据实际项目情况选择使用,我 们可以思考下如何实现一个对象或数组的浅拷贝,遍历对象,然后把属性和属性值都放 在一个新的对象里即可
var shallowCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
100
// 根据 obj 的类型判断是新建一个数组还是对象
var newObj = obj instanceof Array ? [] : {};
// 遍历 obj,并且判断是 obj 的属性才拷贝
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
深拷贝的实现 那如何实现一个深拷贝呢?说起来也好简单,我们在拷贝的时候判断一下属性值的类型, 如果是对象,我们递归调用深拷贝函数就可以了
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
手写深拷贝
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
* @param {Map} map 用于存储循环引用对象的地址
*/
function deepClone(obj = {}, map = new Map()) {
if (typeof obj !== "object") {
return obj;
}
if (map.get(obj)) {
return map.get(obj);
}
let result = {};
// 初始化返回结果
if (
obj instanceof Array ||
// 加 || 的原因是为了防止 Array 的 prototype 被重写,Array.isArray 也是如此
Object.prototype.toString(obj) === "[object Array]"
) {
result = [];
}
// 防止循环引用
map.set(obj, result);
for (const key in obj) {
// 保证 key 不是原型属性
if (obj.hasOwnProperty(key)) {
// 递归调用
result[key] = deepClone(obj[key], map);
}
}
// 返回结果
return result;
}
prototype
对象。深入了解可以阅读:JavaScript 深入之从原型到原型链和轻松理解 JS 原型原型链
可以阅读JavaScript 深入之词法作用域和动态作用域和深入理解 JavaScript 作用域和作用域链。
当 JavaScript 代码执行一段可执行代码时,会创建对应的执行上下文。对于每个执行上下文,都有三个重要属性:
推荐阅读:
- JavaScript 深入之执行上下文栈;
- JavaScript 深入之变量对象;
- JavaScript 深入之作用域链;
- JavaScript 深入之执行上下文。
- JavaScript 的 this 原理
- JavaScript 深入之闭包
call
call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
举个例子:
var obj = {
value: "vortesnail",
};
function fn() {
console.log(this.value);
}
fn.call(obj); // vortesnail
通过 call
方法我们做到了以下两点:
call
改变了 this 的指向,指向到 obj
。fn
函数执行了。那么如果我们自己写 call
方法的话,可以怎么做呢?我们先考虑改造 obj
。
var obj = {
value: "vortesnail",
};
function fn() {
console.log(this.value);
}
fn.call(obj); // vortesnail
这时候 this 就指向了 obj
,但是这样做我们手动给 obj
增加了一个 fn
属性,这显然是不行的,不用担心,我们执行完再使用对象属性的删除方法(delete)不就行了?
obj.fn = fn;
obj.fn();
delete obj.fn;
根据这个思路,我们就可以写出来了:
Function.prototype.myCall = function (context) {
// 判断调用对象
if (typeof this !== "function") {
throw new Error("Type error");
}
// 首先获取参数
let args = [...arguments].slice(1);
let result = null;
// 判断 context 是否传入,如果没有传就设置为 window
context = context || window;
// 将被调用的方法设置为 context 的属性
// this 即为我们要调用的方法
context.fn = this;
// 执行要被调用的方法
result = context.fn(...args);
// 删除手动增加的属性方法
delete context.fn;
// 将执行结果返回
return result;
};
apply
我们会了 call
的实现之后,apply
就变得很简单了,他们没有任何区别,除了传参方式。
Function.prototype.myApply = function (context) {
if (typeof this !== "function") {
throw new Error("Type error");
}
let result = null;
context = context || window;
// 与上面代码相比,我们使用 Symbol 来保证属性唯一
// 也就是保证不会重写用户自己原来定义在 context 中的同名属性
const fnSymbol = Symbol();
context[fnSymbol] = this;
// 执行要被调用的方法
if (arguments[1]) {
result = context[fnSymbol](...arguments[1]);
} else {
result = context[fnSymbol]();
}
delete context[fnSymbol];
return result;
};
bind
bind
返回的是一个函数,这个地方可以详细阅读这篇文章,讲的非常清楚:解析 bind 原理,并手写 bind 实现。
Function.prototype.myBind = function (context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new Error("Type error");
}
// 获取参数
const args = [...arguments].slice(1),
const fn = this;
return function Fn() {
return fn.apply(
this instanceof Fn ? this : context,
// 当前的这个 arguments 是指 Fn 的参数
args.concat(...arguments)
);
};
};
- 首先创一个新的空对象。
- 根据原型链,设置空对象的
__proto__
为构造函数的prototype
。- 构造函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)。
- 判断函数的返回值类型,如果是引用类型,就返回这个引用类型的对象。
指向问题
【1】构造函数通常不使用 return 关键字,它们通常初始化新对象,当构造函数的函数 体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这 个新对象的值。
【2】如果构造函数使用 return 语句但没有指定返回值,或者返回一个原始值,那么这 时将忽略返回值,同时使用这个新对象作为调用结果。
【3】如果构造函数显式地使用 return 语句返回一个对象,那么调用表达式的值就是这 个对象。
实现一个promise.all
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
// 参数可以不是数组,但必须具有 Iterator 接口
if (typeof promises[Symbol.iterator] !== "function") {
reject("Type error");
}
if (promises.length === 0) {
resolve([]);
} else {
const res = [];
let count = 0;
const len = promises.length;
for (let i = 0; i < len; i++) {
//考虑到 promises[i] 可能是 thenable 对象也可能是普通值
Promise.resolve(promises[i])
.then((data) => {
res[i] = data;
if (++count === len) {
resolve(res);
}
})
.catch((err) => {
reject(err);
});
}
}
});
};
- async/await 是消灭异步回调的终极武器。
- 但和 Promise 并不互斥,反而,两者相辅相成。
- 执行 async 函数,返回的一定是 Promise 对象。
- await 相当于 Promise 的 then。
- tru...catch 可捕获异常,代替了 Promise 的 catch。
for(let i=0;i<5;i++){
setTimeout(function(){
console.log(i)
},1000*i)
}
原型链继承
构造继承
实例继承
拷贝继承
组合继承
寄生组合继承
总结
原型链继承 | 将父类的实例作为子类的原型,他的特点是实例是子类的实例也是父 类的实例,父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实 现,缺点是来自原型对象的所有属性被所有实例共享,无法实现多继承,无法向父类构 造函数传参。 |
构造继承 | 使用父类的构造函数来增强子类实例,即复制父类的实例属性给子类, 构造继承可以向父类传递参数,可以实现多继承,通过 call 多个父类对象。但是构造继 承只能继承父类的实例属性和方法,不能继承原型属性和方法,无法实现函数服用,每 个子类都有父类实例函数的副本,影响性能 |
实例继承 | 为父类实例添加新特性,作为子类实例返回,实例继承的特点是不限制 调用方法,不管是 new 子类()还是子类()返回的对象具有相同的效果,缺点是实 例是父类的实例,不是子类的实例,不支持多继承 |
拷贝继承 | 特点:支持多继承,缺点:效率较低,内存占用高(因为要拷贝父类的 属性)无法获取父类不可枚举的方法(不可枚举方法,不能使用 for in 访问到) 5、组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父 类实例作为子类原型,实现函数复用 |
寄生组合继承 | 通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构 造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点 |
JSDOM 事件流存在如下三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
JSDOM 标准事件流的触发的先后顺序为:先捕获再冒泡,点击 DOM 节点时,事件传播 顺序:事件捕获阶段,从上往下传播,然后到达事件目标节点,最后是冒泡阶段,从下 往上传播
DOM 节点添加事件监听方法 addEventListener,中参数 capture 可以指定该监听是添加在 事件捕获阶段还是事件冒泡阶段,为 false 是事件冒泡,为 true 是事件捕获,并非所有 的事件都支持冒泡,比如 focus,blur 等等,我们可以通过 event.bubbles 来判断。
事件模型有三个常用方法:
event.stopPropagation:阻止捕获和冒泡阶段中,当前事件的进一步传播, event.stopImmediatePropagetion,阻止调用相同事件的其他侦听器, event.preventDefault,取消该事件(假如事件是可取消的)而不停止事件的进一步传播, event.target:指向触发事件的元素,在事件冒泡过程中这个值不变 event.currentTarget = this,时间帮顶的当前元素,只有被点击时目标元素的 target 才会等 于 currentTarget,
最后,对于执行顺序的问题,如果 DOM 节点同时绑定了两个事件监听函数,一个用于 捕获,一个用于冒泡,那么两个事件的执行顺序真的是先捕获在冒泡吗,答案是否定的, 绑定在被点击元素的事件是按照代码添加顺序执行的,其他函数是先捕获再冒泡
事件代理是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开 始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给 最里面的 a 加一个 click 点击事件,那么这个事件就会一层一层的往外执行,执行顺序 a>li>ul>div,有这样一个机制,那么我们给最外面的 div 加点击事件,那么里面的 ul,li, a 做点击事件的时候,都会冒泡到最外层的 div 上,所以都会触发,这就是事件代理, 代理它们父级代为执行事件。
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(resolve, reject) {
console.log(2)
for (var i = 0; i < 10000; i++) {
if(i === 10) {console.log(10)}
i == 9999 && resolve();
}
console.log(3)
}).then(function() {
console.log(4)
})
console.log(5);
//2 10 3 5 4 1
要先弄清楚 settimeout(fun,0)何时执行,promise 何时执行,then 何时执行 102 settimeout 这种异步操作的回调,只有主线程中没有执行任何同步代码的前提下,才会 执行异步回调,而 settimeout(fun,0)表示立刻执行,也就是用来改变任务的执行顺序, 要求浏览器尽可能快的进行回调
promise 何时执行,由上图可知 promise 新建后立即执行,所以 promise 构造函数里代码 同步执行的, then 方法指向的回调将在当前脚本所有同步任务执行完成后执行,
那么 then 为什么比 settimeout 执行的早呢,因为 settimeout(fun,0)不是真的立即执行, 经过测试得出结论:执行顺序为:同步执行的代码-》promise.then->settimeout
任务队列中,在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直 提取,直到 microsoft 队列为空为止。 也就是说如果某个 microtask 任务被推入到执行中,那么当主线程任务执行完成后,会 循环调用该队列任务中的下一个任务来执行,直到该任务队列到最后一个任务为止。而 事件循环每次只会入栈一个 macrotask,主线程执行完成该任务后又会检查 microtasks 队 列并完成里面的所有任务后再执行 macrotask 的任务。macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering microtasks: process.nextTick, Promise, MutationObserver
ES6必会十大新特性
vue面试知识点总结https://blog.csdn.net/qq_53061847/article/details/125015424?spm=1001.2014.3001.5502
原生AJAX的请求:
Ajax 能够在不重新加载整个页面的情况下与服务器交换数据并更新部分网页内容,实现 局部刷新,大大降低了资源的浪费,是一门用于快速创建动态网页的技术,ajax 的使用 分为四部分
1、创建 XMLHttpRequest 对象 var xhr = new XMLHttpRequest();
2、向服务器发送请求,使用 xmlHttpRequest 对象的 open 和 send 方法,
3、监听状态变化,执行相应回调函数
var xhr = new XMLHttpRequest();
xhr.open('get', 'aabb.php', true);
xhr.send(null);
xhr.onreadystatechange = function() {
if(xhr.readyState==4) {
if(xhr.status==200) {
console.log(xhr.responseText);
}
}
}
通过实例化一个 XMLHttpRequest 对象得到一个实例,调用实例的 open 方法为这次 ajax 请求设定相应的 http 方法,相应的地址和是否异步,以异步为例,调用 send 方法,这 个方法可以设定需要发送的报文主体,然后通过监听 readystatechange 事件,通过这个实 例 的 readyState 属性来判断这个 ajax 请求状态
其中分为 0,1,2,3,4 这四种状态:
- 0 未初始化
- 1 载入/正在发送请求
- 2 载入完成/数据接收
- 3 交互/解析数据
- 4 接收数据完成
当状态为 4 的时候也就是接受数据完成的时候,这时候可以通过实例的 status 属 性判断这个请求是否成功
需求:如何实现 ajax 请求,假如我有多个请求,我需要让这些 ajax 请求按照某 种顺序一次执行,有什么办法呢?如何处理 ajax 跨域
使 ajax 请求按照队列顺序执行,通过调用递归函数: //按顺序执行多个 ajax 命令,因为数量不定,所以采用递归
function send(action, arg2) {
//将多个命令按顺序封装成数组对象,递归执行
//利用了 deferred 对象控制回调函数的特点
$.when(send_action(action[0], arg2))
.done(function () {
//前一个 ajax 回调函数完毕之后判断队列长度
if (action.length > 1) {
//队列长度大于 1,则弹出第一个,继续递归执行该队列
action.shift();
send(action, arg2);
}
}).fail(function (){
//队列中元素请求失败后的逻辑
//
//重试发送
//send(action, arg2);
//
//忽略错误进行下个
//if (action.length > 1) {
//队列长度大于 1,则弹出第一个,继续递归执行该队列
// action.shift();
// send(action, arg2);
//}
});
}
//处理每个命令的 ajax 请求以及回调函数
function send_action(command, arg2) {
var dtd = $.Deferred();//定义 deferred 对象
$.post( "url", {
command: command
arg2: arg2
}
).done(function (json) {
json = $.parseJSON(json);
//每次请求回调函数的处理逻辑
//
//
//
//逻辑结束
dtd.resolve();
}).fail(function (){
//ajax 请求失败的逻辑
dtd.reject();
});
return dtd.promise();//返回 Deferred 对象的 promise,防止在外部
1. 移动布局方案https://links.jianshu.com/go?to=https%3A%2F%2Fjuejin.im%2Fpost%2F599970f4518825243a78b9d5%23heading-22
2. Rem, Em
https://blog.csdn.net/romantic_love/article/details/80875462
一、rem 单位如何转换为像素值 1.当使用 rem 单位的时候,页面转换为像素大小取决于叶根元素的字体大小,即 HTML 元素的字体大小。根元素字体大小乘 rem 的值。例如,根元素的字体大小为 16px,那么 10rem 就等同于 10*16=160px。
二、em 是如何转换成 px 的 当使用 em 单位的时候,像素值是将 em 值乘以使用 em 单位的元素的字体大小。例如一 个 div 的字体为 18px,设置它的宽高为 10em,那么此时宽高就是 18px*10em=180px。
http原理详解https://blog.csdn.net/hguisu/article/details/8680808
前端工程师做出网页,需要通过网络请求向后端获取数据,因此 http 协议是前端面试的必考内容。
1.1 状态码分类
1.2 常见状态码
1.3 关于协议和规范
4.1 关于缓存
什么是缓存? 把一些不需要重新获取的内容再重新获取一次
为什么需要缓存? 网络请求相比于 CPU 的计算和页面渲染是非常非常慢的。
哪些资源可以被缓存? 静态资源,比如 js css img。
4.2 强制缓存
Cache-Control:
Cache-Control 有哪些值:
4.3 协商缓存(对比缓存)
资源标识:
Last-Modified:
服务端拿到 if-Modified-Since 之后拿这个时间去和服务端资源最后修改时间做比较,如果一致则返回 304 ,不一致(也就是资源已经更新了)就返回 200 和新的资源及新的 Last-Modified。
Etag:
其实 Etag 和 Last-Modified 一样的,只不过 Etag 是服务端对资源按照一定方式(比如 contenthash)计算出来的唯一标识,就像人类指纹一样,传给客户端之后,客户端再传过来时候,服务端会将其与现在的资源计算出来的唯一标识做比较,一致则返回 304,不一致就返回 200 和新的资源及新的 Etag。
两者比较:
4.4 综述
4.4 三种刷新操作对 http 缓存的影响
正常操作:强制缓存有效,协商缓存有效。 手动刷新:强制缓存失效,协商缓存有效。 强制刷新:强制缓存失效,协商缓存失效。
对于更多面试中可能出现的问题,我还是建议精读这篇三元的文章:HTTP 灵魂之问,巩固你的 HTTP 知识体系。
比如会被经常问到的: GET 和 POST 的区别。
HTTP/2 有哪些改进?(很大可能问原理)
关于 HTTPS 的一些原理,可以阅读这篇文章:这一次,彻底理解 https 原理。接着你可以观看这个视频进行更进一步的学习:HTTPS 底层原理,面试官直接下跪,唱征服!
关于跨域问题,大部分文章都是理论性比较强,还不如读这篇文章,聊聊跨域的原理与解决方法,讲的非常清晰,我个人觉得对付面试就是先知道使用流程,把这个流程能自己说出来,然后再讲下原理即可。
参考链接:https://juejin.cn/post/7061588533214969892