Code Monkey home page Code Monkey logo

interviewquestions's People

Contributors

wenjinhua avatar

Watchers

 avatar

interviewquestions's Issues

js基础

  1. 说一下js闭包
  2. 手写call、apply
  3. 深入理解对象和原型链
  4. js继承方式
  5. JS基本数据类型
  6. 数组的遍历方法
  7. 如何判断对象是否为空
  8. 节流和防抖
  9. forEach,map和filter的区别
  10. 函数柯里化
  11. 判断数据类型的方法
  12. window.addEventListener的第三个参数是什么?
  13. 有关promise
  14. delete数组的item,length会不会减一

1. 说一下js的闭包。

  • 闭包是什么?

闭包是有权限访问其他函数作用域内的变量的一个函数。(包含三点:a. 为什么其他非闭包的函数没有权限访问另一个函数的内部作用域、 b. 为什么闭包有这个权限、 c. 什么是函数作用域)

总结后:由于在JS中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数——也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

  • 为什么要用闭包?(闭包解决了什么?)

闭包就是将函数内部和函数外部连接起来的一座桥梁。由于闭包可以缓存上级作用域,那么就使得函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。

  • 闭包的应用场景

a. 一个Ajax请求的成功回调
b. 一个事件绑定的回调方法
c. 一个setTimeout的延时回调
d. 一个函数内部返回另一个匿名函数,这些都是闭包

2.手写call、apply

  • 两种方法的用法
fun.call(thisArg, args1, args2,...);//call方法接收的参数里,第一个是传入的this,剩余的是fun的函数参数
fun.apply(thisArg, argsArr);//第一个参数是传入的this,第二个是参数组成的数组
  • 怎么实现(apply在call的基础上,只需注意是否有第二个参数即可)

先看一个例子

var person = {
  fullName: function(txt) {
     console.log(txt + this.firstName + " " + this.lastName);
   }
 }
var person1 = {
   firstName:"John",
   lastName: "Doe"
} 
person.fullName.call(person1, "Hello, ");  // 输出"Hello, John Doe"

可知call主要做了以下三件事:
(1)改变函数的this指向
注:person.fullName()是隐式绑定了this,根据this绑定优先级(==箭头函数>关键字new>显式绑定call\apply\bind>隐式绑定obj.foo()>默认绑定foo()==)
(2)将call接收的从第二个参数开始的参数作为函数的参数
(3)不更改person(call的调用者)和person1(call中传入的this)的任何属性和方法

实现如下:

  1. 改变函数的this指向:利用隐式绑定,将person1.fullName挂在person的一个方法上
Function.prototype.myCall = function(context){
    //context为传入的this,即person1
    //此时的this指向myCall的调用者,即person.fullName
    context.fn = this;
    context.fn();
}

优化点:
a.兼容this为空时,this指向window: context = context || window
b. 兼容调用函数有返回值: var result = context.fn();
return result;

第一步的代码如下:

Function.prototype.myCall = function(context){
    //context为传入的this,即person1
    //此时的this指向myCall的调用者,即person.fullName
    context = context || window;//优化点1
    context.fn = this;
    var result = context.fn();
    return result;//优化点2
}
  1. 将call接收的从第二个参数开始的参数作为函数的参数,要解决的点有以下几个:
    a. 怎么获取所有的参数: 伪数组arguments
    b. 怎么传递参数:eval函数
Function.prototype.myCall = function(context){
    context = context || window;
    context.fn = this;
    
    //使用arguments获得myCall方法所有的参数
    var args = [];
    for(var i=1; i<arguments.length; i++){
        args.push(arguments[i]);
    }

    var result = eval("context.fn(" + args + ")");
    var result = context.fn();
    return result;
}

==思路如下==:
a. 使用arguments可以获取所有的参数,在这儿我们利用arguments是伪数组(有length属性),来遍历从第二个参数开始的参数,并放到数组中。
b. 为什么不直接这样写呢?以下两种写法里,args都是数组,与形参的类型不匹配

var result = context.fn(args);
var result = eval("context.fn(args)");
var result = context.fn(args);

c. 既然数组的类型不匹配,那么可否把数组里边的元素拆分成形参的形式呢?将数组转化为字符串

var result = context.fn(args.toString());//形式上感觉是对了,但是args.toString()还是作为一个整体对应形参的一个参数,而没有挨个与形参对应。

d. 既然不能正确的将数组转化后的字符串与形参参数一一对应,那么可以转变一下思路,==让函数来迎合数组==,也就是利用eval方法,因为eval方法就接收一个字符串,所以正好用上了c中数组转化后的字符串。

var args_str = args.toString();
var result = eval("context.fn(args_str)");//字符串又是作为一个整体

var result = eval("context.fn(" + args + ")");//字符串的每一项都可以作为一个个体

e. d中的两种写法,同样都是传入一个字符串,但是第一种写法里字符串又只能作为一个整体,即匹配第一个形参。这是为什么呢==?==
==好像是eval中的变量需要拼接的写法,这是规定吗?==

  1. 不更改person(call的调用者)和person1(call中传入的this)的任何属性和方法
    由于我们在函数fn挂在了person1上,如果person1原本就有一个叫做fn的函数呢,那我们可以在fn执行完后把它删掉delete person1.fn。那这样的话会把person1原本的fn函数删除掉。

那怎么办呢? 我们可以采用Math.random()随机生成一个id,把这个id作为函数名,当preson1中已经存在这个id时,那么就再生成一个id。

Function.prototype.myCall = function(context){
    context = context || window;
    
    //生成随机数作为函数名称
    var uniqueId = "00" + Math.Random();
    while(context.hasOwnProperty(uniqueId)){
        uniqueId = "00" + Math.Random();
    }
    context[uniqueId] = this;
    
    var args = [];
    for(var i=1; i<arguments.length; i++){
        args.push(arguments[i]);
    }
    var result = eval("context.fn(" + args + ")");
    var result = context.fn();
    return result;
}

3. 深入理解对象和原型链

4. js继承方式

  • js实现继承的方式
//父类
function Person(name){
    this.name = name;
}
Person.prototype.job = 'frontend';
Person.prototype.sayHello = function() {
    console.log('Hello '+this.name);
}
var person = new Person('name111');
person.sayHello(); // Hello jia ming
  1. 原型链继承: 子类的原型继承父类的实例Child.prototype = new Person();
function Child() {
    this.name = 'child';
}
Child.prototype = new Person();//关键
var child = new Child();
console.log(child.job); // frontend
// instanceof 判断元素是否在另一个元素的原型链上
// child是Person类的实例
console.log(child instanceof Person); // true

特点:
a. 实例可继承的属性有:实例的构造函数Child的属性,父类构造函数Person的属性,父类原型Person.prototype上的属性。(新实例不会继承父类实例person的属性)
b. 所有新实例都会共享父类实例的属性。(原型上的属性是共享的)

2.构造函数继承: 用call或apply将父类构造函数引入子类函数

function Child() {
    Person.call(this, 'reng');//关键
}
var child = new Child();
console.log(child.name); // reng
console.log(child instanceof Person); // false
child.sayHello(); // 报错,继承不了父类原型上的东西

特点:
a. 只继承了父类构造函数的属性,没有继承父类原型的属性。
b. 可以继承多个构造函数的属性(call可以多个),但是无法实现构造函数的复用,且臃肿。
c. 在子实例中可以向父实例传参。

  1. 组合继承(原型链+构造函数)
// 组合继承
function Child(name) {
    Person.call(this, name);//构造函数继承
}
Child.prototype = new Person();//原型继承
var child = new Child('jia');
child.sayHello(); // Hello jia
console.log(child instanceof Person); // true

特点:
a. 结合了两种模式的优点--向父类传参(call)和复用(prototype)。
b. 调用了两次父类的构造函数(耗内存)

  1. 原型式继承: 将原型链继承用一个函数封装起来
// 先封装一个函数容器,用来承载继承的原型和输出对象
function object(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}
var super0 = new Person();
var super1 = object(super0);
console.log(super1 instanceof Person); // true
console.log(super1.job); // frontend

特点:
a. 所有的实例都会继承原型上的属性。

Object.create()规范了原型式继承: 这个方法接收两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。

// 传一个参数的时候
var anotherPerson = Object.create(new Person());//anotherPerson继承了Person
console.log(anotherPerson.job); // frontend
console.log(anotherPerson instanceof Person); // true
// 传两个参数的时候
var anotherPerson = Object.create(new Person(), {
    name: {
        value: 'come on'
    }
});
anotherPerson.sayHello(); // Hello come on
  1. 寄生式继承: 封装了原型式继承,并添加了一些参数
function object(obj) {
    function F(){}
    F.prototype = obj;
    return new F();
}
var sup = new Person();
// 以上是原型式继承,给原型式继承再套个壳子传递参数
function subobject(obj) {
    var sub = object(obj);
    sub.name = 'ming';
    return sub;
}
var sup2 = subobject(sup);
// 这个函数经过声明后就成了可增添属性的对象
console.log(sup2.name); // 'ming'
console.log(sup2 instanceof Person); // true

特点:
a. 就是给原型式继承外面套个壳子。

  1. 寄生组合式继承: 在组合继承的基础上,修改prototype为寄生继承的实例
