Open SunXinFei opened 4 years ago
我们先说下《JS高级程序设计》中提到的组合式继承;继承即为继承父类属性和继承父类方法,组合式继承核心是使用call继承属性;原型对象上挂载要继承的方法;故为代码如下:
//父类构造函数
function SuperType(name){
this.name = name;
this.colors = ['red','white']
}
SuperType.prototype.sayName = function(){
console.log(this.name);
}
function SubType(name,age){
this.age = age;
SuperType.call(this, name);
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
console.log(this.age);
}
let instance = new SubType('Nical',12);
缺点:父类构造函数执行了两次,SubType.prototype = new SuperType();
会导致SubType.prototype挂载上父类的属性:name和colors,通过SuperType.call(this, name);
在子类实例上挂载属性进行屏蔽。
核心思想:复制一个父类的原型对象赋值给子类的prototype,不必为了指定子类的原型而调用超类构造函数。
/**
* subType- 子类型构造函数
* superType-超类型构造函数
*/
function inheritPrototype(subType, superType) {
//1.创建了超类(父类)原型的(副本)浅复制
var prototype = Object.create(superType.prototype);
/*
2.修正子类原型的构造函数属性
constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数
prototype.constructor 未修改前指向的 superType,为了弥补因重写原型而失去的默认constructor属性。
*/
prototype.constructor = subType;
// 3.将子类的原型替换为超类(父类)原型的(副本)浅复制
subType.prototype = prototype;
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
alert(this.name);
};
function SubType(name, age) {
//构造函数式继承--子类构造函数中执行父类构造函数
SuperType.call(this, name);
this.age = age;
}
// 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
alert(this.age);
}
var instance = new SubType("lichonglou");
console.log(instance.name)
// console.log(instance.constructor)//指向SubType 如果没有修正原型的构造函数,则会指向父类构造函数
class Singleton{
constructor(){
if(!this.constructor.instance){
this.constructor.instance = this ;
}
return this.constructor.instance;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2);//true
class EventEmitter {
constructor(){
this._events = {};
}
on(event,callback){//事件绑定
const callbacks = this._events[event] || [];
callbacks.push(callback);
this._events[event] = callbacks;
}
emit(...args){//事件触发
const event = [].shift.call(args);//Array.prototype.shift.call(args); //args.shift();
(this._events[event] || []).forEach(cb=>cb.apply(this,args))
}
off(event,callback){//事件解除
const callbacks = (this._events[event] || []).filter(cb=> cb!==callback);
this._events[event] = callbacks;
}
once(event,callback){//只触发一次,触发后解绑
const wrap = (...args)=>{
callback.apply(this,args);
this.off(event,wrap);
}
this.on(event,wrap);
}
}
//on&emit
eventEmitter.on('abc', test);
eventEmitter.emit('abc', 3, 666);//3 666
//off
eventEmitter.off('abc',test);
//once
eventEmitter.once('abc',test);
eventEmitter.emit('abc',666,1);//666 1
eventEmitter.emit('abc',888,1);//不输出
提升开发效率 提高模块化 便于单元测试
W3C规范:跨域请求中,分为简单请求和复杂请求;所谓的简单请求,需要满足如下几个条件:
Accept Accept-Language Content-Language Content-Type (需要注意额外的限制) DPR Downlink Save-Data Viewport-Width Width
简单请求不会触发 CORS 预检请求(嗅探请求) 简单请求之外为复杂请求
Promise.resolve() 此方法有一个可选的参数,参数的类型会影响它的返回值,具体可分为三种情况(如下所列),其中有两种情况会创建一个新的已处理的Promise实例,还有一种情况会返回这个参数。
总的来说:then方法提供一个供自定义的回调函数,若传入非函数,则会忽略当前then方法,回调函数中会把上一个then中返回的值当做参数值供当前then方法调用。 then方法执行完毕后需要返回一个新的值给下一个then调用(没有返回值默认使用undefined)。 每个then只可能使用前一个then的返回值。 再次描述: 对于传入 then 方法的参数,首先判断其是否为 function,判断为否,直接执行 下一个 then 的 success 函数;判断为是,接着判断函数的返回值 res 类型是否为 Promise,如果为否,直接执行下一个 then 的 success 函数,如果为是,通过 then 调用接下来的函数。
//Promise 支持异步
class SimplePromise {
constructor(callback) {
this._value = null;//存储value值
this.status = 'pending';
this._onSuccess = [];
this._onFail = [];
let resolve = (val)=>{//传递给外部去主动调用的成功函数
if (this.status === 'pending') {
this.status = 'resolve';
this._value = val;
while (this._onSuccess.length > 0) {//如果队列有方法,则进行调用
let temp = this._onSuccess.shift();
temp(val);
}
}
}
let reject = (val)=>{//传递给外部去主动调用的失败函数
if (this.status === 'pending') {
this.status = 'resolve';
this._value = val;
while (this._onFail.length > 0) {//如果队列有方法,则进行调用
let temp = this._onFail.shift();
temp(val);
}
}
}
callback(resolve, reject)
}
then(onFulfilled, onRejected) {
switch (this.status) {//状态判断
case 'resolve'://成功,调用成功回调函数,并传值
onFulfilled(this._value);
break;
case 'reject'://失败,调用失败回调函数,并传值
onRejected(this._value);
break;
case 'pending'://等待状态,则先将函数放入队列中,等待外部调用触发resolve/reject,再进行调用
this._onSuccess.push(onFulfilled);
this._onFail.push(onRejected);
break;
}
}
}
//测试case
const pro = new SimplePromise(function(res, rej) {
setTimeout(function() {
let random = Math.random() * 10
if (random > 5) {
res("success")
} else {
rej("fail")
}
}, 2000)
}
)
pro.then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})
Promise.prototype.all = (promises = [])=>{
return new Promise((resolve,reject)=>{
let resultArr = [];
let count = 0;
promises.forEach((p,i)=>{
Promise.resolve(p).then((value)=>{
count++;
resultArr[i] = value;
if(count === promises.length){
resolve(resultArr)
}
},reject)
})
})
}
Promise.prototype.race = (promises = []) => {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw new TypeError(`argument must be a array`);
}
for (const p of promises) {
// 有一个成功就返回成功状态的promise
// 有一个失败就返回失败状态的promise
p.then(resolve, reject);
}
});
}
let p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(1)
}, 1000)
})
let p2 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(2)
}, 2000)
})
let p3 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(3)
}, 3000)
})
Promise.all([p3, p1, p2]).then(res => {
console.log(res) // [3, 1, 2]
});
Promise.race([p3, p1, p2]).then(res => {
console.log(res) // 1
});
实现一个异步队列,可以设置并发请求数量,应用场景例如:分片上传可以控制三个任务
class PromiseQueue{
constructor(limit=1){
this._list = [];
this._current= 0;
this._limit = limit;
}
add(promiseFn){
this._list.push(promiseFn);
this.loadNext();
}
loadNext(){
if(this._list.length === 0 || this._limit === this._current) return;
this._current++;
const fn = this._list.shift();
const promise = fn();
promise.then(this.onLoaded.bind(this)).catch(this.onLoaded.bind(this));
}
onLoaded(){
this._current--;
this.loadNext();
}
}
const q = new PromiseQueue(2);
[1, 2, 3, 4, 5].forEach(v => {
q.add(() => new Promise((resolve) => {
setTimeout(() => {
console.log(v);
resolve();
}, 1000);
}));
});
function debounce(func, delay){
let id;
return function (...args){
clearTimeout(id);
id = setTimeOut(()=>{
func.apply(this,args)
},delay)
}
}
防抖(debounce) :在事件触发后的n秒之后,再去执行真正需要执行的函数,如果在这n秒之内事件又被触发,则重新开始计时。常见的操作就是搜索,中间不断的输入,我们都可以忽略,只获取最后停止输入的时候,才去请求后端。
//截流
/**
* 连续的方法 延迟,如持续输入数据,搜索
* @param {Function} fn 需要延迟运行的的回调函数
**/
let previous = 0
export const throttle = (fn,delay) => {
return function() {
let now = +new Date();
if (now - previous > delay) {
fn.apply(this, arguments);
previous = now;
}
}
}
//调用地方
/**
* 处理滚动视图的滚动事件
*/
scrollFn(e) {
throttle(this.aaa, 1000)(666);
},
aaa(num){
console.log(this.test,num);
},
节流(throttling):规定好一个单位时间,触发函数一次。如果在这个单位时间内触发多次函数的话,只有一次是可被执行的。想执行多次的话,只能等到下一个周期里。常见的操作比如滚动事件,每隔n毫秒,我们去请求,或者拖拽,每隔n毫秒改变dom的位置。还比如resize窗口。
imgObj.onerror() performance.getEntries(),获取到成功加载的资源,对比可以间接的捕获错误 window.addEventListener('error', fn, true), 会捕获但是不冒泡,所以window.onerror 不会触发,捕获阶段可以触发
Vue有 errorHandler,React有 componentDidCatch 进行错误捕获
方式有很多种,大部分都是进行脚本注入,举个例子:在url里面做操作,将普通访问的url修改为url+script脚本,scrript脚本里面可以做很多功能比如:获取cookie并发送到黑客的服务器(用cookie的httponly解),去提交一个数据...等等。 前端的框架都有成熟的xss跨站脚本防御,一般是用户侧提交的东西,都会进行编码,来防止注入。
这个一般就是首先在A这个安全网站登陆,用户打开了B这个危险网站,B网站里面加了一个img标签,src是A网站的一个get请求,这时候浏览器会将A网站的请求发出去,并且自动携带了A的cookie,A网站后台没有任何保护的情况下就以为是用户在操作,从而被攻击。
使用token解决就是利用了同源策略里面B网站是拿不到A网站任何dom元素或者js脚本执行结果的,将A网站上html加入后端生成的token,发送请求的时候携带上(header中或者链接中都可以),后端拿着token去校验有效性。这样危险网站B就因为不容易伪造token,而伪装失败。
或者设置:
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
Set-Cookie: widget_session=abc123; SameSite=None; Secure
Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
http://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
最常见的是恶意网站使用
for in会输出自身以及原型链上可枚举的属性。 Object.keys是es5中新增的方法,用来获取对象自身可枚举的属性键。 Object.getOwnPropertyNames也是es5中新增的方法,用来获取对象自身的全部属性名。 Object.keys的效果和for in+hasOwnProperty一样
原生具备 Iterator 接口的数据结构如下。
Array Map Set String TypedArray 函数的 arguments 对象 NodeList 对象 https://juejin.im/post/6844904129471463432 https://segmentfault.com/q/1010000011767450?sort=created https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop https://lavrton.com/javascript-loops-how-to-handle-async-await-6252dd3c795/
运算数为数字 typeof(x) = "number" 字符串 typeof(x) = "string" 布尔值 typeof(x) = "boolean" 对象,数组和null typeof(x) = "object" 函数 typeof(x) = "function" 未定义 typeof(undefined) = 'undefined' 所以判断不出对象和数组以及null
a instanceof A 原理就是a沿着proto去查找原型对象,A则沿着prototype去查找,如果两者可以找到相同的则返回true
//fun.call(thisArg, arg1, arg2, ...)
Function.prototype.call = Function.prototype.call || function(context){
var context = context || window;
context.fn = this; // 获取调用call的函数
var args = [];
for(var i=1, len=arguments.length ; i<len ; i++){
args.push(arguments[i]);
}
var result = eval('context.fn(' + args +')');
delete context.fn;
return result;
}
//fun.apply(thisArg,[args]);
Function.prototype.apply = Function.prototype.apply || function(context){
var context = context || window;
context.fn = this;
// var args = [];
// for(var i=1, len=arguments.length ; i<len ; i++){
// args.push(arguments[i]);
// }
var args = arguments[1];
var result = eval('context.fn('+args+')');
delete context.fn;
return result;
}
//fun.bind(thisArg[, arg1[, arg2[, ...]]])
//基础版本
Function.prototype.bind = Function.prototype.bind || function(context){
var self = this;
var context = context || window;
var bindArgs = Array.prototype.slice.call(arguments,1);
return function(){
var tmpArgs = Array.prototype.slice.call(arguments);
var newArgs = bindArgs.concat(tmpArgs);
return self.apply(context, newArgs);
}
}
1、域名分片 HTTP/2 对于同一域名使用一个 TCP 连接足矣,过多 TCP 连接浪费资源而且效果不见得一定好 而且资源分域会破坏 HTTP/2 的优先级特性,还会降低头部压缩效果 2、资源合并 资源合并会不利于缓存机制,而且单文件过大对于 HTTP/2 的传输不好,尽量做到细粒化更有利于 HTTP/2 传输
使用 HTTP/2 尽可能用最少的连接,因为同一个连接上产生的请求和响应越多,动态字典积累得越全,头部压缩效果也就越好,而且多路复用效率高,不会像多连接那样造成资源浪费 为此需要注意以下两点:
参考:
https://juejin.im/post/6844903667569541133
https://segmentfault.com/n/1330000009409949
https://www.zhihu.com/question/34401250
补充:TCP连接的三次握手
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。
function add(x){
let sum = x;
function fn (y){
sum+=y
return fn;
}
fn.valueOf=()=>{
return sum;
}
fn.toString=()=>{
return sum;
};
return fn;
}
console.log(add(2)(3));
console.log(add(1)(2)(3)(4))
每引用一个外部模块,我们都要去下载对应的js文件引入项目, 弊端:
立即调用的函数表达式”(IIFE)去组织模块
从上面的代码比较中我们可以得出AMD规范和CMD规范的区别
一方面,在依赖的处理上
AMD推崇依赖前置,即通过依赖数组的方式提前声明当前模块的依赖 CMD推崇依赖就近,在编程需要用到的时候通过调用require方法动态引入 另一方面,在本模块的对外输出上
AMD推崇通过返回值的方式对外输出 CMD推崇通过给module.exports赋值的方式对外输出
https://zhuanlan.zhihu.com/p/265632724?utm_source=tuicool&utm_medium=referral
异步与同步
单线程与多线程
js为什么是单线程?js语言在设计之初就是单线程的,原因其实也比较简单,js主要是针对浏览器使用,浏览器中单线程能够保证页面中DOM的渲染不出现异常,也正是这个原因,所以在现在新标准允许JavaScript脚本多线程中有一个严格的要求,子线程不能够进行DOM操作,来严格把控住,所以所谓js多线程,也就只能做一些业务上面的运算,比如数量非常大的数据清洗等等。
任务队列
Philip Roberts的PPT视频《Help, I'm stuck in an event-loop》中已经非常清晰地说明了关于任务队列的讲解:
宏任务&微任务
宏任务一般包括:整体代码script,setTimeout,setInterval, setImmediate。 微任务:Promise,process.nextTick , Object.observe [已废弃], MutationObserver
https://juejin.im/post/6844903764202094606
浏览器与node的区别
浏览器为:
node为:
浏览器环境下,microtask 的任务队列是每个 macrotask 执行完之后执行。而在 Node.js 中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 队列的任务。
SettimeOut
基于上面的任务队列,settimeOut函数也就可以解释一些参数0之类的问题了,方法先执行主线程中执行栈的方法,settimeout中的方法会放置到任务队列,并且先进先出,执行栈执行完之后,去任务队列中拿出方法放入执行栈去执行,直到结束为止。 其中delay的毫秒参数的含义如下,MDN,里面提到一点很重要实际延迟可能比预期的更长;这一点很容易理解:实际延迟的时间是看该延迟函数方法之前的那些主线程执行栈中方法执行的时间以及队列中放入执行栈去执行的时间。如果两者超出了延迟时间,那么就产生了实际延迟可能比预期的更长的现象,下面是实验代码验证这个观点
这个例子验证了一点,1000毫秒的含义,其实表示的是第一次运行到这里是1564650928851左右的时间,等到队列中的方法执行完之后,此时间隔时间如果超过了1000ms,则立即执行,如果低于1000ms,则去等待1000ms之后去执行。
这里就验证了,所谓的延迟100ms并不一定是相对延迟了100ms之后,就要去执行,实际延迟可能比预期的更长所以当队列执行到此时,时间已经延迟大于了100ms,所以就立即去执行log 2这个方法。