JS 经典问题
必问
(零) AMD、CMD、CommonJs、ES6的对比
对模块定义的规范化
解决:模块化主要解决两个问题,命名冲突
、文件依赖
AMD | CMD | CommonJs | ES6 |
---|---|---|---|
requireJS | SeaJS | Nodejs | |
define | define | module.exports | export/import |
AMD是预加载 | CMD是懒加载 | ||
优:加载快速,并行加载 并且执行 | 同AMD ,不执行 | ||
缺:执行顺序不可控,容易埋坑 | 顺序执行,等待时间较长 |
注意:Commonjs 中 module.export
跟exports
的区别?
返回对象不同 module.export 可以返回单独返回一个数据类型,而 export只能返回 Object
(一)闭包
简单来说闭包就是函数套函数。
一个函数在执行开始 会给其内部的变量分配一部分内存空间,被后面语句使用,当函数执行完毕,这些变量 为被引用后,则会释放这些内存空间,但是,若在函数内存在一个子函数 在调用主函数的变量的时候,这些变量如果被引用 则这个子函数和这些变量 会被解析器 保存起来 形成一个闭包
优势: 变量私有化
劣势:容易内存泄漏, 解决方法,退出函数之前,变量删除
(二)原型链
参考-面试题:https://www.cnblogs.com/wjyz/p/10219106.html
基础:https://www.jianshu.com/p/08c07a953fa0
什么是原型链?
每个对象都有原型 _proto_,而 原型 还可以由原型 ,以此类推 ,就形成了原型链
prototype 原型指针:是函数独有 的,是一个函数指向一个对象
(三)手写继承
参考:https://www.jianshu.com/p/6925ed009f1e
参考:https://blog.csdn.net/p312011150/article/details/83579313
1. 组合继承
//一般情况 都是使用 call this 变量来进行变量参数传递
function animal(name) {
this.name = name || "动物";
this.run = () => {
console.log(this.name + "==>跑啊");
};
}
// 原型方法
animal.prototype.eat = function(food) {
console.log(this.name + "正在吃:" + food);
};
function Dog() {
animal.call(this);
this.name = "狗狗";
}
Dog.prototype = new animal();
let dog = new Dog();
console.log(dog);
console.log(dog.name);
console.log(dog.run());
console.log(dog.eat("6666"));
2. Object.create()
var Parent = {
getName: function() {
return this.name;
}
}
var child = Object.create(Parent, {
name: { value: "Benjamin"},
url : { value: "http://www.zuojj.com"}
});
console.log(child);
console.log(child.getName());
3. ES6 Class Extend
class Parent {
constructor(name){
this.name = name;
static sayHello(){
console.log(this.name)
}
}
}
class Child extends Parent {
constructor(name, age){
super(name);
this.age = age;
}
sayAge(){
console.log(this.age)
return this.age;
}
}
let parent = new Parent("Parent");
let child = new Child("Child", 18);
(四)深copy 浅 Copy
参考:https://www.cnblogs.com/ljx20180807/p/9790239.html
参考:https://mp.weixin.qq.com/s/vXbFsG59L1Ba0DMcZeU2Bg
Object.assign({},srcObj);
如果对象的属性值为简单类型 得到的新对象为深拷贝;
如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的
- 深copy
简单版本
const deepClone = obj => {
let clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
clone[key] = deepClone(obj[key]);
}
return clone;
};
//对于数组而言 还可以使用
slice(0),concat
(五) Event Loop
参考:https://segmentfault.com/a/1190000016278115
参考:https://segmentfault.com/a/1190000018675871
Event Loop 是一个执行模型
注意:可以将 EventLoop 看成一个单线程结构 ,他将 IO 操作抛出给其他线程,继续执行 他的 方法,在IO 先出现结束后 EventLoop 再把结果抛出主线程
Promise所有的then的回调函数是在一个microtask函数中执行的,但是每一个回调函数的执行,又按照情况分为立即执行,微任务(microtask)和宏任务(macrotask)。
我太难了:这个解释太不靠谱了,需要深入了解一下
- 宏队列: setTimeOut setInterval I/O 等
- 微队列:Promise Object.observe 等
调用栈Stack
浏览器执行代码的过程
:1.执行 全部 JS 代码, 遇到 微任务 ,宏任务,全部抛出到其各自的栈中,继续执行
:2.JS Stack 为空后,再次执行 微任务 ,执行完后 (一个一个的放入Stack 执行)
:3:在执行 为首的 一个宏任务,放入Stack中执行
:4:Stack 为空后继续 2-4
(五)常见算法
- 数组去重
let unique = (array)=>{
return Array.from(new Set(array))
}
unique = (array)=>{
const res = new Map();//WeakSet
return array.filter(curr=>{
let isIn = res.has(curr);
if(!isIn) res.set(curr,0)//0 不重要
return !isIn
})
}
- 数组排序
//冒泡排序
const sortWay = (array)=>{
for(let i=0,len =array.length;i<(len-1);i++ ){
let curr = array[i],currNext = array[i+1];
if(curr < currNext){//降序
array[i]= currNext;
array[i+1]= curr;
}
}
}
//数组自带的sort
array = array.sort((a,b)=>{return a-b})
//数组打乱的算法
//随机取出 20个 数
let a = [1,2,3,4,5....,50];
a = a.map((curr,index)=>{
return {value:curr,radom:Math.radom()}
})).sort((a,b)=>{
return a.radom -b-radom;
}).map(curr=>{
return curr.value;
}).slice(0,20);
谈谈你对js堆和栈的理解
基本问题
(一) typeof 和 instanceof 区别
- typeof在判断 null、array、object以及函数实例(new + 函数)时,得到的都是object
- instanceof 判断一个实例是否属于某种类型 string arrray
(二)es6 常见问题
- 基本数据类型
Number,String, Null, Undefined, Symbol, Boolean
(三)new 做了什么
- 创建一个新的对象
- 新对象的
_proto_
指向 构造对象的prototype
- 构造函数的作用域 赋值给新对象
- 执行构造函数的代码
- 返回这新的对象
(四) arguments与arguments转化成数组的方法
第一:这个问题很奇葩, 但是问了,不会 就尴尬了
function a(){
console.log(arguments)
}
a([1,2,3,4,5,6,7])
转换为数组
function a(){
let newResult = [];
for(let i =0,len = arguments.length;i<len;i++){
newResult.push(arguments[i])
}
return newRresult;
}
a(1,2,3,4,5,6,7)
(五)数据结构
(六)跨域 和cros
参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
跨域
跨域:不同源 就跨域,同源是指 同 协议,同 域名,同端口,
Cros
Cros :跨域资源共享(Cross-origin resource sharing)
是一种机制,出于安全原因,浏览器限制从JS内发起的跨源HTTP请求,并不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了
衍生问题
- 为什么要跨域 优缺点是?
- 怎么解决跨域的问题
- 什么情况需要Cros
跨域 xhr 请求,下载图片 ,css 中的字体引用
- 什么是
简单请求
什么预检请求
简单请求 :不会 发送预检请求 的请求 (GET、HEAD、POST)
预检请求:会发出( OPTIONS 请求进行预先判断允许跨域)(DELETE、PUT,OPTIONS)
- http 常见的请求方式
HTTP请求方法并不是只有GET和POST,只是最常用的。据RFC2616标准(现行的HTTP/1.1)得知,通常有以下8种方法:OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE和CONNECT。
官方定义
HEAD方法跟GET方法相同,只不过服务器响应时不会返回消息体。一个HEAD请求的响应中,HTTP头中包含的元信息应该和一个GET请求的响应消息相同。这种方法可以用来获取请求中隐含的元信息,而不用传输实体本身。也经常用来测试超链接的有效性、可用性和最近的修改。
一个HEAD请求的响应可被缓存,也就是说,响应中的信息可能用来更新之前缓存的实体。如果当前实体跟缓存实体的阈值不同(可通过Content-Length、Content-MD5、ETag或Last-Modified的变化来表明),那么这个缓存就被视为过期了。
简而言之
HEAD请求常常被忽略,但是能提供很多有用的信息,特别是在有限的速度和带宽下。主要有以下特点:
1、只请求资源的首部;
2、检查超链接的有效性;
3、检查网页是否被修改;
4、多用于自动搜索机器人获取网页的标志信息,获取rss种子信息,或者传递安全认证信息等
- 这么配置 允许Cros
Access-Control-Allow-Origin: http://foo.example – 限制允许的域名
Access-Control-Allow-Methods: POST, GET, OPTIONS – 限制允许的请求类型
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400 – 请求结果缓存时间
(七)web安全
参考:https://developer.mozilla.org/zh-CN/docs/Web/Security
例子写得好:https://blog.csdn.net/freeking101/article/details/86537087
- xss 攻击
跨站脚本攻击
是一种代码注入攻击
存储型 XSS :恶意代码存储到 数据库中 服务端执行
反射型 XSS :恶意代码存在 URL 里。服务端执行
DOM型XSS :恶意代码伪造在URL里, 在浏览器端执行
- CSRF攻击
跨站请求伪造
由字面上的意思 就能理解了吧应该
注意:CSRF攻击是源于Web的隐式身份验证机制 ,,大部分是走的cookie 做验证机制
而Cookie 的本身会随着域名下的所有请求达到服务端,这样 就伪造了请求
解决方法
CSRF | XSS |
---|---|
reffer验证 | 输入转义 |
身份代码放在 localstorage、html 或者其他位置(不放在cookie)自定义xhr header | 输入内容长度控制 |
动态签名 | 避免拼接 HTML |
- | 对 各种插入数据进行编码 |
(八)ES6 的箭头函数 this 指向
=> | function |
---|---|
window | 父级 |