// 寄生继承
function object(obj) {
    function F(){}
    F.prototype = obj;
    return new F();
}
var obj = object(Person.prototype);//obj继承了Person

// 组合继承
function Sub() {
    this.age = 100;
    Person.call(this); // 这个继承了父类构造函数的属性
} // 解决了组合式两次调用构造函数属性的特点

// 重点(不太懂)
Sub.prototype = obj;
console.log(Sub.prototype.constructor); // Person
obj.constructor = Sub; // 一定要修复实例
console.log(Sub.prototype.constructor); // Sub
var sub1 = new Sub();

// Sub实例就继承了构造函数属性,父类实例,object的函数属性
console.log(sub1.job); // frontend
console.log(sub1 instanceof Person); // true

5. JS的数据类型

1. 数据类型介绍:

  1. 基本数据类型:string、number、boolean、undefined、null、symbol
  • string 、number 、boolean 和 nullundefined 这五种类型统称为原始类型(Primitive),表示不能再细分下去的基本类型;
  • symbol是ES6中新增的数据类型,symbol 表示独一无二的值,通过 Symbol 函数调用生成,由于生成的 symbol 值为原始类型,所以 Symbol 函数不能使用new 调用;
  • null 和 undefined 通常被认为是特殊值,这两种类型的值唯一,就是其本身。
  1. 对象类型:对象类型也叫引用类型,array和function是对象的子类型。

2. js中的强制转换规则

1. ToPrimitive(转换为原始值),ES7规范。
只针对引用类型,把引用类型转化为原始类型

/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)

其中,对type不同值的说明:

  • type为string
    (1)先调用obj的toString方法,如果为原始值,则return,否则进行第2步
    (2)调用obj的valueOf方法,如果为原始值,则return,否则进行第3步
    (3)抛出TypeError 异常
  • type为number:
    (1)先调用obj的valueOf方法,如果为原始值,则return,否则进行第2步
    (2)调用obj的toString方法,如果为原始值,则return,否则第3步
    (3)抛出TypeError 异常
  • type参数为空
    (1)该对象为Date,则type被设置为String
    (2)否则,type被设置为Number

对于toString()和valueOf()作如下说明:

  • toString()方法返回一个表示该对象的字符串.每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用。
  • Object.prototype.valueOf()方法返回指定对象的原始值。不同内置对象的valueOf实现:
    String => 返回字符串值
    Number => 返回数字值
    Date => 返回一个数字,即时间值,字符串中内容是依赖于具体实现的
    ==Boolean => 返回Boolean的this值
    Object => 返回this==
var str = new String('123');
console.log(str.valueOf());//123

var num = new Number(123);
console.log(num.valueOf());//123

var date = new Date();
console.log(date.valueOf()); //1526990889729

var bool = new Boolean('123');
console.log(bool.valueOf());//true

var obj = new Object({valueOf:()=>{
    return 1
}})
console.log(obj.valueOf());//1

2. Number运算符转换规则:

  • null 转换为 0
  • undefined 转换为 NaN
  • true 转换为 1,false 转换为 0
  • 字符串转换时遵循数字常量规则,转换失败返回NaN

3. String 运算符转换规则

  • null 转换为 'null'
  • undefined 转换为 undefined
  • true 转换为 'true',false 转换为 'false'
  • 数字转换遵循通用规则,极大极小的数字使用指数形式
String(null)                 // 'null'
String(undefined)            // 'undefined'
String(true)                 // 'true'
String(1)                    // '1'
String(-1)                   // '-1'
String(0)                    // '0'
String(-0)                   // '0'
String(Math.pow(1000,10))    // '1e+30'
String(Infinity)             // 'Infinity'
String(-Infinity)            // '-Infinity'
String({})                   // '[object Object]'
String([1,[2,3]])            // '1,2,3'
String(['koala',1])          //koala,1

4. ToBoolean 运算符转换规则
除了下述 6 个值转换结果为 false,其他全部为true:

  • undefined
  • null
  • -0
  • 0或+0
  • NaN
  • ‘’(空字符串)

3. js转换规则不同场景应用

1. 什么时候自动转换为string类型?

  • 在没有对象的前提下
    字符串的自动转换,主要发生在字符串的加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。
'2' + 1 // '21'
'2' + true // "2true"
'2' + false // "2false"
'2' + undefined // "2undefined"
'2' + null // "2null"
  • 当有对象且与对象+时候
//toString的对象
var obj2 = {
    toString:function(){
        return 'a'
    }
}
console.log('2'+obj2)
//输出结果2a

//常规对象
var obj1 = {
   a:1,
   b:2
}
console.log('2'+obj1);
//输出结果 2[object Object]

//几种特殊对象
'2' + {} // "2[object Object]"
'2' + [] // "2"
'2' + function (){} // "2function (){}"
'2' + ['koala',1] // 2koala,1

对下面'2'+obj2详细举例说明如下:
(1)左边为string,ToPrimitive原始值转换后不发生变化
(2)右边转化时同样按照ToPrimitive进行原始值转换,==由于指定的type是number(type为空时,非date对象则为number)==,进行ToPrimitive转化调用obj2.valueof(),得到的不是原始值,进行第三步
(3)调用toString()return 'a'
(4)符号两边存在string,而且是+号运算符则都采用String规则转换为string类型进行拼接
(5)输出结果2a

对下面'2'+obj1详细举例说明如下:
(1)左边为string,ToPrimitive转换为原始值后不发生变化
(2)右边转化时同样按照ToPrimitive进行原始值转换,由于指定的type是number,进行ToPrimitive转化调用obj2.valueof(),得到{ a: 1, b: 2 }
(3)调用toString()return [object Object]
(4)符号两边存在string,而且是+号运算符则都采用String规则转换为string类型进行拼接
(5)输出结果2[object Object]

2. 什么时候自动转换为Number类型

  • 有加法运算符,但是无String类型的时候,都会优先转换为Number类型
true + 0 // 1
true + true // 2
true + false //1
  • 除了加法运算符,其他运算符都会把运算自动转成数值
    (==null转为数值时为0,而undefined转为数值时为NaN==)
'5' - '2' // 3
'5' * '2' // 10
true - 1  // 0
false - 1 // -1
'1' - 1   // 0
'5' * []    // 0
false / '5' // 0
'abc' - 1   // NaN
null + 1 // 1
undefined + 1 // NaN

//一元运算符(注意点)
+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0

3. 判断等号也放在Number里面特殊说明

  • 如果x,y均为number,直接比较
1 == 2 //false
  • 如果存在对象,ToPrimitive()type为number进行转换,再进行后面比较
var obj1 = {
    valueOf:function(){
        return '1'
    }
}
1 == obj1  //true
//obj1转为原始值,调用obj1.valueOf()
//返回原始值'1'
//'1'toNumber得到 1 然后比较 1 == 1
[] == ![] //true
//[]作为对象ToPrimitive得到 ''  
//![]作为boolean转换得到0 
//'' == 0 
//转换为 0==0 //true
  • 存在boolean,按照ToNumber将boolean转换为1或者0,再进行后面比较
//boolean 先转成number,按照上面的规则得到1  
//3 == 1 false
//0 == 0 true
3 == true // false
'0' == false //true
  • 如果x为string,y为number,x转成number进行比较
//'0' toNumber()得到 0  
//0 == 0 true
'0' == 0 //true

4. 什么时候进行布尔转换

  • 布尔比较时
  • if(obj) , while(obj)等判断时或者 三元运算符只能够包含布尔值
if ( !undefined
  && !null
  && !0
  && !NaN
  && !''
) {
  console.log('true');
} // true

//下面两种情况也会转成布尔类型
expression ? true : false
!! expression

4. js中的数据类型判断

三种方式,分别为 typeof、instanceof 和Object.prototype.toString()

  1. 通过 typeof操作符来判断一个值属于哪种基本类型。
typeof 'seymoe'    // 'string'
typeof true        // 'boolean'
typeof 10          // 'number'
typeof Symbol()    // 'symbol'
typeof null        // 'object' 无法判定是否为 null
typeof undefined   // 'undefined'

typeof {}           // 'object'
typeof []           // 'object'
typeof(() => {})    // 'function'

上面代码的输出结果可以看出,

  • null 的判定有误差,得到的结果
    如果使用 typeof,null得到的结果是object
  • 操作符对对象类型及其子类型,例如函数(可调用对象)、数组(有序索引对象)等进行判定,则==除了函数都会得到 object 的结果==。
  1. instanceof
    通过 instanceof 操作符也可以对对象类型进行判定,其原理就是==测试构造函数的prototype 是否出现在被检测对象的原型链上==。
[] instanceof Array            // true
({}) instanceof Object         // true
(()=>{}) instanceof Function   // true
  1. Object.prototype.toString()
Object.prototype.toString.call({})              // '[object Object]'
Object.prototype.toString.call([])              // '[object Array]'
Object.prototype.toString.call(() => {})        // '[object Function]'
Object.prototype.toString.call('seymoe')        // '[object String]'
Object.prototype.toString.call(1)               // '[object Number]'
Object.prototype.toString.call(true)            // '[object Boolean]'
Object.prototype.toString.call(Symbol())        // '[object Symbol]'
Object.prototype.toString.call(null)            // '[object Null]'
Object.prototype.toString.call(undefined)       // '[object Undefined]'

Object.prototype.toString.call(new Date())      // '[object Date]'
Object.prototype.toString.call(Math)            // '[object Math]'
Object.prototype.toString.call(new Set())       // '[object Set]'
Object.prototype.toString.call(new WeakSet())   // '[object WeakSet]'
Object.prototype.toString.call(new Map())       // '[object Map]'
Object.prototype.toString.call(new WeakMap())   // '[object WeakMap]
  • 该方法本质就是依托Object.prototype.toString()方法得到对象内部属性 [[Class]]
  • 传入原始类型却能够判定出结果是因为对值进行了包装
  • null 和 undefined 能够输出结果是内部实现有做处理

5. NaN的相关总结

NaN 是一个全局对象的属性,NaN是一种特殊的Number类型。

  1. 什么时候返回NaN
  • 无穷大除以无穷大
  • 给任意负数做开方运算
  • 算数运算符与不是数字或无法转换为数字的操作数一起使用
  • 字符串解析成数字

6. toString()和String()的区别

  1. toString()
  • 可以将数据都转为字符串,但是null和undefined不可以转换。
  • toString()括号中可以写数字,代表进制
  1. String()可以将null和undefined转换为字符串,但是没法转进制字符串

6. 数组的遍历方法

  1. while
let index = 0
const array = [1,2,3,4,5,6]
while(index<array.length){
    console.log(array[index])
    index++
}
//result:1 2 3 4 5 6
  1. for
const array = [1,2,3,4,5,6]
for(let index = 0; index < array.length; index++) {
    console.log(array[index])
}
//result:1 2 3 4 5 6
  1. forEach
const array = [1,2,3,4,5,6]

array.forEach(function(current_value,index,value){
    console.log(`At index ${index} in ${array} value is ${current_value}`)

})
/*
At index 0 in 1,2,3,4,5,6 value is 1
At index 0 in 1,2,3,4,5,6 value is 2
At index 0 in 1,2,3,4,5,6 value is 3
At index 0 in 1,2,3,4,5,6 value is 4
At index 0 in 1,2,3,4,5,6 value is 5
At index 0 in 1,2,3,4,5,6 value is 6
*/
  1. map: map会对每一个数组选项应用函数,并返回一个新的数组。
const array = [1,2,3,4,5,6]

const square = x => Math.pow(x,2)

const squares = array.map(square)

console.log(`Original array: ${array}`)
console.log(`squared array ${squares}`)
/*
Original array: 1,2,3,4,5,6
squared array 1,4,9,16,25,36
*/
  1. reduce: reduce()方法对数组中的每个元素执行一个由您提供的reduce函数(升序执行),将其结果汇总为单个值返回。
const array = [1,2,3,4,5,6]

const sum = (x,y) => x + y

const array_sum = array.reduce(sum,0)

console.log(`The sum of ${array} is ${array_sum}`)

//The sum of 1,2,3,4,5,6 is 21
  1. filter: 通过布尔函数过滤数组中的元素,最后返回一个由过滤出来的元素组成的数组。
const array = [1,2,3,4,5,6]

const even = x => x < 7

const even_array = array.filter(even)

console.log(`Even numbers in array ${array} is ${even_array}`)

//Even numbers in array 1,2,3,4,5,6 is 2,4,6
every
  1. every: every()方法测试一个数组内的所有元素是否能够通过某个指定函数的测试,它返回一个布尔值。
const array = [1,2,3,4,5,6]

const under_seven = x => x < 7

if(array.every(under_seven)) {
    console.log('所有在该数组中的元素均小于7')
}else{
    console.log('在该数组中的元素至少有一项不小于7')
}
//所有在该数组中的元素均小于7
  1. some: some()方法测试数组中是不是至少有一个元素通过了被提供的函数测试,它返回的是一个 boolean 类型的值。
const array = [1,2,3,4,5,6]

const over_seven= x => x > 7

if(array.every(over_seven)) {
    console.log('至少有一个元素大于7')
}else{
    console.log('没有元素大于7')
}
//没有元素大于7

7. 如何判断对象是否为空

我的csdn上有写

8. 节流和防抖

参考:https://segmentfault.com/a/1190000018428170

  • 对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。
  • 节流:按照设定的时间固定执行一次函数,比如200ms一次。(例如input框的查询)

(防抖中为什么要使用闭包?)

9. forEach,map和filter的区别

  1. forEach
  • 参数:回调函数(当前元素,当前元素的索引,整个数组)
  • 返回值:无
  1. map
  • 参数:回调函数
  • 返回值:由回调函数的返回值组成的新数组
  1. filter
  • 参数:每个元素都会执行的函数(currentValue、index、arr)
  • 返回值:数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组。

10. 函数柯里化

11. 判断数据类型的方法

参考: https://segmentfault.com/a/1190000015264821

  • 基本数据类型(number string boolean undefined null symbol)
  • 复杂数据类型(object)

方法有如下几种

  • typeof
  • instanceof(复杂数据类型)
  • constructor
  • Object.prototype.toString()---最优
  • jquery中的$.type

下面将对如下几种数据进行判断

var bool = true
var num = 1
var str = 'abc'
var und = undefined
var nul = null

var arr = [1,2,3]
var obj = {name:'haoxl',age:18}
var fun = function(){console.log('I am a function')}
  1. typeof
  • 不可检测:null、array
console.log(typeof bool); //boolean
console.log(typeof num);//number
console.log(typeof str);//string
console.log(typeof und);//undefined
console.log(typeof nul);//object no

console.log(typeof arr);//object no
console.log(typeof obj);//object no
console.log(typeof fun);//function
  1. instanceof
  • 不可检测:undefined、null、不是用new创建的基本数据类型
  1. constructor
  • 不可检测:undefined、null
  1. Object.prototype.toString.call
  • 在任何值上调用 Object 原生的 toString() 方法,都会返回一个 [object NativeConstructorName] 格式的字符串。每个类在内部都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的构造函数名。
  • 但是它不能检测非原生构造函数的构造函数名。
console.log(Object.prototype.toString.call(bool));//[object Boolean]
console.log(Object.prototype.toString.call(num));//[object Number]
console.log(Object.prototype.toString.call(str));//[object String]
console.log(Object.prototype.toString.call(und));//[object Undefined]
console.log(Object.prototype.toString.call(nul));//[object Null]
console.log(Object.prototype.toString.call(arr));//[object Array]
console.log(Object.prototype.toString.call(obj));//[object Object]
console.log(Object.prototype.toString.call(fun));//[object Function]

function Person(){}
function Student(){}
Student.prototype = new Person()
var haoxl = new Student()
console.log(Object.prototype.toString.call(haoxl));//[object Object]
  1. jquery中的$.type
  • 原理同4

12. target.addEventListener的第三个参数是什么?

参考:https://www.jianshu.com/p/bad857d649f2
使用方法如下:

target.addEventListener(type, listener[, options]); 
target.addEventListener(type, listener[, useCapture]);
  1. options(object):包括3个布尔值选项
  • capture: 是否使用事件捕获。默认值为false(即使用事件冒泡)
  • once: 是否只调用一次,if true,会在调用后自动销毁listener。默认值为false.。
  • passive: if true, 意味着listener永远不会调用preventDefault方法。如果又确实调用了的话,浏览器只会console一个warning,而不会真的去执行preventDefault方法。
  1. useCapture(boolean):默认值为false(即 使用事件冒泡)

13. 有关promise

1. promise.all如何限制并发数量?

2. promise的异常处理方式

14. delete数组的item,length会不会减一

不会

  1. 删除对象的属性: 从某个对象上移除指定属性。成功删除的时候回返回 true,否则返回 false。
  • 如果你试图删除的属性不存在,那么delete将不会起任何作用,但仍会返回true
  • 如果对象的原型链上有一个与待删除属性同名的属性,那么删除属性之后,对象会使用原型链上的那个属性(也就是说,delete操作只会在自身的属性上起作用)
  • 任何使用 var 声明的属性不能从全局作用域或函数的作用域中删除
    (1)delete操作不能删除任何在全局作用域中的函数(无论这个函数是来自于函数声明或函数表达式)
    (2)在对象(object)中的函数是能够用delete操作删除的。
  • 任何用let或const声明的属性不能够从它被声明的作用域中删除
  • 不可设置的(Non-configurable)属性不能被移除。这意味着像Math, Array, Object内置对象的属性以及使用Object.defineProperty()方法设置为不可设置的属性不能被删除。
  • 当一个属性被设置为不可设置,delete操作将不会有任何效果,并且会返回false。在严格模式下会抛出语法错误(SyntaxError)。
  1. 删除数组的元素
  • 删除一个数组元素时,数组的长度不受影响。
  • 被删除元素的位置的值是empty(undefined类型)

框架

一. react性能优化

  • Code Splitting
  • shouldComponentUpdate||PureComponent避免重复渲染
  • 使用不可突变数据结构
  • 组件尽可能的进行拆分、解耦
  • 列表类组件优化---增加key属性
  • bind函数优化---写在constructor中
  • 不要滥用props---避免传递多余的数据导致不必要的更新
  • ReactDOMServer进行服务端渲染组件

二. react组件之间通信(传参、调用方法)方式

[参考详细分析](https://github.com/wenjinhua/my-blog/issues/2)

三. react的几种事件绑定方式

由于类的方法默认不会绑定this,因此在调用的时候如果忘记绑定,this的值将会是undefined。

3.1 绑定方式

  1. 在构造函数中使用bind绑定this
class Button extends React.Component {
constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick(){
    console.log('this is:', this);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

2 在调用的时候使用bind绑定this

class Button extends React.Component {
  handleClick(){
    console.log('this is:', this);
  }
  render() {
    return (
      <button onClick={this.handleClick.bind(this)}>
        Click me
      </button>
    );
  }
}
  1. 在调用的时候使用箭头函数绑定this
class Button extends React.Component {
  handleClick(){
    console.log('this is:', this);
  }
  render() {
    return (
      <button onClick={()=>this.handleClick()}>
        Click me
      </button>
    );
  }
}
  1. 使用属性初始化器语法绑定this(实验性)
class Button extends React.Component {
  handleClick=()=>{
    console.log('this is:', this);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

3.2比较

  1. 方式2和方式3都是在调用的时候再绑定this。
  • 优点:写法比较简单,当组件中没有state的时候就不需要添加类构造函数来绑定this
  • 缺点:每一次调用的时候都会生成一个新的方法实例,因此对性能有影响,并且当这个函数作为属性值传入低阶组件的时候,这些组件可能会进行额外的重新渲染,因为每一次都是新的方法实例作为的新的属性传递。
  1. 方式1在类构造函数中绑定this,调用的时候不需要再绑定
  • 优点:只会生成一个方法实例,并且绑定一次之后如果多次用到这个方法也不需要再绑定。
  • 缺点:即使不用到state,也需要添加类构造函数来绑定this,代码量多一点。。。
  1. 方式4利用属性初始化语法,将方法初始化为箭头函数,因此在创建函数的时候就绑定了this。
  • 创建方法就绑定this,不需要在类构造函数中绑定,调用的时候不需要再作绑定。结合了方式1、方式2、方式3的优点
  • 目前仍然是实验性语法,需要用babel转译

四. react如何进行组件/逻辑的复用?

  1. 高阶组件(HOC):
    • 属性代理
    • 反向继承
  2. 渲染属性(Render Props)
  3. react-hooks

4.1 用法

  1. hoc
    高阶组件是参数为组件,返回值为新组件的函数
const EnhancedComponent = higherOrderComponent(WrappedComponent);
  1. Render Props
    具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑。
<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>
  1. react-hooks

4.2 优劣对比

  1. Mixin的缺陷:
  • 组件与 Mixin 之间存在隐式依赖(Mixin 经常依赖组件的特定方法,但在定义组件时并不知道这种依赖关系)
  • 多个 Mixin 之间可能产生冲突(比如定义了相同的state字段)
  • Mixin 倾向于增加更多状态,这降低了应用的可预测性
  • 隐式依赖导致依赖关系不透明,维护成本和理解成本迅速攀升:
  1. HOC
  • 优点(和mixin相比)
    • HOC通过外层组件通过 Props 影响内层组件的状态,而不是直接改变其 State不存在冲突和互相干扰,这就降低了耦合度
    • HOC 具有天然的层级结构(组件树结构),这又降低了复杂度
  • 缺点
    • 扩展性限制:HOC 无法从外部访问子组件的 State因此无法通过shouldComponentUpdate滤掉不必要的更新,React 在支持 ES6 Class 之后提供了React.PureComponent来解决这个问题
    • Ref 传递问题: Ref 被隔断,后来的React.forwardRef 来解决这个问题
    • Wrapper Hell: HOC可能出现多层包裹组件的情况,多层抽象同样增加了复杂度和理解成本
    • 命名冲突: 如果高阶组件多次嵌套,没有使用命名空间的话会产生冲突,然后覆盖老属性
    • 不可见性: HOC相当于在原有组件外层再包装一个组件,你压根不知道外层的包装是啥,对于你是黑盒
  1. Render Props
  • 优点:hoc的缺点都可以解决
  • 缺点:
    • 使用繁琐: HOC使用只需要借助装饰器语法通常一行代码就可以进行复用,Render Props无法做到如此简单
    • 嵌套过深: Render Props虽然摆脱了组件多层嵌套的问题,但是转化为了函数回调的嵌套
  1. react-hooks
  • 优点:
    • 简洁: React Hooks解决了HOC和Render Props的嵌套问题,更加简洁
    • 解耦: React Hooks可以更方便地把 UI 和状态分离,做到更彻底的解耦
    • 组合: Hooks 中可以引用另外的 Hooks形成新的Hooks,组合变化万千
    • 函数友好: React Hooks为函数组件而生,从而解决了类组件的几大问题:
      • this 指向容易错误
      • 分割在不同声明周期中的逻辑使得代码难以理解和维护
      • 代码复用成本高(高阶组件容易使代码量剧增)
  • 缺点:
    • 额外的学习成本
    • 写法上有限制(不能出现在条件、循环中),并且写法限制增加了重构成本
    • 破坏了PureComponent、React.memo浅比较的性能优化效果(为了取最新的props和state,每次render()都要重新创建事件处函数)
    • 在闭包场景可能会引用到旧的state、props值
    • React.memo并不能完全替代shouldComponentUpdate(因为拿不到 state change,只针对 props change)

五. setstate是异步的还是同步的?

1.setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。

2.setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数setState(partialState, callback)中的callback拿到更新后的结果。(hooks中通过在useEffect中拿到更新后的值)

3.在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

六.浅谈 react的事件机制(合成事件和原生事件)

6.1 为什么有合成事件的抽象?

如果DOM上绑定了过多的事件处理函数,整个页面响应以及内存占用可能都会受到影响。React为了避免这类DOM事件滥用,同时屏蔽底层不同浏览器之间的事件系统差异,实现了一个中间层——SyntheticEvent。

6.2 合成事件的原理

React并不是将click事件绑在该div的真实DOM上,而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运行。

1.绑定机制

  • 事件委派:所有事件绑定到结构的最外层,使用一个统一的事件监听器。这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。
  • 自动绑定:在React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。而且 React 还会对这种引用进行缓存,以达到 CPU 和内存的最优化。

2.源码分析

  • 事件注册
    即在 document 节点,将 React 事件转化为 DOM 原生事件,并注册回调。
  • 事件存储
  • 事件执行
    每次触发事件都会执行根节点上 addEventListener 注册的回调:
    • 找到事件触发的 DOM 和 React Component
    • 从该 React Component,调用 findParent 方法,遍历得到所有父组件,存在数组中
    • 从该组件直到最后一个父组件,根据之前事件存储,用 React 事件名 + 组件 key,找到对应绑定回调方法,执行

6.3 合成事件和原生事件的执行顺序

浏览器事件执行的三个阶段:

  • 捕获阶段
  • 目标阶段
  • 冒泡阶段
  1. 原生事件:在目标阶段触发(先触发
  2. 合成事件:在由当前dom元素冒泡到document元素上时执行(但是React支持将监听器注册在捕获阶段,即便如此,原生事件依然先于捕获阶段的合成事件执行
  • 在原生事件中执行e.stopPropagation会阻止合成事件(阻止了事件冒泡到document上)
  • 在合成事件中执行e.stopPropagation不会阻止原生事件
  • 当合成事件和DOM 事件 都绑定在document上的时候,React的处理是合成事件应该是先放进去的所以会先触发,在这种情况下,原生事件对象的 stopImmediatePropagation能做到阻止进一步触发document DOM事件

(其中,stopImmediatePropagation :如果有多个相同类型事件的事件监听函数绑定到同一个元素,则当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation()方法,则剩下的监听函数将不会被执行)

6.4 react阻止冒泡的方法总结

  • 阻止合成事件间的冒泡,用e.stopPropagation();
  • 阻止合成事件与最外层document上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation();
  • 阻止合成事件与除最外层document上的原生事件上的冒泡,通过判断e.target来避免
componentDidMount() { 
document.body.addEventListener('click', e => {   
 if (e.target && e.target.matches('div.code')) {  
      return;    
  }    
  this.setState({   active: false,    });   }); 
 }

七. react的请求放在哪个生命周期中?

  1. componengwillmount中请求的弊端:
  • 跟服务器端渲染(同构)有关系,如果在componentWillMount里面获取数据,fetch data会执行两次,一次在服务器端一次在客户端。在componentDidMount中可以解决这个问题。
  • 在componentWillMount中fetch data,如果请求依赖于dom节点,那么此时dom还没渲染出来。
  • react16.0以后,componentWillMount可能会被执行多次
  1. 疑问点:
  • 1中为什么在willmount中会请求两次?
  • 3中,执行多次的机制是什么?

八. react最新的生命周期

  1. 挂载阶段
  • constructor
  • getDerivedStateFromProps
    static getDerivedStateFromProps(nextProps, prevState),这是个静态方法,可用于在接收到新的属性想去修改state
  • render
  • componentDidMount
  1. 更新阶段
  • getDerivedStateFromProps: 此方法在更新个挂载阶段都可能会调用
  • shouldComponentUpdate: shouldComponentUpdate(nextProps, nextState),有两个参数nextProps和nextState,表示新的属性和变化之后的state,返回一个布尔值
  • render
  • getSnapshotBeforeUpdate: getSnapshotBeforeUpdate(prevProps, prevState),这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用
  • componentDidUpdate:
    componentDidUpdate(prevProps, prevState, snapshot),该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或更新状态。
  1. 卸载阶段
  • componentWillUnmount

十. 为什么列表循环的key最好不要用index?

  1. key为index时:
    变化前数组的值是[1,2,3,4],key就是对应的下标:0,1,2,3
    变化后数组的值是[4,3,2,1],key对应的下标也是:0,1,2,3
  • 那么diff算法在变化前的数组找到key =0的值是1,在变化后数组里找到的key=0的值是4
  • 因为子元素不一样就重新删除并更新
  1. 加了唯一的key,如下:
    变化前数组的值是[1,2,3,4],key就是对应的下标:id0,id1,id2,id3
    变化后数组的值是[4,3,2,1],key对应的下标也是:id3,id2,id1,id0
  • 那么diff算法在变化前的数组找到key =id0的值是1,在变化后数组里找到的key=id0的值也是1
  • 因为子元素相同,就不删除并更新,只做移动操作,这就提升了性能

十一. 如何在React中使用innerHTML?

  • dangerouslySetInnerHTML:
    dangerouslySetInnerHTML 是 React 为浏览器 DOM 提供 innerHTML 的替换方案。通常来讲,使用代码直接设置 HTML 存在风险,因为很容易无意中使用户暴露于跨站脚本(XSS)的攻击。因此,你可以直接在 React 中设置 HTML,但当你想设置 dangerouslySetInnerHTML 时,需要向其传递包含 key 为 __html 的对象,以此来警示你。
function createMarkup() {
  return {__html: 'First &middot; Second'};
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}

十三. 什么是受控组件和非受控组件?

  • 受状态控制的组件:必须要有onChange方法,否则不能使用 受控组件可以赋予默认值(官方推荐使用 受控组件) 实现双向数据绑定
class Input extends Component{
    constructor(){
        super();
        this.state = {val:'100'}
    }
    handleChange = (e) =>{ //e是事件源
        let val = e.target.value;
        this.setState({val});
    };
    render(){
        return (<div>
            <input type="text" value={this.state.val} onChange={this.handleChange}/>
            {this.state.val}
        </div>)
    }
}
  • 非受控也就意味着我可以不需要设置它的state属性,而通过ref来操作真实的DOM
class Sum extends Component{
    constructor(){
        super();
        this.state =  {result:''}
    }
    //通过ref设置的属性 可以通过this.refs获取到对应的dom元素
    handleChange = () =>{
        let result = this.refs.a.value + this.b.value;
        this.setState({result});
    };
    render(){
        return (
            <div onChange={this.handleChange}>
                <input type="number" ref="a"/>
                {/*x代表的真实的dom,把元素挂载在了当前实例上*/}
                <input type="number" ref={(x)=>{
                    this.b = x;
                }}/>
                {this.state.result}
            </div>
        )
    }
}

十四. react diff算法详解

16.1 什么是diff算法?

参考:https://segmentfault.com/a/1190000016539430
https://segmentfault.com/a/1190000018914249 https://zhuanlan.zhihu.com/p/20346379

  1. 传统diff:
    diff算法即差异查找算法;对于Html DOM结构即为tree的差异查找算法;时间复杂度为O(n^3)
  • O(n^3)度的计算
  1. react diff:
    React采用虚拟DOM技术实现对真实DOM的映射,即React Diff算法的差异查找实质是对两个JavaScript对象的差异查找;
  • 基于三个策略
    • tree diff:DOM 节点跨层级的移动操作特别少,可以忽略不计。
    • component diff:拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
    • element diff:于同一层级的一组子节点,它们可以通过唯一 id 进行区分

16.2 react diff解读

  1. diff粒度
  • tree diff:对树的每一层遍历,如果组件不存在就直接销毁
  • component diff:
    • 同一类型的组件,继续比较(是否需要比较,可用shoulComponentUpdate)
    • 不同类型的组件,直接替换
  • element diff:
    • 上述同一类型的组件则继续比较下去(以列表为例)
    • 有三种操作:添加、删除、移动
    • 比较策略:
      • 使用uuid即key对列表组件命名
      • 先全部遍历一遍,确定需要删除和新增的
      • 确定需要移动的
    • 建议:uuid不要设置为数组的index
  1. React更新阶段会对ReactElement类型判断而进行不同的操作;ReactElement类型包含三种即:文本、Dom、组件;
  2. 每个类型的元素更新处理方式:
  • 自定义元素的更新,主要是更新render出的节点,做甩手掌柜交给render出的节点的对应component去管理更新。
  • text节点的更新很简单,直接更新文案。
  • 浏览器基本元素的更新,分为两块:
    • 更新属性,对比出前后属性的不同,局部更新。并且处理特殊属性,比如事件绑定。
    • 子节点的更新,子节点更新主要是找出差异对象

16.3 基于react Diff的开发建议

  1. 基于tree diff:
    • 开发组件时,注意保持DOM结构的稳定;即,尽可能少地动态操作DOM结构,尤其是移动操作。
    • 当节点数过大或者页面更新次数过多时,页面卡顿的现象会比较明显。这时可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。
  2. 基于component diff:
    • 注意使用 shouldComponentUpdate() 来减少组件不必要的更新。
    • 对于类似的结构应该尽量封装成组件,既减少代码量,又能减少component diff的性能消耗。
  3. 基于element diff:
    • 对于列表结构,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

十五. 说说对ssr的理解

直接在服务端层获取数据,渲染出完成的 HTML 文件,直接返回给用户浏览器访问。

  • 前后端分离: 前端与服务端隔离,前端动态获取数据,渲染页面。
  • 缺点
    • 首屏渲染性能瓶颈:空白延迟: HTML下载时间 + JS下载/执行时间 + 请求时间 + 渲染时间。在这段时间内,页面处于空白的状态。
    • SEO 问题: 由于页面初始状态为空,因此爬虫无法获取页面中任何有效数据,因此对搜索引擎不友好。
  • 原理
    • Node 服务: 让前后端运行同一套代码成为可能。
    • Virtual Dom: 让前端代码脱离浏览器运行。
  • 开发流程

十六. 高阶组件hoc

  1. 简述
  • 高阶组件不是组件,是 增强函数,可以输入一个元组件,返回出一个新的增强组件;
  • 高阶组件的主要作用是 代码复用,操作 状态和参数;
  1. 用法
  • 属性代理 (Props Proxy): 返回出一个组件,它基于被包裹组件进行 功能增强;

    • 默认参数: 可以为组件包裹一层默认参数;
    function proxyHoc(Comp) {
    	return class extends React.Component {
    		render() {
    			const newProps = {
    				name: 'tayde',
    				age: 1,
    			}
    			return <Comp {...this.props} {...newProps} />
    		}
    	}
    }
    
    
    • 提取状态: 可以通过 props 将被包裹组件中的 state 依赖于外层组件,例如用于转换受控组件:
    function withOnChange(Comp) {
    	return class extends React.Component {
    		constructor(props) {
    			super(props)
    			this.state = {
    				name: '',
    			}
    		}
    		onChangeName = () => {
    			this.setState({
    				name: 'dongdong',
    			})
    		}
    		render() {
    			const newProps = {
    				value: this.state.name,
    				onChange: this.onChangeName,
    			}
    			return <Comp {...this.props} {...newProps} />
    		}
    	}
    }
    
    
    • 包裹组件: 可以为被包裹元素进行一层包装,
  • 反向继承 (Inheritance Inversion): 返回出一个组件,继承于被包裹组件,常用于以下操作

    function IIHoc(Comp) {
        return class extends Comp {
            render() {
                return super.render();
            }
        };
    }
    
    • 渲染劫持
      • 条件渲染: 根据条件,渲染不同的组件
      • 可以直接修改被包裹组件渲染出的 React 元素树
    • 操作状态 (Operate State): 可以直接通过 this.state 获取到被包裹组件的状态,并进行操作。但这样的操作容易使 state 变得难以追踪,不易维护,谨慎使用。
  1. 应用场景
  • 权限控制(条件渲染),通过抽象逻辑,统一对页面进行权限判断,按不同的条件进行页面渲染
  • 性能监控,包裹组件的生命周期,进行统一埋点:
function withTiming(Comp) {
    return class extends Comp {
        constructor(props) {
            super(props);
            this.start = Date.now();
            this.end = 0;
        }
        componentDidMount() {
            super.componentDidMount && super.componentDidMount();
            this.end = Date.now();
            console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);
        }
        render() {
            return super.render();
        }
    };
}
  • 代码复用,可以将重复的逻辑进行抽象。
  1. 注意
  • 纯函数: 增强函数应为纯函数,避免侵入修改元组件;
  • 避免用法污染: 理想状态下,应透传元组件的无关参数与事件,尽量保证用法不变
  • 命名空间: 为 HOC 增加特异性的组件名称,这样能便于开发调试和查找问题
  • 引用传递: 如果需要传递元组件的 refs 引用,可以使用React.forwardRef
  • 静态方法: 元组件上的静态方法并无法被自动传出,会导致业务层无法调用;解决:
    • 函数导出
    • 静态方法赋值
  • 重新渲染: 由于增强函数每次调用是返回一个新组件,因此如果在 Render 中使用增强函数,就会导致每次都重新渲染整个HOC,而且之前的状态会丢失;

浏览器

@[TOC]
一. 浏览器的缓存机制
二. 如何进行性能优化
三. 从输入网址到网页出现全过程

一. 浏览器的缓存机制

二. 如何进行性能优化?

  • 编码优化
  • 静态资源优化
  • 交付优化
  • 其他

2.1 度量标准与设定目标

  • 首次有效绘制(First Meaningful Paint,简称FMP,当主要内容呈现在页面上)
  • 英雄渲染时间(Hero Rendering Times,度量用户体验的新指标,当用户最关心的内容渲染完成)
  • 可交互时间(Time to Interactive,简称TTI,指页面布局已经稳定,关键的页面字体是可见的,并且主进程可用于处理用户输入,基本上用户可以点击UI并与其交互)
  • 输入响应(Input responsiveness,界面响应用户输入所需的时间)
  • 感知速度指数(Perceptual Speed Index,简称PSI,测量页面在加载过程中视觉上的变化速度,分数越低越好)
  • 自定义指标,由业务需求和用户体验来决定。

2.2 编码优化

  1. 数据读取速度
  • 几个影响数据访问速度的因素:
    • 字面量与局部变量的访问速度最快,数组元素和对象成员相对较慢
    • 变量从局部作用域到全局作用域的搜索过程越长速度越慢
    • 对象嵌套的越深,读取速度就越慢
    • 对象在原型链中存在的位置越深,找到它的速度就越慢
  • 推荐的做法是缓存对象成员值。将对象成员值缓存到局部变量中会加快访问速度
  1. dom
    应用在运行时,性能的瓶颈主要在于DOM操作的代价非常昂贵,下面列出一些关于DOM操作相关提升性能的建议:
  • 在JS中对DOM进行访问的代价非常高。请尽可能减少访问DOM的次数(建议缓存DOM属性和元素、把DOM集合的长度缓存到变量中并在迭代中使用。读变量比读DOM的速度要快很多。)
  • 重排与重绘的代价非常昂贵。如果操作需要进行多次重排与重绘,建议先让元素脱离文档流,处理完毕后再让元素回归文档流,这样浏览器只会进行两次重排与重绘
  • 善于使用事件委托
  1. 流程控制
  • 避免使用for...in(它能枚举到原型,所以很慢)
  • ?在JS中倒序循环会略微提升性能
  • 减少迭代的次数
  • ?基于循环的迭代比基于函数的迭代快8倍
  • 用Map表代替大量的if-else和switch会提升性能

2.3 静态资源优化

  1. 使用Brotli或Zopfli进行纯文本压缩
  2. 图片优化
    尽可能通过srcset,sizes和元素使用响应式图片。还可以通过元素使用WebP格式的图像。

2.4 交付优化

  1. 异步无阻塞加载JS
  • defer(推荐)
  • async(推荐)
  • 动态创建script标签
  • 使用XHR异步请求JS代码并注入到页面
  1. 使用Intersection Observer实现懒加载
  • 可以通过Intersection Observer延迟加载图片、视频、广告脚本、或任何其他资源。
  • 可以先加载低质量或模糊的图片,当图片加载完毕后再使用完整版图片替换它。
  1. 优先加载关键的CSS
    默认情况下浏览器只有在完成标签中CSS的加载与解析之后才会渲染页面。如果CSS文件过大,用户就需要等待很长的时间才能看到渲染结果。
  • 将首屏渲染必须用到的CSS提取出来内嵌到中
  • 将剩余部分的CSS用异步的方式加载
  1. 快速响应的用户界面
  • 使用骨架屏或添加一些Loading过渡动画提示用户体验
  • 输入响应:
    • ?将一个大任务拆分成多个小任务分布在不同的Macrotask中执行
    • 或者使用WebWorkers,它可以在UI线程外执行JS代码运算,不会阻塞UI线程,所以不会影响用户体验。

2.5 构建优化

  1. 使用 Tree-shaking、Scope hoisting、Code-splitting
  • Tree-shaking是一种在构建过程中清除无用代码的技术。使用Tree-shaking可以减少构建后文件的体积。
  • Scope Hoisting。它们可以检查import链,并尽可能的将散乱的模块放到一个函数中,前提是不能造成代码冗余。
  • code-splitting能够把代码分离到不同的bundle中,然后可以按需加载或并行加载这些文件。
  1. 服务端渲染(SSR)
    单页应用需要等JS加载完毕后在前端渲染页面,也就是说在JS加载完毕并开始执行渲染操作前的这段时间里浏览器会产生白屏。
    服务端渲染(Server Side Render,简称SSR)的意义在于弥补主要内容在前端渲染的成本,减少白屏时间,提升首次有效绘制的速度。可以使用服务端渲染来获得更快的首次有效绘制。

  2. 使用import函数动态导入模块

  • 使用import函数可以在运行时动态地加载ES2015模块,从而实现按需加载的需求。
  • 使用静态import导入初始依赖模块。其他情况下使用动态import按需加载依赖

三. 从输入网址到网页出现全过程

  1. DNS 解析:将域名解析成 IP 地址(浏览器并不能直接通过域名找到对应的服务器,而是要通过 IP 地址)
  2. TCP 连接:TCP 三次握手
  3. 发送 HTTP 请求
  4. 服务器处理请求并返回 HTTP 报文
  5. 浏览器解析渲染页面
  6. 断开连接:TCP 四次挥手

1. DNS域名解析

  • url组成:scheme://host.domain:port/path/filename

(1)scheme - 定义因特网服务的类型。常见的协议有 http、https、ftp、file,其中最常见的类型是 http,而 https 则是进行加密的网络传输。
host - 定义域主机(http 的默认主机是 www)
domain - 定义因特网域名,比如 http://w3school.com.cn
port - 定义主机上的端口号(http 的默认端口号是 80)
path - 定义服务器上的路径(如果省略,则文档必须位于网站的根目录中)。



filename - 定义文档/资源的名称

  • 域名解析过程

(1) 浏览器缓存
当用户通过浏览器访问某域名时,浏览器首先会在自己的缓存中查找是否有该域名对应的IP地址(若曾经访问过该域名且没有清空缓存便存在)

(2)系统缓存
当浏览器缓存中无域名对应IP则会自动检查用户计算机系统Hosts文件DNS缓存是否有该域名对应IP. (==windows和mac下各自所在的路径是什么?==)

(3)路由器缓存
当浏览器及系统缓存中均无域名对应IP则进入路由器缓存中检查.
以上三步均为客户端的DNS缓存;

(4)ISP(互联网服务提供商)DNS缓存
当在用户客服端查找不到域名对应IP地址,则将进入ISP DNS缓存中进行查询。比如你用的是电信的网络,则会进入电信的DNS缓存服务器中进行查找;

(5)根域名服务器
当以上均未完成,则进入根服务器进行查询。全球仅有13台根域名服务器,1个主根域名服务器,其余12为辅根域名服务器。根域名收到请求后会查看区域文件记录,若无则将其管辖范围内顶级域名(如.com)服务器IP告诉本地DNS服务器;

(6)顶级域名服务器
顶级域名服务器收到请求后查看区域文件记录,若无则将其管辖范围内主域名服务器的IP地址告诉本地DNS服务器;

(7)主域名服务器
主域名服务器接受到请求后查询自己的缓存,如果没有则进入下一级域名服务器进行查找,并重复该步骤直至找到正确纪录;

(8)保存结果至缓存
本地域名服务器把返回的结果保存到缓存,以备下一次使用,同时将该结果反馈给客户端,客户端通过这个IP地址与web服务器建立链接。

2. TCP三次握手

  • 字段说明:
    (1)SYN:发起一个新连接(用来同步的)
    (2)ACK: 确认序号有效(用来应答的)
    (3)Ack: 确认序号,x+1,只有ACK为1时,Ack才有效
    (4)序号:seq序号,占32位,用来标识tcp端发送的字节流
    (5)状态:SYN_SENT在发送连接请求后等待匹配的状态
    SYN_REVD在收到和发送一个连接请求后等待连接的状态
    ESTABLISHE代表一个打开的链接,数据可以传输给用户
  • 定义:
    三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了==确认双方的接收能力和发送能力是否正常==、指定自己的初始化序列号为后面的可靠性传送做准备。
  • 过程:
    (1)客户端给服务端发一个 SYN 报文(SYN=1),并指明客户端的初始化序列号 ISN©(seq=x)。此时客户端处于 SYN_SENT 状态。
    (2)服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文(SYN=1)作为应答,并且也是指定了自己的初始化序列号 ISN(s)(seq=y)。同时会把客户端的 ISN + 1 作为Ack(ack=x+1) 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。
    (3)客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 Ack 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。
  • 问题:
    (1)为什么需要三次握手?两次不行吗?
    a. 第一次握手:客户端发送网络包,服务端收到了。
    这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
    b. 第二次握手:服务端发包,客户端收到了。
    这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
    c. 第三次握手:客户端发包,服务端收到了。
    这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
    ==因此,需要三次握手才能确认双方的接收与发送能力是否正常。==
    (2)什么是半连接队列?
    a. 服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。
    b. 当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。
    (3)ISN(Initial Sequence Number)是固定的吗?
    a. 当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。==ISN随时间而变化,因此每个连接都将具有不同的ISN==。ISN可以看作是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。
    b. 三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
    (4)三次握手过程中可以携带数据吗?
    第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。

3. 发送http请求

http请求包括请求航、请求头、请求体
POST /chapter17/user.html HTTP/1.1

  1. 请求行: 包含请求方法、URL、协议版本
  • 请求方法包含 8 种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE。
  • URL 即请求地址,由 <协议>://<主机>:<端口>/<路径>?<参数> 组成
  • 协议版本即 http 版本号

4. 服务器处理请求并返回 HTTP 报文

响应报文包括响应行、响应头、响应体

  • 响应行:协议版本,状态码,状态码描述

5. 浏览器解析渲染页面

  1. 浏览器将从服务器获取的HTML文档构建成文档对象模型DOM
  • DOM 树解析的过程是一个深度优先遍历。即先构建当前节点的所有子节点,再构建下一个兄弟节点。
  • 在读取 HTML 文档,构建 DOM 树的过程中,若遇到 script 标签,则 DOM 树的构建会暂停,直至脚本执行完毕。
  1. 根据 CSS 解析生成 CSS 规则树
  • 解析 CSS 规则树时 js 执行将暂停,直至 CSS 规则树就绪。
  • 浏览器在 CSS 规则树生成之前不会进行渲染。
  1. 结合 DOM 树和 CSS 规则树,生成渲染树
  • DOM 树和 CSS 规则树全部准备好了以后,浏览器才会开始构建渲染树。
  • 精简 CSS 并可以加快 CSS 规则树的构建,从而加快页面相应速度。
  1. 根据渲染树计算每一个节点的信息(布局)
  • 布局:通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸
  • 回流:在布局完成后,发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染。
  1. 根据计算好的信息绘制页面
  • 绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。
  • 重绘:某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的重绘。
  • 回流:某个元素的尺寸发生了变化,则需重新计算渲染树,重新渲染。

其中,重绘和回流
(1)当页面元素样式的改变不影响元素在文档流中的位置时(例如background-color, border-color,visibility),浏览器只会将新样式赋予元素并进行重绘操作。
(2)当改变影响文档内容或者结构,或者元素位置时,回流操作就会被触发,一般有以下几种情况:
a. DOM操作(对元素的增删改,顺序变化等);
b. 内容变化,包括表单区域内的文本改变;
c. CSS属性的更改或重新计算;
d. 增删样式表内容;
e. 修改class属性;
f. 浏览器窗口变化(滚动或缩放);
g. 伪类样式激活(:hover等)。

  • 浏览器如何优化渲染
    (1)合法地书写HTML和CSS,不要忘了文档编码类型。样式文件应当在 标签中,脚本文件在 结束前。
    (2)简化并优化你的CSS选择器.将嵌套层减少到最小。浏览器中CSS选择器是从右到左进行匹配的,这也是为什么越短的选择器运行越快的原因
    (3)尽量减少DOM操作。缓存所有的内容,包括属性和对象(如果他们需要被复用的话)。尽量将元素缓存到本地之后再进行操作,最后再添加到DOM当中。
    (4)尽量只对 position 为 absolute/fixed的元素设置动画。

6. TCP连接4次挥手

  • 定义:这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
  • 过程:
    (1)发起方向被动方发送报文,Fin、Ack、Seq,表示已经没有数据传输了。并进入 FIN_WAIT_1 状态。(第一次挥手:由浏览器发起的,发送给服务器,我请求报文发送完了,你准备关闭吧)
    (2)被动方发送报文,Ack、Seq,表示同意关闭请求。此时主机发起方进入 FIN_WAIT_2 状态。(第二次挥手:由服务器发起的,告诉浏览器,我请求报文接受完了,我准备关闭了,你也准备吧)
    (3)被动方向发起方发送报文段,Fin、Ack、Seq,请求关闭连接。并进入 LAST_ACK 状态。(第三次挥手:由服务器发起,告诉浏览器,我响应报文发送完了,你准备关闭吧)
    (4)发起方向被动方发送报文段,Ack、Seq。然后进入等待 TIME_WAIT 状态。被动方收到发起方的报文段以后关闭连接。发起方等待一定时间未收到回复,则正常关闭。(第四次挥手:由浏览器发起,告诉服务器,我响应报文接受完了,我准备关闭了,你也准备吧)
  • 问题:
  1. 挥手为什么需要四次?
    因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。
    但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。==只有等到我服务端所有的报文都发送完了,我才能发送FIN报文==,因此不能一起发送。故需要四次挥手。

2.四次挥手释放连接时,等待2MSL的意义?

  • 保证客户端发送的最后一个ACK报文段能够到达服务端。
    这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,服务端超时重传FIN+ACK报文段,而客户端能在2MSL时间内收到这个重传的FIN+ACK报文段。
  • 防止“已失效的连接请求报文段”出现在本连接中。
    客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。

css基础

一. css盒模型
二. 块级格式化上下文
三. css清除浮动
四. css的伪元素和伪类
五. css的样式优先级
六. css中transition和animation的属性都有哪些?
七. css性能优化
八. 为什么CSS选择器是从右向左匹配的?
九. 隐藏页面中的某个元素的方法有哪些?

一. css盒模型

1. 概念

CSS盒模型本质上是一个盒子,封装周围的HTML元素.包括margin、border、padding、content

2. 分类

  1. W3C盒子模型(标准盒模型):
    盒子总宽度/高度 = width/height + padding + border + margin。( ==即 width/height 只是内容高度,不包含 padding 和 border 值== )

  2. IE盒子模型(怪异盒模型):
    盒子总宽度/高度 = width/height + margin = (内容区宽度/高度 + padding + border) + margin。( ==即 width/height 包含了 padding 和 border 值== )

3. 设置方式

标准:box-sizing: content-box; ( 浏览器默认设置 )
IE: box-sizing: border-box;

4. JS如何获取盒模型对应的宽和高

(1)dom.style.width/height: 只能取到行内样式的宽和高,style 标签中和 link 外链的样式取不到。
(2)dom.currentStyle.width/height: (只有IE兼容)取到的是最终渲染后的宽和高
(3)window.getComputedStyle(dom).width/height: 同(2)但是多浏览器支持,IE9 以上支持。
(4)dom.getBoundingClientRect().width/height: 也是得到渲染后的宽和高,大多浏览器支持。IE9 以上支持,除此外还可以取到相对于视窗的上下左右的距离。
(5)dom.offsetWidth/offsetHeight: 包括高度(宽度)、内边距和边框,不包括外边距。最常用,兼容性最好。

二. 块级格式化上下文

1. 定义:
它是一个独立的渲染区域,只有块级元素参与, 它规定了内部的块级元素如何布局,并且与这个区域外部毫不相干。

2. 布局规则:

  • 内部的Box会在垂直方向,一个接一个地放置。
  • Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
  • 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
  • BFC的区域不会与float box重叠。
  • ==BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。==
  • 计算BFC的高度时,浮动元素也参与计算

3. 哪些元素会生成BFC?

  • 根元素
  • float属性不为none
  • position为absolute或fixed
  • overflow不为visible
  • display为inline-block, table-cell, table-caption, flex, inline-flex

4. BFC的作用及其原理

(1) 自适应两栏布局:

  • 块级元素的宽度默认会撑满整行
  • BFC元素不会和浮动元素发生重叠
<style>
    body {
        width: 300px;
        position: relative;
    }
 
    .aside {
        width: 100px;
        height: 150px;
        float: left;
        background: #f66;
    }
 
    .main { 
        height: 200px;
        background: #fcc;
        overflow: hidden;
    }
</style>
<body>
    <div class="aside"></div>
    <div class="main"></div>
</body>

默认情况下,main会占据一整行的宽度,且在aside下方
给main设置了overflow:hidden之后,形成了bfc,由于bfc元素不会与浮动元素发生重叠,所以main就自然占据了aside剩余的行宽。从而形成了两栏自适应布局。

(2)清除浮动(详见3)

  • 计算BFC的高度时,浮动元素也参与计算
<style>
    .par {
        border: 5px solid #fcc;
        width: 300px;
        overflow: hidden;
    }
 
    .child {
        border: 5px solid #f66;
        width:100px;
        height: 100px;
        float: left;
    }
</style>
<body>
    <div class="par">
        <div class="child"></div>
        <div class="child"></div>
    </div>
</body>

(3)防止垂直margin重叠

  • 属于同一个BFC的两个相邻Box的margin会发生重叠
  • 为了使得margin不重叠,另两个相邻的box分别处在不同的bfc中
<style>
    .wrap{
        overflow: hidden;
    }
    p {
        color: #f55;
        background: #fcc;
        width: 200px;
        line-height: 100px;
        text-align:center;
        margin: 100px;
    }
</style>
<body>
    <p>Haha</p>
    <p>Hehe</p>
    
    /*修改后*/
    <div class='wrap'>
        <p>Hehe</p>
    </div>
</body>

修改后:另第二个p标签处在另外一个bfc中

三. css清除浮动

1. 概念

在非IE浏览器(如Firefox)下,==当容器的高度为auto,且容器的内容中有浮动(float为left或right)的元素==,在这种情况下,容器的高度不能自动伸长以适应内容的高度,使得内容溢出到容器外面而影响(甚至破坏)布局的现象。这个现象叫==浮动溢出==,为了防止这个现象的出现而进行的CSS处理,就叫CSS清除浮动。

2. 清除浮动的方法

  • 利用clear属性(1 2 3)
  • 利用bfc元素高度计算的特性(4 5)
.news {
  background-color: gray;
  border: solid 1px black;
  }
.news img {
  float: left;
  }
.news p {
  float: right;
  }
<div class="news">
<img src="news-pic.jpg" />
<p>some text</p>
</div>

(1) 使用带clear属性的空元素: 给news增加一个子元素

.clear{
    clear: both
}
<div class="clear"></div>

其中,clear属性为不允许元素的某一方位(left、right、both)有浮动元素出现。
优点:简单,代码少,浏览器兼容性好。
缺点:需要添加大量无语义的html元素,代码不够优雅,后期不容易维护。

(2) 使用邻接元素处理:给浮动元素本来的非浮动兄弟节点增加clear属性(原理同1)

.news {
  background-color: gray;
  border: solid 1px black;
  }
.news img {
  float: left;
  }
.news p {
  float: right;
  }
.content{
  clear:both;
  }
<div class="news">
    <img src="news-pic.jpg" />
    <p>some text</p>
    <div class="content">***</div> /*浮动元素的非浮动兄弟节点*/
</div>

(3) 使用CSS的:after伪元素:原理也是利用clear属性,只不过是利用伪元素增加一个隐藏的节点

.news {
  background-color: gray;
  border: solid 1px black;
  }
.news img {
  float: left;
  }
.news p {
  float: right;
  }
  
.clearfix:after{
  content: "020";
  display: block;
  height: 0;//是否能省略高度的设置
  clear: both;
  visibility: hidden;  
  }
  
<div class="news clearfix">
    <img src="news-pic.jpg" />
    <p>some text</p>
</div>

(4) 使用CSS的overflow属性:利用bfc的高度计算要包含浮动元素的特性

.news{
    overflow: hidden;
}

(5) 给浮动的元素的容器添加浮动:原理同(4)
但是这样会使其整体浮动,影响布局,不推荐使用。

四. css的伪元素和伪类

  1. 伪类操作文档树中已有的元素;伪元素操作文档树中没有的元素
  2. 伪类用:表示;伪元素用::表示

五. css的样式优先级

  • 引入css样式的方式
    行内式 > 嵌入式 > 链接式 (还有导入式@import(url(demo.css)))

  • 不同级别:
    !important > 行内样式 > id选择器 > 类选择器 > 标签选择器 > 通配符 > 继承 > 浏览器默认属性

  • 同一级别:后写的样式会覆盖先写的样式

六. css中transition和animation的属性都有哪些?

1. transition

  • transition-property: 指定应用过渡属性的名称(none、add、属性名称)
  • transition-duration:以秒或毫秒为单位指定过渡动画所需的时间。默认值为 0s ,表示不出现过渡动画。
  • transition-timing-function:会建立一条加速度曲线,因此在整个transition变化过程中,变化速度可以不断改变。值是timeing-function(ease-in ease-out等)
  • transition-delay:规定了在过渡效果开始作用之前需要等待的时间

2. animation

  • animation-delay: 设置延时,即从元素加载完成之后到动画序列开始执行的这段时间
  • animation-direction:
    设置动画在每次运行完后是反向运行还是重新回到开始位置重复运行。
  • animation-duration:
    设置动画一个周期的时长。
  • animation-iteration-count:
    设置动画重复次数,可以指定infinite无限次重复动画
  • animation-name:
    指定由@Keyframes描述的关键帧名称
  • animation-play-state:
    允许暂停和恢复动画。
  • animation-timing-function:
    设置动画速度,即通过建立加速度曲线,设置动画在关键帧之间是如何变化。
  • animation-fill-mode:
    指定动画执行前后如何为目标元素应用样式

七. css性能优化

参考:https://juejin.im/post/5b6133a351882519d346853f

  1. 内联首屏关键CSS(Critical CSS)
  • 内联CSS能够使浏览器开始页面渲染的时间提前。
    (css的加载不阻塞dom的构建,但是阻塞dom的渲染,因为需要等到cssom构建好后才能渲染)
  • 因为初始拥塞窗口存在限制,所以内联CSS后的文件大小不能超过14.6k,
  • ( 内联css不能使用缓存)
  1. 异步加载CSS(非内联首屏关键CSS可使用异步加载的方式)
  • 第一种方式是使用JavaScript动态创建样式表link元素,并插入到DOM中。
  • 第二种方式是将link元素的media属性设置为用户浏览器不匹配的媒体类型(对浏览器来说,如果样式表不适用于当前媒体类型,其优先级会被放低,会在不阻塞页面渲染的情况下再进行下载。)
  • 我们还可以通过rel属性将link元素标记为alternate可选样式表,也能实现浏览器异步加载。
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
  • ,rel="preload"5这一Web标准指出了如何异步加载资源,包括CSS类资源。
<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">
  1. 去除无用CSS
  2. 有选择地使用选择器
  • 保持简单,不要使用嵌套过多过于复杂的选择器
  • 通配符和属性选择器效率最低,需要匹配的元素最多,尽量避免使用。
  • 不要使用类选择器和ID选择器修饰元素标签,如h3#markdown-content,这样多此一举,还会降低效率。
  • 不要为了追求速度而放弃可读性与可维护性。
  1. 减少使用昂贵的属性
    在浏览器绘制屏幕时,所有需要浏览器进行操作或计算的属性相对而言都需要花费更大的代价。如box-shadow/border-radius/filter/透明度/:nth-child等。
  2. 优化重排与重绘
  • 减少重排(避免以下操作。优先使用flex)
    • 改变font-size和font-family
    • 改变元素的内外边距
    • 通过JS改变CSS类
    • 通过JS获取DOM元素的位置相关属性(如width/height/left等)
    • 滚动滚动条或者改变窗口大小
  • 减少重绘
    • 浏览器对此做了优化,它会将多次的重排、重绘操作合并为一次执行。
  1. 不要使用@import(使用link标签就行了)
  • 使用@import引入CSS会影响浏览器的并行下载。使用@import引用的CSS文件只有在引用它的那个css文件被下载、解析之后,浏览器才会知道还有另外一个css需要下载
  • 其次,多个@import会导致下载顺序紊乱。

八. 为什么CSS选择器是从右向左匹配的?

举例:div > div p em(不匹配时效率高)

  • 如果是从左到右:首先就要检查当前元素到html的整条路径,找到最上层的 div,再往下找,如果遇到不匹配就必须回到最上层那个 div,往下超找div去匹配选择器中的第一个div,回溯若干次才能确定匹配与否,效率很低。
  • 如果是从右到左,那么如果确认当前元素不匹配em时,一步就能排除了。只有在匹配时,才会不断向上找父节点进行验证

九. 隐藏页面中的某个元素的方法有哪些?

  • 完全隐藏:元素从渲染树中消失,不占据空间。
  • 视觉上的隐藏:屏幕中不可见,占据空间。
  • 语义上的隐藏:读屏软件不可读,但正常占据空。
  1. 完全隐藏(2)
  • display:none
  • hidden 属性
<div hidden></div>
  1. 视觉上的隐藏(7)
  • 利用 position 和 盒模型 将元素移出可视区范围
    • 设置 posoition 为 absolute 或 fixed,通过设置 top、left 等值,将其移出可视区域。
    position:absolute;
    left: -99999px;
    
    • 设置 position 为 relative,通过设置 top、left 等值,将其移出可视区域。
    position: relative;
    left: -99999px;
    height: 0
    
    • 设置 margin 值,将其移出可视区域范围(可视区域占位)。
    margin-left: -99999px;
    height: 0;
    
  • 利用 transfrom
    • 缩放
    transform: scale(0);
    height: 0;
    
    • 移动
    transform: translateX(-99999px);
    height: 0
    
    • 旋转
    transform: rotateY(90deg);
    
  • 设置其大小为0
    • 宽高为0,字体大小为0:
    • 宽高为0,超出隐藏:
    height: 0;
    width: 0;
    overflow: hidden;
    
  • 设置透明度为0:opacity: 0;
  • visibility属性:visibility: hidden;
  • 层级覆盖,z-index 属性
  • clip-path 裁剪
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
  1. 语义上的隐藏(1)
  • 读屏软件不可读,占据空间,可见。
<div aria-hidden="true">
</div>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.