Fork me on GitHub
艺术码畜的生活瞬间

前端面试大全ES6

前端面试题大全(ES6)

前端面试题类目分类

  • HTML5 + CSS3
  • JavaScript
  • Vue + Vue3
  • React
  • Webpack
  • 服务端

考点频率 :♥︎ 、 ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎ ♥︎

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ var、let、const之间的区别

一、var

在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量

注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象

var a = 10;
console.log(window.a) // 10

使用var声明的变量存在变量提升的情况

console.log(a) // undefined
var a = 20

在编译阶段,编译器会将其变成以下执行

var a
console.log(a)
a = 20

使用var,我们能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明

var a = 20 
var a = 30
console.log(a) // 30

在函数中使用使用var声明变量时候,该变量是局部的

var a = 20
function change(){
    var a = 30
}
change()
console.log(a) // 20 

而如果在函数内不使用var,该变量是全局的

var a = 20
function change(){
   a = 30
}
change()
console.log(a) // 30 

二、let

letES6新增的命令,用来声明变量

用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效

{
    let a = 20
}
console.log(a) // ReferenceError: a is not defined.

不存在变量提升

console.log(a) // 报错ReferenceError
let a = 2

这表示在声明它之前,变量a是不存在的,这时如果用到它,就会抛出一个错误

只要块级作用域内存在let命令,这个区域就不再受外部影响

var a = 123
if (true) {
    a = 'abc' // ReferenceError
    let a;
}

使用let声明变量前,该变量都不可用,也就是大家常说的“暂时性死区”

最后,let不允许在相同作用域中重复声明

let a = 20
let a = 30
// Uncaught SyntaxError: Identifier 'a' has already been declared

注意的是相同作用域,下面这种情况是不会报错的

let a = 20
{
    let a = 30
}

因此,我们不能在函数内部重新声明参数

function func(arg) {
  let arg;
}
func()
// Uncaught SyntaxError: Identifier 'arg' has already been declared

三、const

const声明一个只读的常量,一旦声明,常量的值就不能改变

const a = 1
a = 3
// TypeError: Assignment to constant variable.

这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值

const a;
// SyntaxError: Missing initializer in const declaration

如果之前用varlet声明过变量,再用const声明同样会报错

var a = 20
let b = 20
const a = 30
const b = 30
// 都会报错

const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动

对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量

对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

其它情况,constlet一致

四、区别

varletconst三者区别可以围绕下面五点展开:

  • 变量提升
  • 暂时性死区
  • 块级作用域
  • 重复声明
  • 修改声明的变量
  • 使用
变量提升
var `声明的变量存在变量提升,即变量可以在声明之前调用,值为`undefined

letconst不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错

// var
console.log(a)  // undefined
var a = 10

// let 
console.log(b)  // Cannot access 'b' before initialization
let b = 10

// const
console.log(c)  // Cannot access 'c' before initialization
const c = 10
暂时性死区

var不存在暂时性死区

letconst存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量

// var
console.log(a)  // undefined
var a = 10

// let
console.log(b)  // Cannot access 'b' before initialization
let b = 10

// const
console.log(c)  // Cannot access 'c' before initialization
const c = 10
块级作用域

var不存在块级作用域

letconst存在块级作用域

// var
{
    var a = 20
}
console.log(a)  // 20

// let
{
    let b = 20
}
console.log(b)  // Uncaught ReferenceError: b is not defined

// const
{
    const c = 20
}
console.log(c)  // Uncaught ReferenceError: c is not defined
重复声明

var允许重复声明变量

letconst在同一作用域不允许重复声明变量

// var
var a = 10
var a = 20 // 20

// let
let b = 10
let b = 20 // Identifier 'b' has already been declared

// const
const c = 10
const c = 20 // Identifier 'c' has already been declared
修改声明的变量

varlet可以

const声明一个只读的常量。一旦声明,常量的值就不能改变

// var
var a = 10
a = 20
console.log(a)  // 20

//let
let b = 10
b = 20
console.log(b)  // 20

// const
const c = 10
c = 20
console.log(c) // Uncaught TypeError: Assignment to constant variable
使用

能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ ES6中数组新增了哪些扩展?

一、扩展运算符的应用

ES6通过扩展元素符...,好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

主要用于函数调用的时候,将一个数组变为参数序列

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

const numbers = [4, 38];
add(...numbers) // 42

可以将某些数据结构转为数组

[...document.querySelectorAll('div')]

能够更简单实现数组复制

const a1 = [1, 2];
const [...a2] = a1;
// [1,2]

数组的合并也更为简洁了

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

注意:通过扩展运算符实现的是浅拷贝,修改了引用指向的值,会同步反映到新数组

下面看个例子就清楚多了

const arr1 = ['a', 'b',[1,2]];
const arr2 = ['c'];
const arr3  = [...arr1,...arr2]
arr1[2][0] = 9999 // 修改arr1里面数组成员值
console.log(arr3) // 影响到arr3,['a','b',[9999,2],'c']

扩展运算符可以与解构赋值结合起来,用于生成数组

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest  // []

const [first, ...rest] = ["foo"];
first  // "foo"
rest   // []

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错

const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错

const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 报错

可以将字符串转为真正的数组

[...'hello']
// [ "h", "e", "l", "l", "o" ]

定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错

const obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object

二、构造函数新增的方法

关于构造函数,数组新增的方法有如下:

  • Array.from()
  • Array.of()
Array.from()

将两类对象转为真正的数组:类似数组的对象和可遍历(iterable)的对象(包括 ES6 新增的数据结构 SetMap

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

还可以接受第二个参数,用来对每个元素进行处理,将处理后的值放入返回的数组

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
Array.of()

用于将一组值,转换为数组

Array.of(3, 11, 8) // [3,11,8]

没有参数的时候,返回一个空数组

当参数只有一个的时候,实际上是指定数组的长度

参数个数不少于 2 个时,Array()才会返回由参数组成的新数组

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

三、实例对象新增的方法

关于数组实例对象新增的方法有如下:

  • copyWithin()
  • find()、findIndex()
  • fill()
  • entries(),keys(),values()
  • includes()
  • flat(),flatMap()
copyWithin()

将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组

参数如下:

  • target(必需):从该位置开始替换数据。如果为负值,表示倒数。
  • start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
  • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
[1, 2, 3, 4, 5].copyWithin(0, 3) // 将从 3 号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2
// [4, 5, 3, 4, 5] 
find()、findIndex()

find()用于找出第一个符合条件的数组成员

参数是一个回调函数,接受三个参数依次为当前的值、当前的位置和原数组

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10
findIndex`返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回`-1
[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26
fill()

使用给定值,填充一个数组

['a', 'b', 'c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]

还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

注意,如果填充的类型为对象,则是浅拷贝

entries(),keys(),values()

keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
includes()

用于判断数组是否包含给定的值

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

方法的第二个参数表示搜索的起始位置,默认为0

参数为负数则表示倒数的位置

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true
flat(),flatMap()

将数组扁平化处理,返回一个新数组,对原数据没有影响

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]

flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1

[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]

[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]

flatMap()方法对原数组的每个成员执行一个函数相当于执行Array.prototype.map(),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组

// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
flatMap()`方法还可以有第二个参数,用来绑定遍历函数里面的`this

四、数组的空位

数组的空位指,数组的某一个位置没有任何值

ES6 则是明确将空位转为undefined,包括Array.from、扩展运算符、copyWithin()fill()entries()keys()values()find()findIndex()

建议大家在日常书写中,避免出现空位

五、排序稳定性

sort()默认设置为稳定的排序算法

const arr = [
  'peach',
  'straw',
  'apple',
  'spork'
];

const stableSorting = (s1, s2) => {
  if (s1[0] < s2[0]) return -1;
  return 1;
};

arr.sort(stableSorting)
// ["apple", "peach", "straw", "spork"]

排序结果中,strawspork的前面,跟原始顺序一致

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ ES6中对象新增了哪些扩展?

一、属性的简写

ES6中,当对象键名与对应值名相等的时候,可以进行简写

const baz = {foo:foo}

// 等同于
const baz = {foo}

方法也能够进行简写

const o = {
  method() {
    return "Hello!";
  }
};

// 等同于

const o = {
  method: function() {
    return "Hello!";
  }
}

在函数内作为返回值,也会变得方便很多

function getPoint() {
  const x = 1;
  const y = 10;
  return {x, y};
}

getPoint()
// {x:1, y:10}

注意:简写的对象方法不能用作构造函数,否则会报错

const obj = {
  f() {
    this.foo = 'bar';
  }
};

new obj.f() // 报错

二、属性名表达式

ES6 允许字面量定义对象时,将表达式放在括号内

let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"

表达式还可以用于定义方法名

let obj = {
  ['h' + 'ello']() {
    return 'hi';
  }
};

obj.hello() // hi

注意,属性名表达式与简洁表示法,不能同时使用,会报错

// 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };

// 正确
const foo = 'bar';
const baz = { [foo]: 'abc'};

注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]

const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

三、super关键字

this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto); // 为obj设置原型对象
obj.find() // "hello"

四、扩展运算符的应用

在解构赋值中,未被读取的可遍历的属性,分配到指定的对象上面

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

注意:解构赋值必须是最后一个参数,否则会报错

解构赋值是浅拷贝

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2; // 修改obj里面a属性中键值
x.a.b // 2,影响到了结构出来x的值

对象的扩展运算符等同于使用Object.assign()方法

五、属性的遍历

ES6 一共有 5 种方法可以遍历对象的属性。

  • for…in:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
  • Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名
  • Object.getOwnPropertyNames(obj):回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
  • Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有 Symbol 属性的键名
  • Reflect.ownKeys(obj):返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举

上述遍历,都遵守同样的属性遍历的次序规则:

  • 首先遍历所有数值键,按照数值升序排列
  • 其次遍历所有字符串键,按照加入时间升序排列
  • 最后遍历所有 Symbol 键,按照加入时间升序排
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

六、对象新增的方法

关于对象新增的方法,分别有以下:

  • Object.is()
  • Object.assign()
  • Object.getOwnPropertyDescriptors()
  • Object.setPrototypeOf(),Object.getPrototypeOf()
  • Object.keys(),Object.values(),Object.entries()
  • Object.fromEntries()
Object.is()

严格判断两个值是否相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.assign()
Object.assign()`方法用于对象的合并,将源对象`source`的所有可枚举属性,复制到目标对象`target

Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

注意:Object.assign()方法是浅拷贝,遇到同名属性会进行替换

Object.getOwnPropertyDescriptors()

返回指定对象所有自身属性(非继承属性)的描述对象

const obj = {
  foo: 123,
  get bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: get bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }
Object.setPrototypeOf()

Object.setPrototypeOf方法用来设置一个对象的原型对象

Object.setPrototypeOf(object, prototype)

// 用法
const o = Object.setPrototypeOf({}, null);
Object.getPrototypeOf()

用于读取一个对象的原型对象

Object.getPrototypeOf(obj);
Object.keys()

返回自身的(不含继承的)所有可遍历(enumerable)属性的键名的数组

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]
Object.values()

返回自身的(不含继承的)所有可遍历(enumerable)属性的键对应值的数组

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
Object.entries()

返回一个对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对的数组

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
Object.fromEntries()

用于将一个键值对数组转为对象

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ ES6中函数新增了哪些扩展?

一、参数

ES6允许为函数的参数设置默认值

function log(x, y = 'World') {
  console.log(x, y);
}

console.log('Hello') // Hello World
console.log('Hello', 'China') // Hello China
console.log('Hello', '') // Hello

函数的形参是默认声明的,不能使用letconst再次声明

function foo(x = 5) {
    let x = 1; // error
    const x = 2; // error
}

参数默认值可以与解构赋值的默认值结合起来使用

function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

上面的foo函数,当参数为对象的时候才能进行解构,如果没有提供参数的时候,变量xy就不会生成,从而报错,这里设置默认值避免

function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5

参数默认值应该是函数的尾参数,如果不是非尾部的参数设置默认值,实际上这个参数是没发省略的

function f(x = 1, y) {
  return [x, y];
}

f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错
f(undefined, 1) // [1, 1]

二、属性

函数的length属性

length将返回没有指定默认值的参数个数

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

rest 参数也不会计入length属性

(function(...args) {}).length // 0

如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
name属性

返回该函数的函数名

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

如果将一个具名函数赋值给一个变量,则 name属性都返回这个具名函数原本的名字

const bar = function baz() {};
bar.name // "baz"
Function`构造函数返回的函数实例,`name`属性的值为`anonymous
(new Function).name // "anonymous"

bind返回的函数,name属性值会加上bound前缀

function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

三、作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域

等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的

下面例子中,y=x会形成一个单独作用域,x没有被定义,所以指向全局变量x

let x = 1;

function f(y = x) { 
  // 等同于 let y = x  
  let x = 2; 
  console.log(y);
}

f() // 1

四、严格模式

只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错

// 报错
function doSomething(a, b = a) {
  'use strict';
  // code
}

// 报错
const doSomething = function ({a, b}) {
  'use strict';
  // code
};

// 报错
const doSomething = (...a) => {
  'use strict';
  // code
};

const obj = {
  // 报错
  doSomething({a, b}) {
    'use strict';
    // code
  }
};

五、箭头函数

使用“箭头”(=>)定义函数

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回

var sum = (num1, num2) => { return num1 + num2; }

如果返回对象,需要加括号将对象包裹

let getTempItem = id => ({ id: id, name: "Temp" });

注意点:

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ ES6中新增的Set、Map两种数据结构怎么理解?

Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构

什么是集合?什么又是字典?

  • 集合
    是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合
  • 字典
    是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同

区别?

  • 共同点:集合、字典都可以存储不重复的值
  • 不同点:集合是以[值,值]的形式存储元素,字典是以[键,值]的形式存储

一、Set

Setes6新增的数据结构,类似于数组,但是成员的值都是唯一的,没有重复的值,我们一般称为集合

Set本身是一个构造函数,用来生成 Set 数据结构

const s = new Set();
增删改查

Set的实例关于增删改查的方法:

  • add()
  • delete()
  • has()
  • clear()
add()

添加某个值,返回 Set 结构本身

当添加实例中已经存在的元素,set不会进行处理添加

s.add(1).add(2).add(2); // 2只被添加了一次
delete()

删除某个值,返回一个布尔值,表示删除是否成功

s.delete(1)
has()

返回一个布尔值,判断该值是否为Set的成员

s.has(2)
clear()

清除所有成员,没有返回值

s.clear()
遍历

Set实例遍历的方法有如下:

关于遍历的方法,有如下:

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

Set的遍历顺序就是插入顺序

keys方法、values方法、entries方法返回的都是遍历器对象

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
forEach()`用于对每个成员执行某种操作,没有返回值,键值、键名都相等,同样的`forEach`方法有第二个参数,用于绑定处理函数的`this
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

扩展运算符和 Set 结构相结合实现数组或字符串去重

// 数组
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)]; // [3, 5, 2]

// 字符串
let str = "352255";
let unique = [...new Set(str)].join(""); // '352'

实现并集、交集、和差集

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

二、Map

Map类型是键值对的有序列表,而键和值都可以是任意类型

Map本身是一个构造函数,用来生成 Map 数据结构

const m = new Map()
增删改查

Map 结构的实例针对增删改查有以下属性和操作方法:

  • size 属性
  • set()
  • get()
  • has()
  • delete()
  • clear()
size

size属性返回 Map 结构的成员总数。

const map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2
set()

设置键名key对应的键值为value,然后返回整个 Map 结构

如果key已经有值,则键值会被更新,否则就新生成该键

同时返回的是当前Map对象,可采用链式写法

const m = new Map();

m.set('edition', 6)        // 键是字符串
m.set(262, 'standard')     // 键是数值
m.set(undefined, 'nah')    // 键是 undefined
m.set(1, 'a').set(2, 'b').set(3, 'c') // 链式操作
get()
get`方法读取`key`对应的键值,如果找不到`key`,返回`undefined
const m = new Map();

const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数

m.get(hello)  // Hello ES6!
has()

has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中

const m = new Map();

m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');

m.has('edition')     // true
m.has('years')       // false
m.has(262)           // true
m.has(undefined)     // true
delete()
delete`方法删除某个键,返回`true`。如果删除失败,返回`false
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined)     // true

m.delete(undefined)
m.has(undefined)       // false
clear()

clear方法清除所有成员,没有返回值

let map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2
map.clear()
map.size // 0
遍历

Map 结构原生提供三个遍历器生成函数和一个遍历方法:

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回所有成员的遍历器
  • forEach():遍历 Map 的所有成员

遍历顺序就是插入顺序

const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});

三、WeakSet 和 WeakMap

WeakSet

创建WeakSet实例

const ws = new WeakSet();

WeakSet 可以接受一个具有 Iterable 接口的对象作为参数

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}

APIWeakSetSet有两个区别:

  • 没有遍历操作的API
  • 没有size属性

WeackSet只能成员只能是引用类型,而不能是其他类型的值

let ws=new WeakSet();

// 成员不是引用类型
let weakSet=new WeakSet([2,3]);
console.log(weakSet) // 报错

// 成员为引用类型
let obj1={name:1}
let obj2={name:1}
let ws=new WeakSet([obj1,obj2]); 
console.log(ws) //WeakSet {{…}, {…}}

WeakSet 里面的引用只要在外部消失,它在 WeakSet 里面的引用就会自动消失

WeakMap

WeakMap结构与Map结构类似,也是用于生成键值对的集合

APIWeakMapMap有两个区别:

  • 没有遍历操作的API
  • 没有clear清空方法
// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap();
const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2

// WeakMap 也可以接受一个数组,
// 作为构造函数的参数
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"

WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名

const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key

WeakMap的键名所指向的对象,一旦不再需要,里面的键名对象和所对应的键值对会自动消失,不用手动删除引用

举个场景例子:

在网页的 DOM 元素上添加数据,就可以使用WeakMap结构,当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除

const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"

注意:WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用

下面代码中,键值obj会在WeakMap产生新的引用,当你修改obj不会影响到内部

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};

wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ 你是怎么理解ES6中 Promise的?使用场景?

一、介绍

Promise ,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大

在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('得到最终结果: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

阅读上面代码,是不是很难受,上述形成了经典的回调地狱

现在通过Promise的改写上面的代码

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback);

瞬间感受到promise解决异步操作的优点:

  • 链式操作减低了编码难度
  • 代码可读性明显增强

下面我们正式来认识promise

状态

promise对象仅有三种状态

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)
特点
  • 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
  • 一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果
流程

认真阅读下图,我们能够轻松了解promise整个流程

二、用法

Promise对象是一个构造函数,用来生成Promise实例

const promise = new Promise(function(resolve, reject) {});
Promise`构造函数接受一个函数作为参数,该函数的两个参数分别是`resolve`和`reject
  • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
  • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”
实例方法

Promise构建出来的实例存在以下方法:

  • then()
  • catch()
  • finally()
then()

then是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数

then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});
catch

catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止

getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 处理前面三个Promise产生的错误
});

一般来说,使用catch方法代替then()第二个参数

Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程

catch()方法之中,还能再抛出错误,通过后面catch方法捕获到

finally()

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
构造函数方法

Promise构造函数存在以下方法:

  • all()
  • race()
  • allSettled()
  • resolve()
  • reject()
  • try()
all()

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.all([p1, p2, p3]);

接受一个数组(迭代对象)作为参数,数组成员都应为Promise实例

实例p的状态由p1p2p3决定,分为两种:

  • 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数
  • 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

如果p2没有自己的catch方法,就会调用Promise.all()catch方法

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
race()

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.race([p1, p2, p3]);

只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变

率先改变的 Promise 实例的返回值则传递给p的回调函数

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log)
.catch(console.error);
allSettled()

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例

只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束

const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];

await Promise.allSettled(promises);
removeLoadingIndicator();
resolve()

将现有对象转为 Promise 对象

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

参数可以分成四种情况,分别如下:

  • 参数是一个 Promise 实例,promise.resolve将不做任何修改、原封不动地返回这个实例
  • 参数是一个thenable对象,promise.resolve会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法
  • 参数不是具有then()方法的对象,或根本就不是对象,Promise.resolve()会返回一个新的 Promise 对象,状态为resolved
  • 没有参数时,直接返回一个resolved状态的 Promise 对象
reject()
Promise.reject(reason)`方法也会返回一个新的 Promise 实例,该实例的状态为`rejected
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了

Promise.reject()方法的参数,会原封不动地变成后续方法的参数

Promise.reject('出错了')
.catch(e => {
  console.log(e === '出错了')
})
// true

三、使用场景

将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};

通过链式操作,将多个渲染数据分别给个then,让其各司其职。或当下个异步请求依赖上个请求结果的时候,我们也能够通过链式操作友好解决问题

// 各司其职
getInfo().then(res=>{
    let { bannerList } = res
    //渲染轮播图
    console.log(bannerList)
    return res
}).then(res=>{
    
    let { storeList } = res
    //渲染店铺列表
    console.log(storeList)
    return res
}).then(res=>{
    let { categoryList } = res
    console.log(categoryList)
    //渲染分类列表
    return res
})

通过all()实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading即可

function initLoad(){
    // loading.show() //加载loading
    Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{
        console.log(res)
        loading.hide() //关闭loading
    }).catch(err=>{
        console.log(err)
        loading.hide()//关闭loading
    })
}
//数据初始化    
initLoad()

通过race可以设置图片请求超时

//请求某个图片资源
function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
           resolve(img);
        }
        //img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正确的
        img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";
    });
    return p;
}

//延时函数,用于给请求计时
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('图片请求超时');
        }, 5000);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ 怎么理解ES6中 Generator的?使用场景?

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同

回顾下上文提到的解决异步的手段:

  • 回调函数
  • promise

那么,上文我们提到promsie已经是一种比较流行的解决异步方案,那么为什么还出现Generator?甚至async/await呢?

该问题我们留在后面再进行分析,下面先认识下Generator

Generator函数

执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

形式上,Generator 函数是一个普通函数,但是有两个特征:

  • function关键字与函数名之间有一个星号
  • 函数体内部使用yield表达式,定义不同的内部状态
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

二、使用

Generator 函数会返回一个遍历器对象,即具有Symbol.iterator属性,并且返回给自己

function* gen(){
  // some code
}

var g = gen();

g[Symbol.iterator]() === g
// true

通过yield关键字可以暂停generator函数返回的遍历器对象的状态

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloWorldGenerator();

上述存在三个状态:helloworldreturn

通过next方法才会遍历到下一个内部状态,其运行逻辑如下:

  • 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
  • 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
  • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  • 如果该函数没有return语句,则返回的对象的value属性值为undefined
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

done用来判断是否存在下个状态,value对应状态值

yield`表达式本身没有返回值,或者说总是返回`undefined

通过调用next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

正因为Generator 函数返回Iterator对象,因此我们还可以通过for...of进行遍历

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5

原生对象没有遍历接口,通过Generator 函数为它加上这个接口,就能使用for...of进行遍历了

function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);

  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

三、异步解决方案

回顾之前展开异步解决的方案:

  • 回调函数
  • Promise 对象
  • generator 函数
  • async/await

这里通过文件读取案例,将几种解决异步的方案进行一个比较:

回调函数

所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数

fs.readFile('/etc/fstab', function (err, data) {
  if (err) throw err;
  console.log(data);
  fs.readFile('/etc/shells', function (err, data) {
    if (err) throw err;
    console.log(data);
  });
});

readFile函数的第三个参数,就是回调函数,等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行

Promise

Promise就是为了解决回调地狱而产生的,将回调函数的嵌套,改成链式调用

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};


readFile('/etc/fstab').then(data =>{
    console.log(data)
    return readFile('/etc/shells')
}).then(data => {
    console.log(data)
})

这种链式操作形式,使异步任务的两段执行更清楚了,但是也存在了很明显的问题,代码变得冗杂了,语义化并不强

generator

yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
async/await

将上面Generator函数改成async/await形式,更为简洁,语义化更强了

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
区别:

通过上述代码进行分析,将promiseGeneratorasync/await进行比较:

  • promiseasync/await是专门用于处理异步操作的
  • Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口…)
  • promise编写代码相比Generatorasync更为复杂化,且可读性也稍差
  • Generatorasync需要与promise对象搭配处理异步情况
  • async实质是Generator的语法糖,相当于会自动执行Generator函数
  • async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案

四、使用场景

Generator是异步解决的一种方案,最大特点则是将异步操作同步化表达出来

function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()

// 卸载UI
loader.next()

包括redux-saga 中间件也充分利用了Generator特性

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'

function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

function* mySaga() {
  yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

function* mySaga() {
  yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}

export default mySaga;

还能利用Generator函数,在对象上实现Iterator接口

function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ 怎么理解ES6中Proxy的?使用场景?

定义: 用于定义基本操作的自定义行为

本质: 修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(meta programming)

元编程(Metaprogramming,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作

一段代码来理解

#!/bin/bash
# metaprogram
echo '#!/bin/bash' >program
for ((I=1; I<=1024; I++)) do
    echo "echo $I" >>program
done
chmod +x program

这段程序每执行一次能帮我们生成一个名为program的文件,文件内容为1024行echo,如果我们手动来写1024行代码,效率显然低效

  • 元编程优点:与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译

Proxy 亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

二、用法

Proxy为 构造函数,用来生成 Proxy 实例

var proxy = new Proxy(target, handler)

参数

target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))

handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为

handler解析

关于handler拦截属性,有如下:

  • get(target,propKey,receiver):拦截对象属性的读取
  • set(target,propKey,value,receiver):拦截对象属性的设置
  • has(target,propKey):拦截propKey in proxy的操作,返回一个布尔值
  • deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
  • ownKeys(target):拦截Object.keys(proxy)for...in等循环,返回一个数组
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),返回一个布尔值
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作

Reflect

若需要在Proxy内部调用对象的默认行为,建议使用Reflect,其是ES6中操作对象而提供的新 API

基本特点:

  • 只要Proxy对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在
  • 修改某些Object方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false
  • Object操作都变成函数行为

下面我们介绍proxy几种用法:

get()

get接受三个参数,依次为目标对象、属性名和 proxy 实例本身,最后一个参数可选

var person = {
  name: "张三"
};

var proxy = new Proxy(person, {
  get: function(target, propKey) {
    return Reflect.get(target,propKey)
  }
});

proxy.name // "张三"

get能够对数组增删改查进行拦截,下面是试下你数组读取负数的索引

function createArray(...elements) {
  let handler = {
    get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
        propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver);
    }
  };

  let target = [];
  target.push(...elements);
  return new Proxy(target, handler);
}

let arr = createArray('a', 'b', 'c');
arr[-1] // c

注意:如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则会报错

const target = Object.defineProperties({}, {
  foo: {
    value: 123,
    writable: false,
    configurable: false
  },
});

const handler = {
  get(target, propKey) {
    return 'abc';
  }
};

const proxy = new Proxy(target, handler);

proxy.foo
// TypeError: Invariant check failed
set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身

假定Person对象有一个age属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy保证age的属性值符合要求

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // 对于满足条件的 age 属性以及其他属性,直接保存
    obj[prop] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错

如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用

const obj = {};
Object.defineProperty(obj, 'foo', {
  value: 'bar',
  writable: false,
});

const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = 'baz';
  }
};

const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"

注意,严格模式下,set代理如果没有返回true,就会报错

'use strict';
const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
    // 无论有没有下面这一行,都会报错
    return false;
  }
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'
deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除

var handler = {
  deleteProperty (target, key) {
    invariant(key, 'delete');
    Reflect.deleteProperty(target,key)
    return true;
  }
};
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`无法删除私有属性`);
  }
}

var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: 无法删除私有属性

注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错

取消代理
Proxy.revocable(target, handler);

三、使用场景

Proxy其功能非常类似于设计模式中的代理模式,常用功能如下:

  • 拦截和监视外部对对象的访问
  • 降低函数或类的复杂度
  • 在复杂操作前对操作进行校验或对所需资源进行管理

使用 Proxy 保障数据类型的准确性

let numericDataStore = { count: 0, amount: 1234, total: 14 };
numericDataStore = new Proxy(numericDataStore, {
    set(target, key, value, proxy) {
        if (typeof value !== 'number') {
            throw Error("属性只能是number类型");
        }
        return Reflect.set(target, key, value, proxy);
    }
});

numericDataStore.count = "foo"
// Error: 属性只能是number类型

numericDataStore.count = 333
// 赋值成功

声明了一个私有的 apiKey,便于 api 这个对象内部的方法调用,但不希望从外部也能够访问 api._apiKey

let api = {
    _apiKey: '123abc456def',
    getUsers: function(){ },
    getUser: function(userId){ },
    setUser: function(userId, config){ }
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
    get(target, key, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} 不可访问.`);
        } return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} 不可修改`);
        } return Reflect.get(target, key, value, proxy);
    }
});

console.log(api._apiKey)
api._apiKey = '987654321'
// 上述都抛出错误

还能通过使用Proxy实现观察者模式

观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行

observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数

const queuedObservers = new Set();

const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});

function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  queuedObservers.forEach(observer => observer());
  return result;
}

观察者函数都放进Set集合,当修改obj的值,在会set函数中拦截,自动执行Set所有的观察者

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ 怎么理解ES6中Module的?使用场景?

一、介绍

模块,(Module),是能够单独命名并独立地完成一定功能的程序语句集合(即程序代码和数据结构的集合体)。

两个基本的特征:外部特征和内部

  • 外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能
  • 内部特征是指模块的内部环境具有的特点(即该模块的局部数据和程序代码)
为什么需要模块化
  • 代码抽象
  • 代码封装
  • 代码复用
  • 依赖管理

如果没有模块化,我们代码会怎样?

  • 变量和方法不容易维护,容易污染全局作用域
  • 加载资源的方式通过script标签从上到下。
  • 依赖的环境主观逻辑偏重,代码较多就会比较复杂。
  • 大型项目资源难以维护,特别是多人合作的情况下,资源的引入会让人奔溃

因此,需要一种将JavaScript程序模块化的机制,如

  • CommonJs (典型代表:node.js早期)
  • AMD (典型代表:require.js)
  • CMD (典型代表:sea.js)
AMD

Asynchronous ModuleDefinition(AMD),异步模块定义,采用异步方式加载模块。所有依赖模块的语句,都定义在一个回调函数中,等到模块加载完成之后,这个回调函数才会运行

代表库为require.js

/** main.js 入口文件/主模块 **/
// 首先用config()指定各模块路径和引用名
require.config({
  baseUrl: "js/lib",
  paths: {
    "jquery": "jquery.min",  //实际路径为js/lib/jquery.min.js
    "underscore": "underscore.min",
  }
});
// 执行基本操作
require(["jquery","underscore"],function($,_){
  // some code here
});
CommonJs

CommonJS 是一套 Javascript 模块规范,用于服务端

// a.js
module.exports={ foo , bar}

// b.js
const { foo,bar } = require('./a.js')

其有如下特点:

  • 所有代码都运行在模块作用域,不会污染全局作用域
  • 模块是同步加载的,即只有加载完成,才能执行后面的操作
  • 模块在首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存
  • require返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值

既然存在了AMD以及CommonJs机制,ES6Module又有什么不一样?

ES6 在语言标准的层面上,实现了Module,即模块功能,完全可以取代 CommonJS AMD 规范,成为浏览器和服务器通用的模块解决方案

CommonJS AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性

// CommonJS模块
let { stat, exists, readfile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

ES6设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量

// ES6模块
import { stat, exists, readFile } from 'fs';

上述代码,只加载3个方法,其他方法不加载,即 ES6 可以在编译时就完成模块加载

由于编译加载,使得静态分析成为可能。包括现在流行的typeScript也是依靠静态分析实现功能

二、使用

ES6模块内部自动采用了严格模式,这里就不展开严格模式的限制,毕竟这是ES5之前就已经规定好

模块功能主要由两个命令构成:

  • export:用于规定模块的对外接口
  • import:用于输入其他模块提供的功能
export

一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

或 
// 建议使用下面写法,这样能瞬间确定输出了哪些变量
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };

输出函数或类

export function multiply(x, y) {
  return x * y;
};

通过as可以进行输出变量的重命名

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
import

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块

// main.js
import { firstName, lastName, year } from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

同样如果想要输入变量起别名,通过as关键字

import { lastName as surname } from './profile.js';

当加载整个模块的时候,需要用到星号*

// circle.js
export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}

// main.js
import * as circle from './circle';
console.log(circle)   // {area:area,circumference:circumference}

输入的变量都是只读的,不允许修改,但是如果是对象,允许修改属性

import {a} from './xxx.js'

a.foo = 'hello'; // 合法操作
a = {}; // Syntax Error : 'a' is read-only;

不过建议即使能修改,但我们不建议。因为修改之后,我们很难差错

import后面我们常接着from关键字,from指定模块文件的位置,可以是相对路径,也可以是绝对路径

import { a } from './a';

如果只有一个模块名,需要有配置文件,告诉引擎模块的位置

import { myMethod } from 'util';

在编译阶段,import会提升到整个模块的头部,首先执行

foo();

import { foo } from 'my_module';

多次重复执行同样的导入,只会执行一次

import 'lodash';
import 'lodash';

上面的情况,大家都能看到用户在导入模块的时候,需要知道加载的变量名和函数,否则无法加载

如果不需要知道变量名或函数就完成加载,就要用到export default命令,为模块指定默认输出

// export-default.js
export default function () {
    console.log('foo');
}

加载该模块的时候,import命令可以为该函数指定任意名字

// import-default.js
import customName from './export-default';
customName(); // 'foo'

动态加载

允许您仅在需要时动态加载模块,而不必预先加载所有模块,这存在明显的性能优势

这个新功能允许您将import()作为函数调用,将其作为参数传递给模块的路径。 它返回一个 promise,它用一个模块对象来实现,让你可以访问该对象的导出

import('/modules/myModule.mjs')
  .then((module) => {
    // Do something with the module.
  });

复合写法

如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起

export { foo, bar } from 'my_module';

// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

同理能够搭配as*搭配使用

三、使用场景

如今,ES6模块化已经深入我们日常项目开发中,像vuereact项目搭建项目,组件化开发处处可见,其也是依赖模块化实现

vue组件

<template>
  <div class="App">
      组件化开发 ---- 模块化
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

react组件

function App() {
  return (
    <div className="App">
        组件化开发 ---- 模块化
    </div>
  );
}

export default App;

包括完成一些复杂应用的时候,我们也可以拆分成各个模块

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ 怎么理解ES6中 Decorator 的?使用场景?

一、介绍

Decorator,即装饰器,从名字上很容易让我们联想到装饰者模式

简单来讲,装饰者模式就是一种在不改变原类和使用继承的情况下,动态地扩展对象功能的设计理论。

ES6Decorator功能亦如此,其本质也不是什么高大上的结构,就是一个普通的函数,用于扩展类属性和类方法

这里定义一个士兵,这时候他什么装备都没有

class soldier{ 
}

定义一个得到 AK 装备的函数,即装饰器

function strong(target){
    target.AK = true
}

使用该装饰器对士兵进行增强

@strong
class soldier{
}

这时候士兵就有武器了

soldier.AK // true

上述代码虽然简单,但也能够清晰看到了使用Decorator两大优点:

  • 代码可读性变强了,装饰器命名相当于一个注释
  • 在不改变原有代码情况下,对原来功能进行扩展

二、用法

Docorator修饰对象为下面两种:

  • 类的装饰
  • 类属性的装饰
类的装饰

当对类本身进行装饰的时候,能够接受一个参数,即类本身

将装饰器行为进行分解,大家能够有个更深入的了解

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

下面@testable就是一个装饰器,target就是传入的类,即MyTestableClass,实现了为类添加静态属性

@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true

如果想要传递参数,可以在装饰器外层再封装一层函数

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false
类属性的装饰

当对类属性进行装饰的时候,能够接受三个参数:

  • 类的原型对象
  • 需要装饰的属性名
  • 装饰属性名的描述对象

首先定义一个readonly装饰器

function readonly(target, name, descriptor){
  descriptor.writable = false; // 将可写属性设为false
  return descriptor;
}

使用readonly装饰类的name方法

class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

相当于以下调用

readonly(Person.prototype, 'name', descriptor);

如果一个方法有多个装饰器,就像洋葱一样,先从外到内进入,再由内到外执行

function dec(id){
    console.log('evaluated', id);
    return (target, property, descriptor) =>console.log('executed', id);
}

class Example {
    @dec(1)
    @dec(2)
    method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1

外层装饰器@dec(1)先进入,但是内层装饰器@dec(2)先执行

注意

装饰器不能用于修饰函数,因为函数存在变量声明情况

var counter = 0;

var add = function () {
  counter++;
};

@add
function foo() {
}

编译阶段,变成下面

var counter;
var add;

@add
function foo() {
}

counter = 0;

add = function () {
  counter++;
};

意图是执行后counter等于 1,但是实际上结果是counter等于 0

三、使用场景

基于Decorator强大的作用,我们能够完成各种场景的需求,下面简单列举几种:

使用react-redux的时候,如果写成下面这种形式,既不雅观也很麻烦

class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

通过装饰器就变得简洁多了

@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}

mixins,也可以写成装饰器,让使用更为简洁了

function mixins(...list) {
  return function (target) {
    Object.assign(target.prototype, ...list);
  };
}

// 使用
const Foo = {
  foo() { console.log('foo') }
};

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() // "foo"

下面再讲讲core-decorators.js几个常见的装饰器

@antobind

autobind装饰器使得方法中的this对象,绑定原始对象

import { autobind } from 'core-decorators';

class Person {
  @autobind
  getPerson() {
    return this;
  }
}

let person = new Person();
let getPerson = person.getPerson;

getPerson() === person;
// true
@readonly

readonly装饰器使得属性或方法不可写

import { readonly } from 'core-decorators';

class Meal {
  @readonly
  entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salmon';
// Cannot assign to read only property 'entree' of [object Object]

@deprecate

deprecatedeprecated装饰器在控制台显示一条警告,表示该方法将废除

import { deprecate } from 'core-decorators';

class Person {
  @deprecate
  facepalm() {}

  @deprecate('功能废除了')
  facepalmHard() {}
}

let person = new Person();

person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
// DEPRECATION Person#facepalmHard: 功能废除了

♥︎ ♥︎ ♥︎ ♥︎ ♥︎ Symbol数据类型

ES6引入一种新的原始数据类型为 Symbol ,表示为 独一无二 的值,用来定义独一无二的对象属性名。

Symbol的讲解

4个方面说说Symbol数据类型:

  1. Symbol的定义;
  2. Symbol作为对象属性名;
  3. Symbol使用场景;
  4. Symbol获取。

Symbol的定义

  • 一种Symbol类型可以通过使用Symbol()函数来生成;
  • Symbol()函数可以接收一个字符串作为参数

示例代码:

let s1 = Symbol('web');
let s2 = Symbol('web');
console.log(s1 === s2);
console.log(typeof s1);
console.log(typeof s2);
复制代码

chrome截图:

img

由图可知:Symbol()函数接收的参数相同,其变量的值也不同,s1和s2是Symbol类型的变量,因为变量的值不同,所以打印的结果为false。使用typeof来获取相应的类型,所以打印的结果都为symbol。

Symbol作为对象属性名

Symbol可以通过三种方式作为对象属性名。

  • 第一种:

示例代码:

let symbol = Symbol();
let a = {};
a[symbol] = 'web';

由代码可知:首先声明了一个Symbol类型的变量symbol,一个空的对象为a,通过a[symbol]给a对象赋值一个web的字符串。表示symbol作为对象属性名,web作为它的属性值。

  • 第二种:

示例代码:

let symbol = Symbol();
let a = {
    [symbol]:'web'
};

由代码可知:首先声明了一个Symbol类型的变量symbol,接着在声明对象a的同时通过[symbol]给a对象性赋值为web的字符串。

  • 第三种:

示例代码:

let symbol = Symbol();
let a = {};
Object.defineProperty(a, symbol, {value: 'web'});

由代码可知:首先声明了一个Symbol类型的变量symbol,一个空对象为a,通过Object.defineProperty()方法给a对象赋值为web的字符串。

Symbol的值作为对象属性名,是不能用点运算符的。

Symbol使用场景

一种有两种使用场景:

  1. 因为Symbol的值是均不相等的,所以Symbol类型的值作为对象属性名,不会出现重复。
  2. 代码形成强耦合的某一个具体的字符串。

Symbol获取

通过Object.getOwnPropertySymbols()方法,可以获取指定对象的所有Symbols属性名。:

参考文献:

[语音仓库]: “https://github.com/febobo/web-interview

前端面试大全服务端

前端面试题大全(服务端)

前端面试题类目分类

  • HTML5 + CSS3
  • JavaScript
  • Vue + Vue3
  • React
  • Webpack
  • 服务端

考点频率 :♥︎ 、 ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎ ♥︎

服务端

♥︎ ♥︎ ♥︎ http状态码有哪些

http状态码的作用是服务器告诉客户端当前请求响应的状态,通过状态码就能判断和分析服务器的运行状态

状态码第一位数字决定了不同的响应状态,有如下:

  • 1 表示消息
  • 2 表示成功
  • 3 表示重定向
  • 4 表示请求错误
  • 5 表示服务器错误

1xx

代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束

常见的有:

  • 100(客户端继续发送请求,这是临时响应):这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应
  • 101:服务器根据客户端的请求切换协议,主要用于websocket或http2升级

2xx

代表请求已成功被服务器接收、理解、并接受

常见的有:

  • 200(成功):请求已成功,请求所希望的响应头或数据体将随此响应返回
  • 201(已创建):请求成功并且服务器创建了新的资源
  • 202(已创建):服务器已经接收请求,但尚未处理
  • 203(非授权信息):服务器已成功处理请求,但返回的信息可能来自另一来源
  • 204(无内容):服务器成功处理请求,但没有返回任何内容
  • 205(重置内容):服务器成功处理请求,但没有返回任何内容
  • 206(部分内容):服务器成功处理了部分请求

3xx

表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向

常见的有:

  • 300(多种选择):针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择
  • 301(永久移动):请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置
  • 302(临时移动): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
  • 303(查看其他位置):请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码
  • 305 (使用代理): 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理
  • 307 (临时重定向): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求

4xx

代表了客户端看起来可能发生了错误,妨碍了服务器的处理

常见的有:

  • 400(错误请求): 服务器不理解请求的语法
  • 401(未授权): 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
  • 403(禁止): 服务器拒绝请求
  • 404(未找到): 服务器找不到请求的网页
  • 405(方法禁用): 禁用请求中指定的方法
  • 406(不接受): 无法使用请求的内容特性响应请求的网页
  • 407(需要代理授权): 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理
  • 408(请求超时): 服务器等候请求时发生超时

5xx

表示服务器无法完成明显有效的请求。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生

常见的有:

  • 500(服务器内部错误):服务器遇到错误,无法完成请求
  • 501(尚未实施):服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码
  • 502(错误网关): 服务器作为网关或代理,从上游服务器收到无效响应
  • 503(服务不可用): 服务器目前无法使用(由于超载或停机维护)
  • 504(网关超时): 服务器作为网关或代理,但是没有及时从上游服务器收到请求
  • 505(HTTP 版本不受支持): 服务器不支持请求中所用的 HTTP 协议版本

下面给出一些状态码的适用场景:

  • 100:客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。常用于POST大数据传输
  • 206:一般用来做断点续传,或者是视频文件等大文件的加载
  • 301:永久重定向会缓存。新域名替换旧域名,旧的域名不再使用时,用户访问旧域名时用301就重定向到新的域名
  • 302:临时重定向不会缓存,常用 于未登陆的用户访问用户中心重定向到登录页面
  • 304:协商缓存,告诉客户端有缓存,直接使用缓存中的数据,返回页面的只有头部信息,是没有内容部分
  • 400:参数有误,请求无法被服务器识别
  • 403:告诉客户端进制访问该站点或者资源,如在外网环境下,然后访问只有内网IP才能访问的时候则返回
  • 404:服务器找不到资源时,或者服务器拒绝请求又不想说明理由时
  • 503:服务器停机维护时,主动用503响应请求或 nginx 设置限速,超过限速,会返回503
  • 504:网关超时

♥︎ ♥︎ ♥︎ 你知道哪些http首部字段?

HTTP头字段(HTTP header fields),是指在超文本传输协议(HTTP)的请求和响应消息中的消息头部分

字段名 说明 示例
Accept 能够接受的回应内容类型(Content-Types) Accept: text/plain
Accept-Charset 能够接受的字符集 Accept-Charset: utf-8
Accept-Encoding 能够接受的编码方式列表 Accept-Encoding: gzip, deflate
Accept-Language 能够接受的回应内容的自然语言列表 Accept-Language: en-US
Authorization 用于超文本传输协议的认证的认证信息 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control 用来指定在这次的请求/响应链中的所有缓存机制 都必须 遵守的指令 Cache-Control: no-cache
Connection 该浏览器想要优先使用的连接类型 Connection: keep-alive Connection: Upgrade
Cookie 服务器通过 Set- Cookie (下文详述)发送的一个 超文本传输协议Cookie Cookie: $Version=1; Skin=new;
Content-Length 以 八位字节数组 (8位的字节)表示的请求体的长度 Content-Length: 348
Content-Type 请求体的 多媒体类型 Content-Type: application/x-www-form-urlencoded
Date 发送该消息的日期和时间 Date: Tue, 15 Nov 1994 08:12:31 GMT
Expect 表明客户端要求服务器做出特定的行为 Expect: 100-continue
Host 服务器的域名(用于虚拟主机 ),以及服务器所监听的传输控制协议端口号 Host: en.wikipedia.org:80 Host: en.wikipedia.org
If-Match 仅当客户端提供的实体与服务器上对应的实体相匹配时,才进行对应的操作。主要作用时,用作像 PUT 这样的方法中,仅当从用户上次更新某个资源以来,该资源未被修改的情况下,才更新该资源 If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since 允许在对应的内容未被修改的情况下返回304未修改 If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
If-None-Match 允许在对应的内容未被修改的情况下返回304未修改 If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range 如果该实体未被修改过,则向我发送我所缺少的那一个或多个部分;否则,发送整个新的实体 If-Range: “737060cd8c284d8af7ad3082f209582d”
Range 仅请求某个实体的一部分 Range: bytes=500-999
User-Agent 浏览器的浏览器身份标识字符串 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0
Origin 发起一个针对 跨来源资源共享 的请求 Origin: http://www.example-social-network.com

♥︎ ♥︎ ♥︎ 为什么浏览器要限制TCP的最大个数

建立一个tcp连接需要:1,socket文件描述符;2,IP地址;3,端口;4,内存

1、内存资源: 一个tcp连接最小占用内存为4096+4096 = 8k, 那么对于一个8G内存的机器,在不考虑其他限制下, 最多支持的并发量为:810241024/8 约等于100万, 在实际中,由于linux kernel对一些资源的限制, 加上程序的业务处理,所以,8G内存是很难达到100万连接的

2、CPU资源

♥︎ ♥︎ ♥︎ 说说 HTTP1.0/1.1/2.0 的区别?

一、HTTP1.0

HTTP协议的第二个版本,第一个在通讯中指定版本号的HTTP协议版本

HTTP 1.0 浏览器与服务器只保持短暂的连接,每次请求都需要与服务器建立一个TCP连接

服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求

简单来讲,每次与服务器交互,都需要新开一个连接

例如,解析html文件,当发现文件中存在资源文件的时候,这时候又创建单独的链接

最终导致,一个html文件的访问包含了多次的请求和响应,每次请求都需要创建连接、关系连接

这种形式明显造成了性能上的缺陷

如果需要建立长连接,需要设置一个非标准的Connection字段 Connection: keep-alive

二、HTTP1.1

HTTP1.1中,默认支持长连接(Connection: keep-alive),即在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟

建立一次连接,多次请求均由这个连接完成

这样,在加载html文件的时候,文件中多个请求和响应就可以在一个连接中传输

同时,HTTP 1.1还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间

同时,HTTP1.1HTTP1.0的基础上,增加更多的请求头和响应头来完善的功能,如下:

  • 引入了更多的缓存控制策略,如If-Unmodified-Since, If-Match, If-None-Match等缓存头来控制缓存策略
  • 引入range,允许值请求资源某个部分
  • 引入host,实现了在一台WEB服务器上可以在同一个IP地址和端口号上使用不同的主机名来创建多个虚拟WEB站点

并且还添加了其他的请求方法:putdeleteoptions

三、HTTP2.0

HTTP2.0在相比之前版本,性能上有很大的提升,如添加了一个特性:

  • 多路复用
  • 二进制分帧
  • 首部压缩
  • 服务器推送
多路复用

HTTP/2 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了”队头堵塞”

上图中,可以看到第四步中cssjs资源是同时发送到服务端

二进制分帧

帧是HTTP2通信中最小单位信息

HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x 的文本格式,解析起来更高效

将请求和响应数据分割为更小的帧,并且它们采用二进制编码

HTTP2 中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流

每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装,这也是多路复用同时发送数据的实现条件

首部压缩

HTTP/2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送

首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新

例如:下图中的两个请求, 请求一发送了所有的头部字段,第二个请求则只需要发送差异数据,这样可以减少冗余数据,降低开销

服务器推送

HTTP2引入服务器推送,允许服务端推送资源给客户端

服务器会顺便把一些客户端需要的资源一起推送到客户端,如在响应一个页面请求中,就可以随同页面的其它资源

免得客户端再次创建连接发送请求到服务器端获取

这种方式非常合适加载静态资源

四、总结

HTTP1.0:

  • 浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接

HTTP1.1:

  • 引入了持久连接,即TCP连接默认不关闭,可以被多个请求复用
  • 在同一个TCP连接里面,客户端可以同时发送多个请求
  • 虽然允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的,服务器只有处理完一个请求,才会接着处理下一个请求。如果前面的处理特别慢,后面就会有许多请求排队等着
  • 新增了一些请求方法
  • 新增了一些请求头和响应头

HTTP2.0:

  • 采用二进制格式而非文本格式
  • 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行
  • 使用报头压缩,降低开销
  • 服务器推送

♥︎ ♥︎ ♥︎ HTTP2.0的特点

HTTP2.0大幅度的提高了web性能,在HTTP1.1完全语意兼容的基础上,进一步减少了网络的延迟。

  1. 二进制分帧
  2. 多路复用
  3. 首部压缩
  4. 流量控制
  5. 请求优先级
  6. 服务器推送

♥︎ ♥︎ ♥︎ 简述https原理,以及与http的区别

  1. HTTP协议工作在80端口,HTTPS协议工作在443端口
  2. HTTPS需要申请证书(用于验证服务器身份)
  3. HTTP在TCP三次握手建立连接之后即可开始传输数据;HTTPS协议则需要在建立TCP连接之后客户端与服务器在进行SSL加密,确定对话密钥,完成加密后才开始传输数据。
  4. HTTPS协议传输是密文,HTTP协议传输是明文

♥︎ ♥︎ ♥︎ CDN 是什么?描述下 CDN 原理?为什么要用 CDN?

CDN的全称是Content Delivery Network,即内容分发网络 共有云厂商在全世界各地都遍布不计其数都数据中心和服务器, CDN服务简单来讲就是这些厂商将你的服务器上面的文档分发到他们不同地区的服务器的当中, 每个地区可以称为一个节点,这样用户在访问你的网址时, 浏览器发送的请求就会优先绕去离客户最近的节点来获取数据, 这样方便客户更快的速度访问网站。 CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器, 通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容, 降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。

♥︎ ♥︎ ♥︎ DNS 查询的过程,分为哪两种,是怎么一个过程

1、分布域名解析:是指分在客户端上维护一个静态的文本文件,其中包含主机名和IP地址的映射。 随着网络规模的扩大,分布式分辨率的有效性越来越低。

2、集中式域名解析:要求网络中有多台DNS服务器,负责维护域名/IP地址映射数据库。 客户端从指定的服务器获取域名的地址信息。一旦客户端指定的DNS服务器不包含相应的数据, DNS服务器就会在网络中进行递归查询,并获取其他服务器上的地址信息。

♥︎ ♥︎ ♥︎ 强缓存和协商缓存的区别

强缓存(本地缓存):直接使用本地的缓存,不用跟服务器进行通信

相关header字段

expires:一个未来时间,代表请求有效期,没有过期之前都使用当前请求。

cache-control

no-cache:不使用本地缓存。向浏览器发送新鲜度校验请求

pubilc:任何情况下都缓存(即使是HTTP认证的资源)

private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存

no-store:禁止浏览器缓存数据,也禁止保存至临时文件中,每次都重新请求,多次设置 cache-control,优先级最高

协商缓存:将资源一些相关信息返回服务器,让服务器判断浏览器是否能直接使用本地缓存,整个过程至少与服务器通信一次

相关header字段

Last-Modified/If-Modified-Since(两个都是时间格式字符串)

浏览器第一次发请求,服务器在返回的 respone 的 header 加上 Last-Modified,表示资源的最后修改时间再次请求资源,在 requset 的 header 加上 If-Modified-Since ,值就是上一次请求返回的 Last-Modified 值

服务器根据请求传过来的值判断资源是否有变化,没有则返回 304,有变化就正常返回资源内容,更新 LastModified 的值

304 从缓存加载资源,否则直接从服务器加载资源

Etag/If-None-Match与 Last-Modified/If-Modified-Since 不同的是,返回 304 时,ETag 还是会重新生成返回至浏览器。

♥︎ ♥︎ ♥︎ 为什么from表单提交没有跨域问题,但ajax有跨域问题

浏览器的策略本质是:一个域名下面的JS,没有经过允许是不能读取另一个域名的内容,但是浏览器不阻止你向另外一个域名发送请求。 所以form表单提交没有跨域问题,提交form表单到另外一个域名,原来页面是无法获取新页面的内容,或者说form提交后不需要返回,但是ajax是需要返回的。 而ajax是想要读取响应内容,浏览器是不允许你这么做的。 浏览器的安全策略限制的是js脚本,并不限制src,form表单提交之类的请求, 就是说form表单提交不存在安全问题,ajax提交跨域存在安全问题。


♥︎ ♥︎ ♥︎ ♥︎ url从输入到渲染页面的全过程

浏览器构建HTTP Request请求, DNS解析URL地址、生成HTTP请求报文、构建TCP连接、使用IP协议选择传输路线

将请求通过网络传输到服务端

从客户机到服务器需要通过许多网络设备,一般包括集线器、交换器、路由器等

服务器构建HTTP Response响应,响应客户端的请求

将响应体的数据通过网络传输返回给客户端

浏览器渲染页面 解析HTML、CSS、JS,生成RenderTree渲染页面

♥︎ ♥︎ ♥︎ ♥︎ TCP为什么需要三次握手和四次挥手?

一、三次握手

三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包

主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备

过程如下:

  • 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c),此时客户端处于 SYN_SENT 状态
  • 第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,为了确认客户端的 SYN,将客户端的 ISN+1作为ACK的值,此时服务器处于 SYN_RCVD 的状态
  • 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,值为服务器的ISN+1。此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接

上述每一次握手的作用如下:

  • 第一次握手:客户端发送网络包,服务端收到了
    这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
  • 第二次握手:服务端发包,客户端收到了
    这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常
  • 第三次握手:客户端发包,服务端收到了。
    这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常

通过三次握手,就能确定双方的接收和发送能力是正常的。之后就可以正常通信了

为什么不是两次握手?

如果是两次握手,发送端可以确定自己发送的信息能对方能收到,也能确定对方发的包自己能收到,但接收端只能确定对方发的包自己能收到 无法确定自己发的包对方能收到

并且两次握手的话, 客户端有可能因为网络阻塞等原因会发送多个请求报文,延时到达的请求又会与服务器建立连接,浪费掉许多服务器的资源

二、四次挥手

tcp终止一个连接,需要经过四次挥手

过程如下:

  • 第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态,停止发送数据,等待服务端的确认
  • 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态
  • 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态
  • 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态

四次挥手原因

服务端在收到客户端断开连接Fin报文后,并不会立即关闭连接,而是先发送一个ACK包先告诉客户端收到关闭连接的请求,只有当服务器的所有报文发送完毕之后,才发送FIN报文断开连接,因此需要四次挥手

三、总结

一个完整的三次握手四次挥手如下图所示:

♥︎ ♥︎ ♥︎ ♥︎ 说一下http缓存策略,有什么区别,分别解决了什么问题?

浏览器每次发起请求时,先在本地缓存中查找结果以及缓存标识,根据缓存标识来判断是否使用本地缓存, 如果缓存有效,则使用本地缓存。

向服务器发起请求并携带缓存标识。根据是否需向服务器发起HTTP请求, 将缓存过程划分为两个部分:强制缓存和协商缓存,强缓优先于协商缓存。

强缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。

协商缓存,让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率, 将缓存信息中的Etag和Last-Modified,通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。

解决问题

  1. 减少了冗余的数据传输
  2. 减少了服务器的负担,大大提升了网站的性能
  3. 加快了客户端加载网页的速度

♥︎ ♥︎ ♥︎ ♥︎ 为什么说HTTPS比HTTP安全? HTTPS是如何保证安全的?

一、安全特性

在上篇文章中,我们了解到HTTP在通信过程中,存在以下问题:

  • 通信使用明文(不加密),内容可能被窃听
  • 不验证通信方的身份,因此有可能遭遇伪装

HTTPS的出现正是解决这些问题,HTTPS是建立在SSL之上,其安全性由SSL来保证

在采用SSL后,HTTP就拥有了HTTPS的加密、证书和完整性保护这些功能

SSL(Secure Sockets Layer 安全套接字协议),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议

二、如何做

SSL 的实现这些功能主要依赖于三种手段:

  • 对称加密:采用协商的密钥对数据加密
  • 非对称加密:实现身份认证和密钥协商
  • 摘要算法:验证信息的完整性
  • 数字签名:身份验证

对称加密

对称加密指的是加密和解密使用的秘钥都是同一个,是对称的。只要保证了密钥的安全,那整个通信过程就可以说具有了机密性

非对称加密

非对称加密,存在两个秘钥,一个叫公钥,一个叫私钥。两个秘钥是不同的,公钥可以公开给任何人使用,私钥则需要保密

公钥和私钥都可以用来加密解密,但公钥加密后只能用私钥解密,反过来,私钥加密后也只能用公钥解密

混合加密

HTTPS通信过程中,采用的是对称加密+非对称加密,也就是混合加密

在对称加密中讲到,如果能够保证了密钥的安全,那整个通信过程就可以说具有了机密性

HTTPS采用非对称加密解决秘钥交换的问题

具体做法是发送密文的一方使用对方的公钥进行加密处理“对称的密钥”,然后对方用自己的私钥解密拿到“对称的密钥”

这样可以确保交换的密钥是安全的前提下,使用对称加密方式进行通信

举个例子:

网站秘密保管私钥,在网上任意分发公钥,你想要登录网站只要用公钥加密就行了,密文只能由私钥持有者才能解密。而黑客因为没有私钥,所以就无法破解密文

上述的方法解决了数据加密,在网络传输过程中,数据有可能被篡改,并且黑客可以伪造身份发布公钥,如果你获取到假的公钥,那么混合加密也并无多大用处,你的数据扔被黑客解决

因此,在上述加密的基础上仍需加上完整性、身份验证的特性,来实现真正的安全,实现这一功能则是摘要算法

摘要算法

实现完整性的手段主要是摘要算法,也就是常说的散列函数、哈希函数

可以理解成一种特殊的压缩算法,它能够把任意长度的数据“压缩”成固定长度、而且独一无二的“摘要”字符串,就好像是给这段数据生成了一个数字“指纹”

摘要算法保证了“数字摘要”和原文是完全等价的。所以,我们只要在原文后附上它的摘要,就能够保证数据的完整性

比如,你发了条消息:“转账 1000 元”,然后再加上一个 SHA-2 的摘要。网站收到后也计算一下消息的摘要,把这两份“指纹”做个对比,如果一致,就说明消息是完整可信的,没有被修改

img

数字签名

数字签名能确定消息确实是由发送方签名并发出来的,因为别人假冒不了发送方的签名

原理其实很简单,就是用私钥加密,公钥解密

签名和公钥一样完全公开,任何人都可以获取。但这个签名只有用私钥对应的公钥才能解开,拿到摘要后,再比对原文验证完整性,就可以像签署文件一样证明消息确实是你发的

和消息本身一样,因为谁都可以发布公钥,我们还缺少防止黑客伪造公钥的手段,也就是说,怎么判断这个公钥就是你的公钥

这时候就需要一个第三方,就是证书验证机构

CA验证机构

数字证书认证机构处于客户端与服务器双方都可信赖的第三方机构的立场

CA 对公钥的签名认证要求包括序列号、用途、颁发者、有效时间等等,把这些打成一个包再签名,完整地证明公钥关联的各种信息,形成“数字证书”

流程如下图:

  • 服务器的运营人员向数字证书认证机构提出公开密钥的申请

  • 数字证书认证机构在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名

  • 然后分配这个已签名的公开密钥,并将该公开密钥放入公钥证书后绑定在一起

  • 服务器会将这份由数字证书认证机构颁发的数字证书发送给客户端,以进行非对称加密方式通信接到证书的客户端可使用数字证书认证机构的公开密钥,对那张证书上的数字签名进行验证,一旦验证通过,则证明:认证服务器的公开密钥的是真实有效的数字证书认证机构

  • 服务器的公开密钥是值得信赖的

三、总结

可以看到,HTTPSHTTP虽然只差一个SSL,但是通信安全得到了大大的保障,通信的四大特性都以解决,解决方式如下:

  • 机密性:混合算法
  • 完整性:摘要算法
  • 身份认证:数字签名
  • 不可否定:数字签名

同时引入第三方证书机构,确保公开秘钥的安全性

♥︎ ♥︎ ♥︎ ♥︎ 如何理解UDP 和 TCP? 区别? 应用场景?

一、UDP

UDP(User Datagram Protocol),用户数据包协议,是一个简单的面向数据报的通信协议,即对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层

也就是说无论应用层交给UDP多长的报文,它统统发送,一次发送一个报文

而对接收方,接到后直接去除首部,交给上面的应用层就完成任务

UDP报头包括4个字段,每个字段占用2个字节(即16个二进制位),标题短,开销小

特点如下:

  • UDP 不提供复杂的控制机制,利用 IP 提供面向无连接的通信服务
  • 传输途中出现丢包,UDP 也不负责重发
  • 当包的到达顺序出现乱序时,UDP没有纠正的功能。
  • 并且它是将应用程序发来的数据在收到的那一刻,立即按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况,UDP 也无法进行流量控制等避免网络拥塞行为

二、TCP

TCP(Transmission Control Protocol),传输控制协议,是一种可靠、面向字节流的通信协议,把上面应用层交下来的数据看成无结构的字节流来发送

可以想象成流水形式的,发送方TCP会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着,TCP会根据当前网络的拥塞状态来确定每个报文段的大小

TCP报文首部有20个字节,额外开销大

特点如下:

  • TCP充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在 UDP 中都没有。
  • 此外,TCP 作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。
  • 根据 TCP 的这些机制,在 IP 这种无连接的网络上也能够实现高可靠性的通信( 主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现)

三、区别

UDPTCP两者的都位于传输层,如下图所示:

两者区别如下表所示:

TCP UDP
可靠性 可靠 不可靠
连接性 面向连接 无连接
报文 面向字节流 面向报文
效率 传输效率低 传输效率高
双共性 全双工 一对一、一对多、多对一、多对多
流量控制 滑动窗口
拥塞控制 慢开始、拥塞避免、快重传、快恢复
传输效率
  • TCP 是面向连接的协议,建立连接3次握手、断开连接四次挥手,UDP是面向无连接,数据传输前后不连接连接,发送端只负责将数据发送到网络,接收端从消息队列读取
  • TCP 提供可靠的服务,传输过程采用流量控制、编号与确认、计时器等手段确保数据无差错,不丢失。UDP 则尽可能传递数据,但不保证传递交付给对方
  • TCP 面向字节流,将应用层报文看成一串无结构的字节流,分解为多个TCP报文段传输后,在目的站重新装配。UDP协议面向报文,不拆分应用层报文,只保留报文边界,一次发送一个报文,接收方去除报文首部后,原封不动将报文交给上层应用
  • TCP 只能点对点全双工通信。UDP 支持一对一、一对多、多对一和多对多的交互通信

两者应用场景如下图:

可以看到,TCP 应用场景适用于对效率要求低,对准确性要求高或者要求有链接的场景,而UDP 适用场景为对效率要求高,对准确性要求低的场景

♥︎ ♥︎ ♥︎ ♥︎ 如何理解OSI七层模型?

一、是什么

OSI (Open System Interconnect)模型全称为开放式通信系统互连参考模型,是国际标准化组织 ( ISO ) 提出的一个试图使各种计算机在世界范围内互连为网络的标准框架

OSI 将计算机网络体系结构划分为七层,每一层实现各自的功能和协议,并完成与相邻层的接口通信。即每一层扮演固定的角色,互不打扰

二、划分

OSI主要划分了七层,如下图所示:

68747470733a2f2f7374617469632e7675652d6a732e636f6d2f65623162323137302d623631652d313165622d616239302d6439616538313462323430642e706e67

应用层

应用层位于 OSI 参考模型的第七层,其作用是通过应用程序间的交互来完成特定的网络应用

该层协议定义了应用进程之间的交互规则,通过不同的应用层协议为不同的网络应用提供服务。例如域名系统 DNS,支持万维网应用的 HTTP 协议,电子邮件系统采用的 SMTP 协议等

在应用层交互的数据单元我们称之为报文

表示层

表示层的作用是使通信的应用程序能够解释交换数据的含义,其位于 OSI 参考模型的第六层,向上为应用层提供服务,向下接收来自会话层的服务

该层提供的服务主要包括数据压缩,数据加密以及数据描述,使应用程序不必担心在各台计算机中表示和存储的内部格式差异

会话层

会话层就是负责建立、管理和终止表示层实体之间的通信会话

该层提供了数据交换的定界和同步功能,包括了建立检查点和恢复方案的方法

传输层

传输层的主要任务是为两台主机进程之间的通信提供服务,处理数据包错误、数据包次序,以及其他一些关键传输问题

传输层向高层屏蔽了下层数据通信的细节。因此,它是计算机通信体系结构中关键的一层

其中,主要的传输层协议是TCPUDP

网络层

两台计算机之间传送数据时其通信链路往往不止一条,所传输的信息甚至可能经过很多通信子网

网络层的主要任务就是选择合适的网间路由和交换节点,确保数据按时成功传送

在发送数据时,网络层把传输层产生的报文或用户数据报封装成分组和包,向下传输到数据链路层

在网络层使用的协议是无连接的网际协议(Internet Protocol)和许多路由协议,因此我们通常把该层简单地称为 IP 层

数据链路层

数据链路层通常也叫做链路层,在物理层和网络层之间。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层协议

在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧

每一帧的数据可以分成:报头head和数据data两部分:

  • head 标明数据发送者、接受者、数据类型,如 MAC地址
  • data 存储了计算机之间交互的数据

通过控制信息我们可以知道一个帧的起止比特位置,此外,也能使接收端检测出所收到的帧有无差错,如果发现差错,数据链路层能够简单的丢弃掉这个帧,以避免继续占用网络资源

物理层

作为 OSI 参考模型中最低的一层,物理层的作用是实现计算机节点之间比特流的透明传送

该层的主要任务是确定与传输媒体的接口的一些特性(机械特性、电气特性、功能特性,过程特性)

该层主要是和硬件有关,与软件关系不大

三、传输过程

数据在各层之间的传输如下图所示:

  • 应用层报文被传送到运输层
  • 在最简单的情况下,运输层收取到报文并附上附加信息,该首部将被接收端的运输层使用
  • 应用层报文和运输层首部信息一道构成了运输层报文段。附加的信息可能包括:允许接收端运输层向上向适当的应用程序交付报文的信息以及差错检测位信息。该信息让接收端能够判断报文中的比特是否在途中已被改变
  • 运输层则向网络层传递该报文段,网络层增加了如源和目的端系统地址等网络层首部信息,生成了网络层数据报
  • 网络层数据报接下来被传递给链路层,在数据链路层数据包添加发送端 MAC 地址和接收端 MAC 地址后被封装成数据帧
  • 在物理层数据帧被封装成比特流,之后通过传输介质传送到对端
  • 对端再一步步解开封装,获取到传送的数据

♥︎ ♥︎ ♥︎ ♥︎ 如何理解TCP/IP协议?

一、是什么

TCP/IP,传输控制协议**/**网际协议,是指能够在多个不同网络间实现信息传输的协议簇

  • TCP(传输控制协议)

一种面向连接的、可靠的、基于字节流的传输层通信协议

  • IP(网际协议)

用于封包交换数据网络的协议

TCP/IP协议不仅仅指的是TCP IP两个协议,而是指一个由FTPSMTPTCPUDPIP等协议构成的协议簇,

只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以通称为TCP/IP协议族(英语:TCP/IP Protocol Suite,或TCP/IP Protocols)

二、划分

TCP/IP协议族按层次分别了五层体系或者四层体系

五层体系的协议结构是综合了 OSI 和 TCP/IP 优点的一种协议,包括应用层、传输层、网络层、数据链路层和物理层

五层协议的体系结构只是为介绍网络原理而设计的,实际应用还是 TCP/IP 四层体系结构,包括应用层、传输层、网络层(网际互联层)、网络接口层

如下图所示:

五层体系

应用层

TCP/IP 模型将 OSI 参考模型中的会话层、表示层和应用层的功能合并到一个应用层实现,通过不同的应用层协议为不同的应用提供服务

如:FTPTelnetDNSSMTP

传输层

该层对应于 OSI 参考模型的传输层,为上层实体提供源端到对端主机的通信功能

传输层定义了两个主要协议:传输控制协议(TCP)和用户数据报协议(UDP)

其中面向连接的 TCP 协议保证了数据的传输可靠性,面向无连接的 UDP 协议能够实现数据包简单、快速地传输

网络层

负责为分组网络中的不同主机提供通信服务,并通过选择合适的路由将数据传递到目标主机

在发送数据时,网络层把运输层产生的报文段或用户数据封装成分组或包进行传送

数据链路层

数据链路层在两个相邻节点传输数据时,将网络层交下来的IP数据报组装成帧,在两个相邻节点之间的链路上传送帧

物理层

保数据可以在各种物理媒介上进行传输,为数据的传输提供可靠的环境

四层体系

TCP/IP 的四层结构则如下表所示:

层次名称 单位 功 能 协 议
网络接口层 负责实际数据的传输,对应OSI参考模型的下两层 HDLC(高级链路控制协议)PPP(点对点协议) SLIP(串行线路接口协议)
网络层 数据报 负责网络间的寻址数据传输,对应OSI参考模型的第三层 IP(网际协议) ICMP(网际控制消息协议)ARP(地址解析协议) RARP(反向地址解析协议)
传输层 报文段 负责提供可靠的传输服务,对应OSI参考模型的第四层 TCP(控制传输协议) UDP(用户数据报协议)
应用层 负责实现一切与应用程序相关的功能,对应OSI参考模型的上三层 FTP(文件传输协议) HTTP(超文本传输协议) DNS(域名服务器协议)SMTP(简单邮件传输协议)NFS(网络文件系统协议)

三、总结

OSI 参考模型与 TCP/IP 参考模型区别如下:

相同点:

  • OSI 参考模型与 TCP/IP 参考模型都采用了层次结构
  • 都能够提供面向连接和无连接两种通信服务机制

不同点:

  • OSI 采用的七层模型; TCP/IP 是四层或五层结构
  • TCP/IP 参考模型没有对网络接口层进行细分,只是一些概念性的描述; OSI 参考模型对服务和协议做了明确的区分
  • OSI 参考模型虽然网络划分为七层,但实现起来较困难。TCP/IP 参考模型作为一种简化的分层结构是可以的
  • TCP/IP协议去掉表示层和会话层的原因在于会话层、表示层、应用层都是在应用程序内部实现的,最终产出的是一个应用数据包,而应用程序之间是几乎无法实现代码的抽象共享的,这也就造成 OSI 设想中的应用程序维度的分层是无法实现的

♥︎ ♥︎ ♥︎ ♥︎ 说一下 GET 和 POST 的区别?

GETPOST,两者是HTTP协议中发送请求的方法

GET

GET方法请求一个指定资源的表示形式,使用GET的请求应该只被用于获取数据

POST

POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用

本质上都是TCP链接,并无差别

但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中会体现出一些区别

一、区别

w3schools得到的标准答案的区别如下:

  • GET在浏览器回退时是无害的,而POST会再次提交请求。
  • GET产生的URL地址可以被Bookmark,而POST不可以。
  • GET请求会被浏览器主动cache,而POST不会,除非手动设置。
  • GET请求只能进行url编码,而POST支持多种编码方式。
  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
  • GET请求在URL中传送的参数是有长度限制的,而POST没有。
  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
  • GET参数通过URL传递,POST放在Request body中

参数位置

貌似从上面看到GETPOST请求区别非常大,但两者实质并没有区别

无论 GET 还是 POST,用的都是同一个传输层协议,所以在传输上没有区别

当不携带参数的时候,两者最大的区别为第一行方法名不同

POST /uri HTTP/1.1 \r\n

GET /uri HTTP/1.1 \r\n

当携带参数的时候,我们都知道GET请求是放在url中,POST则放在body

GET 方法简约版报文是这样的

GET /index.html?name=qiming.c&age=22 HTTP/1.1
Host: localhost

POST 方法简约版报文是这样的

POST /index.html HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded

name=qiming.c&age=22

注意:这里只是约定,并不属于HTTP规范,相反的,我们可以在POST请求中url中写入参数,或者GET请求中的body携带参数

参数长度

HTTP 协议没有BodyURL 的长度限制,对 URL 限制的大多是浏览器和服务器的原因

IEURL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持

这里限制的是整个URL长度,而不仅仅是参数值的长度

服务器处理长 URL 要消耗比较多的资源,为了性能和安全考虑,会给 URL 长度加限制

安全

POST GET 安全,因为数据在地址栏上不可见

然而,从传输的角度来说,他们都是不安全的,因为 HTTP 在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文

只有使用HTTPS才能加密安全

数据包

对于GET方式的请求,浏览器会把http headerdata一并发送出去,服务器响应200(返回数据)

对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok

并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次

♥︎ ♥︎ ♥︎ ♥︎ 对WebSocket的理解?应用场景?

一、是什么

WebSocket,是一种网络传输协议,位于OSI模型的应用层。可在单个TCP连接上进行全双工通信,能更好的节省服务器资源和带宽并达到实时通迅

客户端和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输

从上图可见,websocket服务器与客户端通过握手连接,连接成功后,两者都能主动的向对方发送或接受数据

而在websocket出现之前,开发实时web应用的方式为轮询

不停地向服务器发送 HTTP 请求,问有没有数据,有数据的话服务器就用响应报文回应。如果轮询的频率比较高,那么就可以近似地实现“实时通信”的效果

轮询的缺点也很明显,反复发送无效查询请求耗费了大量的带宽和 CPU 资源

二、特点

全双工

通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合

例如指 A→B 的同时 B→A ,是瞬时同步的

二进制帧

采用了二进制帧结构,语法、语义与 HTTP 完全不兼容,相比http/2WebSocket 更侧重于“实时通信”,而HTTP/2 更侧重于提高传输效率,所以两者的帧结构也有很大的区别

不像 HTTP/2 那样定义流,也就不存在多路复用、优先级等特性

自身就是全双工,也不需要服务器推送

协议名

引入wswss分别代表明文和密文的websocket协议,且默认端口使用80或443,几乎与http一致

ws://www.chrono.com
ws://www.chrono.com:8080/srv
wss://www.chrono.com:445/im?user_id=xxx
握手

WebSocket 也要有一个握手过程,然后才能正式收发数据

客户端发送数据格式如下:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
  • Connection:必须设置Upgrade,表示客户端希望连接升级
  • Upgrade:必须设置Websocket,表示希望升级到Websocket协议
  • Sec-WebSocket-Key:客户端发送的一个 base64 编码的密文,用于简单的认证秘钥。要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept应答,否则客户端会抛出错误,并关闭连接
  • Sec-WebSocket-Version :表示支持的Websocket版本

服务端返回的数据格式:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: chat
  • HTTP/1.1 101 Switching Protocols:表示服务端接受 WebSocket 协议的客户端连接
  • Sec-WebSocket-Accep:验证客户端请求报文,同样也是为了防止误连接。具体做法是把请求头里“Sec-WebSocket-Key”的值,加上一个专用的 UUID,再计算摘要

优点

  • 较少的控制开销:数据包头部协议较小,不同于http每次请求需要携带完整的头部
  • 更强的实时性:相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少
  • 保持创连接状态:创建通信后,可省略状态信息,不同于HTTP每次请求需要携带身份验证
  • 更好的二进制支持:定义了二进制帧,更好处理二进制内容
  • 支持扩展:用户可以扩展websocket协议、实现部分自定义的子协议
  • 更好的压缩效果:Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率

二、应用场景

基于websocket的事实通信的特点,其存在的应用场景大概有:

  • 弹幕
  • 媒体聊天
  • 协同编辑
  • 基于位置的应用
  • 体育实况更新
  • 股票基金报价实时更新

前端面试大全React

前端面试题大全(React.js)

前端面试题类目分类

  • HTML5 + CSS3
  • JavaScript
  • Vue + Vue3
  • React
  • Webpack
  • 服务端

考点频率 :♥︎ 、 ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎ ♥︎

React.js

♥︎♥︎ fetch的延时操作

// fetch语法:fetch(resource, config).then( function(response) { ... } );resource为要获取的资源,
config是配置对象,包含method请求方法,headers请求头信息等
// 定义一个延时函数,返回一个promise
const delayPromise = (timeout=5000) => {
 return new Promise((resolve, reject) => {
 setTimeout(()=>{
 reject(new Error("网络错误"))
 }, timeout)
 })
}
// 定义一个fetch网络请求,返回一个promise
const fetchPromise = (resource, config) => {
 return new Promise((resolve, reject)=>{
 fetch(resource, config).then(res=>{
 resolve(res)
 })
 })
}
// promise的race静态方法接受多个promise对象组成的数组,该数组中哪个promise先执行完成,race方法就返回这个promise的执行结果
const fetchRequest = (resource, config, timeout) => {
 Promise.race([
 delayPromise(timeout), 
 fetchPromise(resource,config)
 ])
}

♥︎♥︎ A 组件嵌套 B 组件,生命周期执行顺序

父组件创建阶段的生命周期钩子函数 constructor
父组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 constructor
子组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 componentDidMount
父组件创建阶段的生命周期钩子函数 componentDidMount

父->父->子->子->子->父

♥︎♥︎ React 组件中 props 和 state 有什么区别?

1、props是从外部传入组件的参数,一般用于父组件向子组件通信,在组件之间通信使用;state一般用于组件内部的状态维护,更新组建内部的数据,状态,更新子组件的props等

2、props不可以在组件内部修改,只能通过父组件进行修改;state在组件内部通过setState修改;

♥︎♥︎ react中组件分为那两种?

类组件和函数组件

♥︎♥︎ 描述 Flux 与 MVC?

传统的 MVC 模式在分离数据(Model)、UI(View和逻辑(Controller)方面工作得很好,但是 MVC 架构经常遇到两个主要问题:

  1. 数据流不够清晰——跨视图发生的级联更新常常会导致混乱的事件网络,难于调试。
  2. 缺乏数据完整性——模型数据可以在任何地方发生突变,从而在整个UI中产生不可预测的结果。

使用 Flux 模式的复杂用户界面不再遭受级联更新,任何给定的React 组件都能够根据 store 提供的数据重建其状态。Flux 模式还通过限制对共享数据的直接访问来加强数据完整性。

♥︎♥︎ 在 React 中使用构造函数和 getInitialState 有什么区别?

// ES6
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { /* initial state */ };
}
}
// ES5
var MyComponent = React.createClass({
getInitialState() {
  return { /* initial state */ };
},
});

本质上其实是等价的?

区别在于ES6和ES5本身,getInitialState 是搭配 React.createClass 使用的, constructor 是搭配React.Component 使用的,在React组件的生命周期中 constructor 先于 getInitialState

♥︎♥︎ 为什么说React是view(视图层)

react, 是 Facebook 推出的一个用来构建用户界面的 JavaScript 库. React 主要用于构建 UI

React被认为是视图层的框架是因为它是基于组件的,一切都是组件,而组件就是渲染页面的基础。不论组件中包含的jsx,methods,state,props,都是属于组件内部的

View(视图)是应用程序中处理数据显示的部分。视图层主要包括二个部分:

1.视图层显示及交互逻辑;

2.视图层的数据结构ViewObj, 包括React中的props和stats;


♥︎♥︎♥︎ React 事件绑定原理

一、react并没有使用原生的浏览器事件,而是在基于Virtual DOM的基础上实现了合成事件,采用小驼峰命名法,默认的事件传播方式是冒泡,如果想改为捕获的话,直接在事件名后面加上Capture即可;事件对象event也不是原生事件对象,而是合成对象,但通过nativeEvent属性可以访问原生事件对象;

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

1、事件注册

在该阶段主要做了两件事:document上注册、存储事件回调。所有事件都会注册到document上,

拥有统一的回调函数dispatchEvent来执行事件分发,类似于

document.addEventListener("click",dispatchEvent)。
 register:
 addEventListener-click
 addEventListener-change
listenerBank: 
{ 
click: {key1: fn1, key2: fn2}, 
change: {key1: fn3, key3: fn4} 
} 

2、事件合成

事件触发后,会执行一下过程:

(1)进入统一的事件分发函数dispatchEvent;

(2)找到触发事件的 ReactDOMComponent;

(3)开始事件的合成;

—— 根据当前事件类型生成指定的合成对象

—— 封装原生事件和冒泡机制

—— 查找当前元素以及他所有父级

—— 在listenerBank根据key值查找事件回调并合成到 event(合成事件结束)

3、批处理

批量处理合成事件内的回调函数

♥︎♥︎♥︎ React中的 setState 缺点是什么呢

setState执行的时候可以简单的认为,隶属于原生js执行的空间,那么就是属于同步,被react处理过的空间属于异步,这其实也是一种性能的优化,如果多次使用setState修改值,那么在异步中会先进行合并,再进行渲染,降低了操作dom的次数,具体如下:

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

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

(3)setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState, setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

(4)正是由于setState存在异步的机制,如果setState修改值的时候依赖于state本身的值,有时候并不可靠,这时候我们需要传入一个回调函数作为其入参,这个回调函数的第一个参数为更新前的state值。

♥︎♥︎♥︎ React组件通信如何实现

react本身:

(1) props ——父组件向子组件通过props传参

(2) 实例方法——在父组件中可以用 refs 引用子组件,之后就可以调用子组件的实例方法了

(3) 回调函数——用于子组件向父组件通信,子组件调用props传递过来的方法

(4) 状态提升——两个子组件可以通过父组件定义的参数进行传参

(5) Context上下文——一般用作全局主题

(6) Render Props——渲染的细节由父组件控制

状态管理:

(1) mobx/redux/dva——通过在view中触发action,改变state,进而改变其他组件的view

♥︎♥︎♥︎ 类组件和函数组件的区别

(1)语法上:函数组件是一个函数,返回一个jsx元素,而类组件是用es6语法糖class定义,继承component这个类

(2)类组件中可以通过state进行状态管理,而在函数组件中不能使用setState(),在react16.8以后,函数组件可以通过hooks中的useState来模拟类组件中的状态管理;

(3)类组件中有一系列的生命周期钩子函数,在函数组件中也需要借助hooks来使用生命周期函数;

(4)类组件能够捕获最新的值(永远保持一致),这是因为当实例的props属性发生修改时,class组件能够直接通过this捕获到组件最新的props;而函数式组件是捕获渲染所使用的值,已经因为javascript闭包的特性,之前的props参数保存在内存之中,无法从外部进行修改。

♥︎♥︎♥︎ 请你说说React的路由是什么?

路由分为前端路由和后端路由,后端路由是服务器根据用户发起的请求而返回不同内容,前端路由是客户端根据不同的URL去切换组件;在web应用前端开发中,路由系统是最核心的部分,当页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新。

react生态中路由通常是使用react-router来进行配置,其主要构成为:

(1)Router——对应路由的两种模式,包括与BrowserRouter与HashRoute;

(2)route matching组件——控制路径对应的显示组件,可以进行同步加载和异步加载;

(3)navigation组件——用做路由切换和跳转;

BrowserRouter与HashRouter的区别:

(1)底层原理不一样:BrowserRouter使用的是H5的history API,不兼容IE9及以下版本;HashRouter使用的是URL的哈希值;

(2)path表现形式不一样:BrowserRouter的路径中没有#,例如:localhost:3000/demo/test;HashRouter的路径包含#,例如:localhost:3000/#/demo/test;

(3)刷新后对路由state参数的影响:BrowserRouter没有任何影响,因为state保存在history对象中;

react生态中路由通常是使用react-router来进行配置,其主要构成为:

BrowserRouter与HashRouter的区别:HashRouter刷新后会导致路由state参数的丢失;

♥︎♥︎♥︎ 聊聊 Redux 和 Vuex 的设计思想

Flux的核心思想就是数据和逻辑永远单向流动,由三大部分组成 dispatcher(负责分发事件),store(负责保存数据,同时响应事件并更新数据)和 view(负责订阅store中的数据,并使用这些数据渲染相应的页面),Redux和Vuex是flux思想的具体实现,都是用来做状态管理的工具,Redux主要在react中使用,Vuex主要在vue中使用。

Redux设计和使用的三大原则: 
(1)单一的数据源:整个应用的 state被储存在唯一一个 store中; 
(2)状态是只读的:Store.state不能直接修改(只读),必须调用dispatch(action) => store.reducer => return newState;action是一个对象,有type(操作类型)和payload(新值) 属性;
(3)状态修改均由纯函数完成:在Redux中,通过纯函数reducer来确定状态的改变,因为reducer是纯 函数,所以相同的输入,一定会得到相同的输出,同时也不支持异步;返回值是一个全新的state; 

vuex由State + Muatations(commit) + Actions(dispatch) 组成: 
(1)全局只有一个Store实例(单一数据源);
(2)Mutations必须是同步事务,不同步修改的话,会很难调试,不知道改变什么时候发生,也很难确定 先后顺序,A、B两个mutation,调用顺序可能是A -> B,但是最终改变 State的结果可能是B -> A; 
(3)Actions负责处理异步事务,然后在异步回调中触发一个或多个mutations,也可以在业务代码中处 理异步事务,然后在回调中同样操作; 
(4)模块化通过module方式来处理,这个跟Redux-combineReducer类似,在应用中可以通过 namespaceHelper来简化使用;

♥︎♥︎♥︎ React中不同组件之间如何做到数据交互?

正向传值–使用 props

父组件发送数据在子组件中使用 this.props.xxx 来接收数据,如果父级的某个props 改变了,React 会重渲染所有的子节点

逆向传值—函数传值

  • 子组件通过事件调用函数传递
  • 在子组件中使用 this.props调用的函数名绑定发送数据。
  • 在父组件中进行函数传递。
  • 父组件中必须要有一个形参用来接收子组件发送过来的数据。

同级传值—pubsub-js

  • 在第一个要传值的组件中进行数据抛出 PubSub.publish(“ 事件名”,”数据”)。
  • 在第二个要接收数据的组件中接收 PubSub.subscribe(“监听的事件”,(事件,数 据)=>{})。

跨组件传值—context

context 上下文对象,无需为每一层组件手动添加 props,就能在组件数间进行数据传递的方法。
使用 createContext()方法提供了两个对象 - Provider 对象生产者—->用来生产数据 - Consumer对象消费者—->用来使用数据。

♥︎♥︎♥︎ React中refs的作用是什么?

ref是React提供的用来操纵React组件实例或者DOM元素的接口。主要用来做文本框的聚焦、触发强制动画等;

//类组件
class Foo extends React.Component {
   constructor(props) {
 super(props)
 this.myRef = React.createRef()
 }
 render() {
 return }
  
<input ref={ this.myRef } /> <button onClick = {()=>this.handle()}>聚焦
} 
handle() { 
 // 通过current属性访问到当前元素 this.myRef.current.focus() 
} 
// 函数组件 function
Foo() { const inputEl = useRef(null) const handle = () => { inputEl.current.focus() } 
return
<input type="text" ref={ inputEl }/> 聚焦}

♥︎♥︎♥︎ 组件绑定和js原生绑定事件哪个先执行?

先执行js原生绑定事件,再执行合成事件,因为合成事件是发生在冒泡阶段

♥︎♥︎♥︎ diff 和 Key 之间的联系

diff算法即差异查找算法,对于DOM结构即为tree的差异查找算法,只有在React更新阶段才会有Diff算法的运用;react的diff运算为了降低时间复杂度,是按层比较新旧两个虚拟dom树的。diff运算的主要流程见下:

1、tree diff : 新旧两棵dom树,逐层对比的过程就是 tree diff, 当整棵DOM树逐层对比完毕,则所有需要被按需更新的元素,必然能够被找到。 
2、component diff : 在进行tree diff的时候,每一层中,都有自己的组件,组件级别的对比, 叫做 component diff。如果对比前后,组件的类型相同,则暂时认为此组件不需要更新;如果对比前后, 组件的类型不同,则需要移除旧组件,创建新组件,并渲染到页面上。 React只会匹配类型相同的组件,也就是说如果<A>被<B>替换,那么React将直接删除A组件然后创建一 个B组件;如果某组件A转移到同层B组件上,那么这个A组件会先被销毁,然后在B组件下重新生成,以A为根节 点的树整个都被重新创建,这会比较耗费性能,但实际上我们很少跨层移动dom节点,一般都是同层横向移动; 
3、element diff :在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比, 这叫做element diff。 对于列表渲染,react会在创建时要求为每一项输入一个独一无二的key,这样就能进行高效的diff运算 了。比如我们要在b和c节点中间插入一个节点f,jquery会将f这个节点后面的每一个节点都进行更新,比如c 更新成f,d更新成c,e更新成d,这样操作的话就会特别多,而加了key的react咋不会频繁操作dom,而是优先采用移动的方式,找到正确的位置去插入新节点;所以我们不能省略key值,因为在对比两个新旧的子元素是通过key值来精确地判断两个节点是否为同一个,如果没有key的话则是见到谁就更新谁,非常耗费性能。 

当我们通过this.setState()改变数据的时候,React会将其标记为脏节点,在事件循环的最后 才会重新渲染所有的脏节点以及脏节点的子树;另外我们可以使用shouldComponentUpdate这个生命周期来 选择性的渲染子树,可以基于组件之前的状态或者下一个状态来决定它是否需要重新渲染,这样的话可以组织 重新渲染大的子树。

♥︎♥︎♥︎ 虚拟 dom 和原生 dom

(1)原生dom是浏览器通过dom树渲染的复杂对象,属性非常多;

(2)虚拟dom是存在于内存中的js对象,属性远少于原生的dom对象,它用来描述真实的dom,并不会直接在浏览器中显示;

(3)原生dom操作、频繁排版与重绘的效率是相当低的,虚拟dom则是利用了计算机内存高效的运算性能减少了性能的损耗;

(4)虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中对修改部分进行排版与重绘,减少过多DOM节点排版与重绘损耗

♥︎♥︎♥︎ react中如何打包上传图片文件

通过 base64 前端处理图片为 base64 的解决方案

  • 利用 FileReader 对数据进行读取,如果是图片会将图片读取为 base64 的形式
  • 将得到的 base64 的字符串传给后端
  • 后端直接保存该html字符串,之后调用接口查询该数据直接前端通过img标签完成自动解析即可

代码实现:

function App() {
  const handleFileChange = e => {
    const file = e.currentTarget.files[0];
    const reader = new FileReader();

    reader.onload = function() {
      // reader.results当完成onload后会将图片转为base64
      // 后端只要解析base64对应的字符串即可
      const result = this.result;
      console.log(result);
    };

    reader.readAsDataURL(file); // 得到经过base64编码的图片信息
  };

  return (
    <div className="App">
      <input type="file" onChange={handleFileChange} />
    </div>
  );
}

利用input,xhr,formData来实现

  • 利用input[type=‘file’]来实现
  • 点击选择文件,且选择文件完毕后,触发onChange事件
  • 通过event.targer.files(react中)获取所选文件
  • 通过FormData这个类,将文件添加到其实例中
  • 配置xhr,通过POST的方式发送formData到后端即可

代码实现:

import React, { useState } from 'react';
import { ApiHost } from '../../constant';
import { Button, FormControl, Progress } from 'zent';

export type UploadCompletCallback<T> = (
  e: ProgressEvent<XMLHttpRequestEventTarget>,
  reponse: T
) => void;

export type UploadStartCallback = (
  e: ProgressEvent<XMLHttpRequestEventTarget>
) => void;

export type UploadProcessCallback = (loaded: number, total: number) => void;

interface Props {
  onComplete?: UploadCompletCallback<any>;
  onStart?: UploadStartCallback;
  onProcess?: UploadProcessCallback;
  hasProcess?: boolean;
  title: string;
}

export const UploadBtn: React.FC<Props> = props => {
  const { onComplete, onStart, onProcess, hasProcess = false, title } = props;
  const [progress, setProgress] = useState<number>(0);
  const [uploadStatus, setStatus] = useState<boolean>(false);

  // 处理文件上传的核心方法
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files;

    if (files) {
      if (!files.length) return;

  // 将文件处理成formData
      let formData = new FormData();
      for (let k in files) {
        formData.append('file', files[k], window.encodeURI(files[k].name));
      }

      let xhr = new XMLHttpRequest();

      xhr.responseType = 'json';
      xhr.timeout = 5000;
      xhr.open('POST', `${ApiHost}/user/upload`, true);

   // 事件监听
      xhr.addEventListener('loadstart', e => {
        setProgress(0);
        setStatus(true);
        onStart && onStart(e);
      });

      xhr.upload.onprogress = function(e) {
        const { total, loaded } = e;
        setProgress((loaded / total) * 100);
        onProcess && onProcess(loaded, total);
      };

      xhr.addEventListener('load', e => {
        const result = xhr.response;
        onComplete && onComplete(e, result);
      });

      xhr.send(formData);
    } else {
      return;
    }
  };

  const selectFile = () => {
    const btn = document.querySelector("input[type='file']");
    
 //@ts-ignore
    btn && btn.click();
  };
  return (
    <FormControl label={title}>
      <input type="file" onChange={handleChange} style={{ display: 'none' }} />
      <Button onClick={selectFile}>上传</Button>
      {hasProcess && uploadStatus && <Progress percent={progress}></Progress>}
    </FormControl>
  );
};

后端对于该文件上传请求的处理nodejs版本

主要步骤:

  • 利用fs.createReadStream 来读取本地的传过去文件的地址
  • 利用fs.createWriteStream来写到服务器上的某个文件地址
  • 通过reader.pipe来将readFile里面的内容复制到writeFile中

♥︎♥︎♥︎ 对单向数据流和双向数据绑定的理解,好处?

react的单向数据流是指只允许父组件向子组件传递数据,子组件绝对不能修改父组件传的数据,如果想要修改数据,则要在子组件中执行父组件传递过来的回调函数,提醒父组件对数据进行修改。数据单向流让所有的状态改变可以追溯,有利于应用的可维护性;

angular中实现了双向数据绑定,代码编写方便,但是不利于维护

♥︎♥︎♥︎ React 按需加载

1、使用React.lazy, 但是React.lazy技术还不支持服务端渲染

const OtherComponent = React.lazy(() => import('./OtherComponent'))

2、使用Loadable Components这个库

import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))

♥︎♥︎♥︎ React 实现目录树(组件自身调用自身)

需求描述:实现n级属性目录展开,该实现使用react,antd-mobile

使用方法

import TreesMenu from "./treeMenu.jsx";
<TreesMenu
       datas={treesData}
       onselected={item => {
           console.log("选择了树形目录的数据:", item);
       }}
></TreesMenu>

实现数据

export let treesData = [{"Childs":[{"Childs":[],"DeptName":"班子成员","ShortName":"班子成员","DeptCode":"81","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408498041056},{"Childs":[],"DeptName":"调研员","ShortName":"调研员","DeptCode":"82","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408531598048},{"Childs":[],"DeptName":"党政办公室","ShortName":"党政办公室","DeptCode":"83","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408548375776},{"Childs":[],"DeptName":"党政办公室(财务)","ShortName":"党政办公室(财务)","DeptCode":"84","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408581929184},{"Childs":[],"DeptName":"党建办公室","ShortName":"党建办公室","DeptCode":"85","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408598709728},{"Childs":[],"DeptName":"平安办公室","ShortName":"平安办公室","DeptCode":"86","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408615482336},{"Childs":[],"DeptName":"信访办公室","ShortName":"信访办公室","DeptCode":"87","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408632263392},{"Childs":[],"DeptName":"管理办公室","ShortName":"管理办公室","DeptCode":"88","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408649039584},{"Childs":[],"DeptName":"自治办公室","ShortName":"自治办公室","DeptCode":"89","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408665815776},{"Childs":[],"DeptName":"综合事务办公室","ShortName":"综合事务办公室","DeptCode":"90","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408665819872},{"Childs":[],"DeptName":"监察办公室","ShortName":"监察办公室","DeptCode":"91","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408682596576},{"Childs":[],"DeptName":"武装部","ShortName":"武装部","DeptCode":"92","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408699368672},{"Childs":[],"DeptName":"司法所","ShortName":"司法所","DeptCode":"93","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408699371232},{"Childs":[],"DeptName":"城运中心","ShortName":"城运中心","DeptCode":"94","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408716144864},{"Childs":[],"DeptName":"工会","ShortName":"工会","DeptCode":"95","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408749699296},{"Childs":[],"DeptName":"团委","ShortName":"团委","DeptCode":"96","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408749700320},{"Childs":[],"DeptName":"妇联","ShortName":"妇联","DeptCode":"97","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408749703648},{"Childs":[],"DeptName":"房管办","ShortName":"房管办","DeptCode":"98","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408749704672},{"Childs":[],"DeptName":"安监所","ShortName":"安监所","DeptCode":"99","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408766480352},{"Childs":[],"DeptName":"党建服务中心","ShortName":"党建服务中心","DeptCode":"100","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408783258080},{"Childs":[],"DeptName":"社区事务受理中心","ShortName":"社区事务受理中心","DeptCode":"101","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408816808672},{"Childs":[],"DeptName":"社区文化中心","ShortName":"社区文化中心","DeptCode":"102","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408917476576},{"Childs":[],"DeptName":"社区学校","ShortName":"社区学校","DeptCode":"103","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408951031264},{"Childs":[],"DeptName":"绿化市容","ShortName":"绿化市容","DeptCode":"104","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408951033824},{"Childs":[],"DeptName":"周家渡城管中队","ShortName":"周家渡城管中队","DeptCode":"105","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408967806688},{"Childs":[],"DeptName":"商会","ShortName":"商会","DeptCode":"106","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408967810016},{"Childs":[],"DeptName":"开发人员","ShortName":"开发人员","DeptCode":"107","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408967811040},{"Childs":[{"Childs":[],"DeptName":"上南八村","ShortName":"上南八村","DeptCode":"109","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409034918368},{"Childs":[],"DeptName":"上南四村","ShortName":"上南四村","DeptCode":"110","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409051695840},{"Childs":[],"DeptName":"上南五村","ShortName":"上南五村","DeptCode":"111","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409068471776},{"Childs":[],"DeptName":"上南六村","ShortName":"上南六村","DeptCode":"112","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409102022368},{"Childs":[],"DeptName":"上南七村","ShortName":"上南七村","DeptCode":"113","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409118800352},{"Childs":[],"DeptName":"上南九村","ShortName":"上南九村","DeptCode":"114","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409135576544},{"Childs":[],"DeptName":"齐河一","ShortName":"齐河一","DeptCode":"115","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409152353760},{"Childs":[],"DeptName":"齐河二","ShortName":"齐河二","DeptCode":"116","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409152360928},{"Childs":[],"DeptName":"上南十村1","ShortName":"上南十村1","DeptCode":"117","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409169135840},{"Childs":[],"DeptName":"上南十二村","ShortName":"上南十二村","DeptCode":"118","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409185911520},{"Childs":[],"DeptName":"上南十村2","ShortName":"上南十村2","DeptCode":"119","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409202686432},{"Childs":[],"DeptName":"上南十一村","ShortName":"上南十一村","DeptCode":"120","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409219461344},{"Childs":[],"DeptName":"都市庭院","ShortName":"都市庭院","DeptCode":"121","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409219466976},{"Childs":[],"DeptName":"川新","ShortName":"川新","DeptCode":"122","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409236243680},{"Childs":[],"DeptName":"雪野二村","ShortName":"雪野二村","DeptCode":"123","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409253021152},{"Childs":[],"DeptName":"上南花苑","ShortName":"上南花苑","DeptCode":"124","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409269798368},{"Childs":[],"DeptName":"昌里花园","ShortName":"昌里花园","DeptCode":"125","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409286570976},{"Childs":[],"DeptName":"恒大","ShortName":"恒大","DeptCode":"126","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409303348192},{"Childs":[],"DeptName":"上南三村","ShortName":"上南三村","DeptCode":"127","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409303353824},{"Childs":[],"DeptName":"上南一村","ShortName":"上南一村","DeptCode":"128","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409320130784},{"Childs":[],"DeptName":"上南二村","ShortName":"上南二村","DeptCode":"129","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409336908000},{"Childs":[],"DeptName":"昌里五","ShortName":"昌里五","DeptCode":"130","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409353685728},{"Childs":[],"DeptName":"云台一","ShortName":"云台一","DeptCode":"131","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409370462176},{"Childs":[],"DeptName":"云台二","ShortName":"云台二","DeptCode":"132","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409387237344},{"Childs":[],"DeptName":"云莲一","ShortName":"云莲一","DeptCode":"133","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409404012512},{"Childs":[],"DeptName":"昌里七","ShortName":"昌里七","DeptCode":"134","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409420789472},{"Childs":[],"DeptName":"动迁联合","ShortName":"动迁联合","DeptCode":"135","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409437567200},{"Childs":[],"DeptName":"齐河四","ShortName":"齐河四","DeptCode":"146","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409487902688},{"Childs":[],"DeptName":"齐河三","ShortName":"齐河三","DeptCode":"147","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409504677856},{"Childs":[],"DeptName":"昌里四","ShortName":"昌里四","DeptCode":"148","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409521452768},{"Childs":[],"DeptName":"齐河八","ShortName":"齐河八","DeptCode":"149","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409538229984},{"Childs":[],"DeptName":"齐河五","ShortName":"齐河五","DeptCode":"150","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409555007968},{"Childs":[],"DeptName":"齐河七","ShortName":"齐河七","DeptCode":"151","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409555013600}],"DeptName":"居委会","ShortName":"居委会","DeptCode":"108","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409034918112},{"Childs":[{"Childs":[],"DeptName":"办公室","ShortName":"办公室","DeptCode":"137","ParentID":2073409437571296,"Layer":2,"Path":",0,1,2073409437571296,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409437571552},{"Childs":[],"DeptName":"事务所","ShortName":"事务所","DeptCode":"138","ParentID":2073409437571296,"Layer":2,"Path":",0,1,2073409437571296,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409454343904}],"DeptName":"队伍建设办公室","ShortName":"队伍建设办公室","DeptCode":"136","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409437571296},{"Childs":[{"Childs":[],"DeptName":"办公室","ShortName":"办公室","DeptCode":"140","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409454347488},{"Childs":[],"DeptName":"慈善超市","ShortName":"慈善超市","DeptCode":"141","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409471121632},{"Childs":[],"DeptName":"残联","ShortName":"残联","DeptCode":"142","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409471122656},{"Childs":[],"DeptName":"计生办","ShortName":"计生办","DeptCode":"143","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409471124448},{"Childs":[],"DeptName":"爱卫办","ShortName":"爱卫办","DeptCode":"144","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409487897568},{"Childs":[],"DeptName":"老龄办","ShortName":"老龄办","DeptCode":"145","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409487900896},{"Childs":[],"DeptName":"居家养老服务中心","ShortName":"居家养老服务中心","DeptCode":"152","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2080463921349388},{"Childs":[],"DeptName":"综合为老服务中心","ShortName":"综合为老服务中心","DeptCode":"153","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2080464005240076}],"DeptName":"服务办公室","ShortName":"服务办公室","DeptCode":"139","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409454347232}],"DeptName":"周家渡街道办事处","ShortName":"周家渡街道办事处","DeptCode":"SHRGBTRJJSYXGS ","ParentID":0,"Layer":0,"Path":",0,","Sequence":11,"LeaderID":0,"ManagerID":0,"IsEnabled":1,"IsUnit":1,"ID":1,"SystemID":1}];

实现代码

import React from "react";
import { Accordion, List } from "antd-mobile";

class TreesMenu extends React.Component {
  render() {
    let datas = this.props.datas;
    return (
      <div>
        {datas instanceof Array ? (
          <div>
            {datas.map((d, index) => {
              return <TreeNode datas={d} key={index} onselected={(item) => this.props.onselected(item)}></TreeNode>;
            })}
          </div>
        ) : (
          <div>请传入正确格式</div>
        )}
      </div>
    );
  }
}

export default TreesMenu;

class TreeNode extends React.Component {
  renderItems(datas) {
    return (
      <List className="my-list">
        <List.Item>content 1</List.Item>
        <List.Item>content 2</List.Item>
        <List.Item>content 3</List.Item>
      </List>
    );
  }
  render() {
    let data = this.props.datas;
    return data && (!data.Childs || data.Childs.length === 0) ? (
      <div>
        <List className="my-list" onClick={() => {
            this.props.onselected(data)
        }}>
          <List.Item>{data.DeptName}</List.Item>
        </List>
      </div>
    ) : (
      <div>
        <Accordion
          onChange={this.onChange}
        >
          <Accordion.Panel header={data.DeptName} className="tree-accordion-panel">
            {data && data.Childs && data.Childs.map((item, index) => {
              return <TreeNode datas={item} onselected={(item) => this.props.onselected(item)} key={index}></TreeNode>;
            })}
          </Accordion.Panel>
        </Accordion>
      </div>
    );
  }
}

♥︎♥︎♥︎ 如果我进行三次setState会发生什么

如果是在原生js空间,则会同步执行,修改三次state的值,调用三次render函数;

如果是在react函数空间下,则会进行合并,只修改一次state的值,调用一次render。

♥︎♥︎♥︎ 渲染一个react组件的过程

1、babel编译
 当我们对代码进行编译的时候,babel会将我们在组件中编写的jsx代码转化为React.createElement的表达式,createElement方法有三个参数,分别为type(元素类型)、attributes(元素所有属性)、children(元素所有子节点);2、生成element
 当render方法被触发以后,createElement方法会执行,返回一个element对象,这个对象描述了真实节点的信息,其实就是虚拟dom节点;
3、生成真实节点(初次渲染)
 这时候我们会判断element的类型,如果是null、false则实例一个ReactDOMEmptyComponent对 象; 是string、number类型的话则实例一个ReactDOMTextComponent对象; 如果element是对象的话,会进一步判断type元素类型,是原生dom元素,则实例化ReactDOMComponent; 如果是自定义组件,则实例化ReactCompositeComponentWrapper;在这些类生成实例对象的时候,在其内部会调用 mountComponent方法,这个方法里面有一系列浏览器原生dom方法,可以将element渲染成真实的dom并插入到文档中;
 4、生命周期
 componentDidMount:会在组件挂载后(插入DOM树中) 立即调用。一般可以在这里请求数据;
 componentDidUpdate:会在数据更新后立即调用,首次渲染不会执行此方法;可以在其中直接调用 setState,但必须用if语句进行判断,防止死循环;
 conponentWillUnmount:会在组件卸载及销毁之前调用,在此方法中执行必要的清理操作,如清除timer;
 static getDerivedStateFromProps(prps,state):这个生命周期函数代替了componentWillMount和componentWillUpdate生命周期;props和state发生改变则调用,在初始化挂载及后续更新时都会被调用,返回一个对象来更新state,如果返回null则不更新任何内容;
 shouldComponentUpdate(nextProps,nextState):这个生命周期函数的返回值用来判断React组件是否因为当前 state 或 props 更改而重新渲染,默认返回值是true;这个方法在初始化渲染或使用forceUpdate()时不会调用;当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化。

♥︎♥︎♥︎ useEffect 和 useLayoutEffect 的区别

useEffect和useLayout都是副作用hooks,两则非常相似,同样都接收两个参数:

(1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函数,当页面刷新的或销毁的时候执行return后的代码;

(2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数;

useEffect和 useLayout的主要区别就是他们的执行时机不同,在浏览器中js线程与渲染线程是互斥的,当js线程执行时,渲染线程呈挂起状态,只有当js线程空闲时渲染线程才会执行,将生成的 dom绘制。

useLayoutEffect在js线程执行完毕即dom更新之后立即执行,而useEffect是在渲染结束后才执行,也就是说 useLayoutEffect比 useEffect先执行。

♥︎♥︎♥︎ hooks 的使用有什么注意事项

(1)只能在React函数式组件或自定义Hook中使用Hook。

(2)不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。

♥︎♥︎♥︎ 纯函数有什么特点,副作用函数特点

纯函数与外界交换数据只有一个唯一渠道——参数和返回值;函数从函数外部接受的所有输入信息都通过参数传递到该函数内部;函数输出到函数外部的所有信息都通过返回值传递到该函数外部。

纯函数的优点:无状态,线程安全;纯函数相互调用组装起来的函数,还是纯函数;应用程序或者运行环境可以对纯函数的运算结果进行缓存,运算加快速度。

函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。比如调接口、修改全局变量、抛出一个异常或以一个错误终止、打印到终端或读取用户输入、读取或写入一个文件等,所以说副作用是编程中最关键的部分,因为我们需要跟用户、跟数据进行交互。

♥︎♥︎♥︎ 在构造函数调用 super 并将 props 作为参数传入的作用是啥?

ES6 中在调用 super()方法之前,子类构造函数无法使用this引用,在react的类组件中也是如此;将props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props。

♥︎♥︎♥︎ 讲讲什么是 JSX ?

JSX全称为JavaScript XML,是react中的一种语法糖,可以让我们在js代码中脱离字符串直接编写html代码;本身不能被浏览器读取,必须使用@babel/preset-react和webpack等工具将其转换为传统的JS。

主要有以下特点:

(1)类XML语法容易接受,结构清晰;

(2)增强JS语义;

(3)抽象程度高,屏蔽DOM操作,跨平台;

(4)代码模块化;

♥︎♥︎♥︎ 为什么不直接更新 state 呢?

如果试图直接更新 state ,则不会重新渲染组件;需要使用setState()方法来更新 state这样组件才会重新渲染;

♥︎♥︎♥︎ 这三个点(…)在 React 干嘛用的?

…是es6语法新出的规范,叫做展开运算符;在react中可以将对象或数组进行展开,让我们操作改变数据结构非常方便。

♥︎♥︎♥︎ React 中的 useState() 是什么?

useState是一个内置的React Hook,可以让我们在函数组件中像类组件一样使用state并且改变state的值。

♥︎♥︎♥︎ React 中的StrictMode(严格模式)是什么?

React的StrictMode是一种辅助组件,用包装组件,可以帮助我们编写更好的react组件,不会渲染出任何可见的ui;仅在开发模式下运行,它们不会影响生产构建,可以做以下检查:

(1)验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告;

(2)验证是否使用的已经废弃的方法,如果有,会在控制台给出警告;

(3)通过识别潜在的风险预防一些副作用。

♥︎♥︎♥︎ 为什么类方法需要绑定到类实例?

在 JS 中,this 值会根据当前上下文变化。在 React 类组件方法中,开发人员通常希望 this 引用组件的当前实例,因此有必要将这些方法绑定到实例。通常这是在构造函数中完成的:

♥︎♥︎♥︎ 这段代码有什么问题吗?

this.setState((prevState, props) => {
 return {
 streak: prevState.streak + props.count
 }
})
// 没有问题

♥︎♥︎♥︎ 如何在 React 的 Props 上应用验证?

1、使用PropTypes进行类型检查

PropTypes自React v15.5起,请使用这个库prop-types

2、What & Why & When

随着应用的不断增长,也是为了使程序设计更加严谨,我们通常需要对数据的类型(值)进行一些必要的验证。出于性能方面的考虑,propTypes仅在开发模式下进行检测,在程序运行时就能检测出错误,不能使用到用户交互提醒用户操作错误等,也可以使用Flow或者TypeScript做类型检查,后期建议用typescript进行替代更好

3、Where

  • class组件
  • 函数组件
  • React.memo高阶组件 可自行扩展
  • React.forwardRef组件 可自行扩展

4、How

我们在组件类下添加一个静态属性 propTypes (属性名不能更改),它的值也是一个对象,用来设置组件中props的验证规则,key 是要验证的属性名称,value 是验证规则。

// 类组件
import PropTypes from 'prop-types';
class Greeting extends React.Component {
 render() {
 return ()
 }

♥︎♥︎♥︎ 如何有条件地向 React 组件添加属性?

对于某些属性,React足够智能可以忽略该属性,比如值为boolean值属性的值也可以写控制语句管理是否给组件添加属性

♥︎♥︎♥︎ 如何避免组件的重新渲染?

  1. 当porps/state改变时组件会执行render函数也就是重新渲染
  2. class组件中使用shouldComponentUpdate钩子函数
  3. PureComponent默认有避免重新渲染的功能
  4. 函数组件使用高阶组件memo处理

♥︎♥︎♥︎ 什么是纯函数?

一个不会更改入参,且多次调用下相同的入参始终返回相同的结果

♥︎♥︎♥︎ 如何避免在React重新绑定实例?

  1. 将事件处理程序定义为内联箭头函数
  2. 使用箭头函数来定义方法
  3. 使用带有 Hooks 的函数组件

♥︎♥︎♥︎ 在js原生事件中 onclick 和 jsx 里 onclick 的区别

1、js原生中

onclick添加事件处理函数是在全局环境下执行,污染了全局环境,且给很多dom元素添加onclick事件,影响网页的性能,同时如果动态的从dom树种删除了该元素,还要手动注销事件处理器,不然就可能造成内存泄露

2、jsx里的onClick

挂载的函数都控制在组件范围内,不会污染全局空间

jsx中不是直接使用onclick,而是采取了事件委托的方式,挂载最顶层DOM节点,所有点击事件被这个事件捕获,然后根据具体组件分配给特定函数,性能当然比每个onClick都挂载一个事件处理函数要高,加上React控制了组件的生命周期,在unmount的时候自然能够清除相关的所有事件处理函数,内存泄露不再是一个问题

♥︎♥︎♥︎ React组件间信息传递

1.(父组件)向(子组件)传递信息 : porps传值

2.(父组件)向更深层的(子组件) 进行传递信息 : context

3.(子组件)向(父组件)传递信息:callback

4.没有任何嵌套关系的组件之间传值(比如:兄弟组件之间传值): 利用共同父组件context通信、自定义事件

5.利用react-redux进行组件之间的状态信息共享 : 组件间状态信息共享:redux、flux、mobx等

♥︎♥︎♥︎ React状态管理工具有哪些?redux actionCreator都有什么?

简单状态管理:组件内部state、基于Context API封装

复杂状态管理:redux(单项数据流)、mobx(响应式数据流)、RxJS(stream)、dva

创建各种action,包含同步、异步,然后在组件中通过dispatch调用

♥︎♥︎♥︎ vuex 和 redux 的区别?

vuex的流向:

view——>commit——>mutations——>state变化——>view变化(同步操作)
view——>dispatch——>actions——>mutations——>state变化——>view变化(异步操作)

redux的流向:

view——>actions——>reducer——>state变化——>view变化(同步异步一样)

Redux相对于Flux的改进:

(1)把store和Dispatcher合并,结构更加简单清晰

新增state角色,代表每个时间点store对应的值,对状态的管理更加明确

Redux数据流的顺序是:

(2)View调用store.dispatch发起Action->store接受Action(action传入reducer函数,reducer函数返回一个新的state)->通知store.subscribe订阅的重新渲染函数

Vuex是专门为Vue设计的状态管理框架, 同样基于Flux架构,并吸收了Redux的优点

Vuex相对于Redux的不同点有:

(1)改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可

(2)由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可

(3)Vuex数据流的顺序是:View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)

♥︎♥︎♥︎ Redux遵循的三个原则是什么?

1、单一数据源

整个应用的state被存储在一棵object tree中,并且整个 object tree 只存在于唯一一个 store 中

2、State是只读的

唯一改变state的方法就是触发 action,action是一个描述已发生事件的普通对象,这样确保视图和网络请求不能直接修改state

3、使用纯函数来执行修改

为了描述action如何改变state tree,你需要编写reducers

♥︎♥︎♥︎ React中的keys的作用是什么?

key 是用来帮助 react 识别哪些内容被更改、添加或者删除。key 需要写在用数组渲染出来的元素内部,并且需要赋予其一个稳定的值。稳定在这里很重要,因为如果 key 值发生了变更,react 则会触发 UI 的重渲染。这是一个非常有用的特性。

1、key 的唯一性

在相邻的元素间,key 值必须是唯一的,如果出现了相同的 key,同样会抛出一个 Warning,告诉相邻组件间有重复的 key 值。并且只会渲染第一个重复 key 值中的元素,因为 react 会认为后续拥有相同key 的都是同一个组件。

2、key 值不可读

虽然我们在组件上定义了 key,但是在其子组件中,我们并没有办法拿到 key 的值,因为 key 仅仅是给react 内部使用的。如果我们需要使用到 key 值,可以通过其他方式传入,比如将 key 值赋给 id 等

♥︎♥︎♥︎ redux中使用setState不能立刻获取值,怎么办

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

①addeventListener添加的事件或者dom事件中触发

②setState接收的参数还可以是一个函数,在这个函数中可以拿先前的状态,并通过这个函数的返回值得到下一个状态。

 this.setState((preState) => {
 return {
 xxx: preState.xxx + yyy
 }
})

③async/await 异步调用处理

♥︎♥︎♥︎ React新老版生命周期函数

New Version

挂载:constructor –> getDerivedStateFromProps –> render –> componentDidMount

更新:

setState() –> getDerivedStateFromProps –> shouldComponentUpdate –> render –>getSnapshotBeforeUpdate –> componentDidUpdate

forceUpdate() –> getDerivedStateFromProps –> render –> getSnapshotBeforeUpdate –>componentDidUpdate

卸载: componentWillUnmount

Old Version

挂载:constructor –> getDerivedStateFromProps –> render –> ComponentDidMount

更新:

New props –> getDerivedStateFromProps –> shouldComponentUpdate –> render –>getSnapshotBeforeUpdate –> componentDidUpdate

setState() –> shouldComponentUpdate –> render –> getSnapshotBeforeUpdate –>componentDidUpdate

forceUpdate() –> render –> getSnapshotBeforeUpdate –> componentDidUpdate

卸载:componentWillUnmount

♥︎♥︎♥︎ React中怎么让 setState 同步更新?

setState 回调,setState,第二个参数是一个回调函数,可实现同步

引入 Promise 封装 setState,在调用时我们可以使用 Async/Await 语法来优化代码风格

setStateAsync(state) {
 return new Promise((resolve) => {
 this.setState(state, resolve)
 });
}

//传入状态计算函数, setState 的第一个参数,
this.setState((prevState, props) => ({ count: prevState.count + 1
}));

//在 setTimeout 函数中调用 setState

♥︎♥︎♥︎ 为什么不建议在 componentWillMount 做AJAX操作

Fiber原因,React16之后,采用了Fiber架构,只有componentDidMount的生命周期函数确定会执行一次,其他像componentWillMount可能会执行多次

render 阶段 可能会被React暂停,中止或重启

♥︎♥︎♥︎ 怎么用useEffect模拟生命周期函数?

  • 默认函数组件没有生命周期
  • 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期
  • 通过Effect hook把生命周期“钩”到纯函数中
    // 模拟 class 组件的 DidMount 和 DidUpdate
    useEffect(() => {
      console.log('在此发送一个 ajax 请求')
    })
 
    // 模拟 class 组件的 DidMount
     useEffect(() => {
        console.log('加载完了')
     }, []) // 第二个参数是 [] (不依赖于任何 state)
 
     // 模拟 class 组件的 DidUpdate
     useEffect(() => {
         console.log('更新了')
     }, [count, name]) // 第二个参数就是依赖的 state
 
    // 模拟 class 组件的 DidMount
    useEffect(() => {
        let timerId = window.setInterval(() => {
            console.log(Date.now())
        }, 1000)
 
        // 返回一个函数
        // 模拟 WillUnMount 组件销毁的时候 停止计时器
        return () => {
            window.clearInterval(timerId)
        }
    }, [])
  • 模拟componentDidMount - useEffect 依赖 [ ]
  • 模拟compenentDidUpdate - useEffect 无依赖 ,或者 依赖 [a,b,c]
  • 模拟componentWillUnMount - useEffect 中返回一个函数

♥︎♥︎♥︎ 各种useEffect使用情况?

1、默认情况下,它在第一次渲染之后和每次更新之后都会执行,无需清除的effect

// 在函数式组件中 在 return之前
// Similar to componentDidMount and componentDidUpdate:
 useEffect(() => {
 // Update the document title using the browser API
 document.title = You clicked ${count} times ;
 });

2、需要清除的effect:React 会在组件卸载的时候执行清除操作

 useEffect(() => {
 function handleStatusChange(status) {
 setIsOnline(status.isOnline);
 }
 ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
 // Specify how to clean up after this effect:
 return function cleanup() {
 ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
 };
 });

3、使用多个effect实现关注点的分离

把类组件中的分散在多个生命周期中的同一件事件的处理,合并到同一个effect中处理

4、通过跳过effect进行性能优化

useEffect(() => {
 document.title = You clicked ${count} times ;
}, [count]); // 仅在 count 更改时更新

♥︎♥︎♥︎ useCallback是干什么的?使用useCallback有什么好处?

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized(缓存)版本,该回调函数仅在某个依赖项改变时才会更新 好处 当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用

♥︎♥︎♥︎ 能简单说一下redux-sage的使用流程吗?

redux-saga 是一个用于管理 Redux 应用异步操作的中间件(又称异步 action)。 redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。

Reducers 负责处理 action 的 state 更新

Sagas 负责协调那些复杂或异步的操作

1、connet to the store:本质是管理 Redux 应用异步操作的中间件

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// Create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// Mount it on the Store
const store = createStore(
 reducer,
 applyMiddleware(sagaMiddleware)
)
// Then run the saga
sagaMiddleware.run(mySaga)
// Render the application

2、initiate a side effect:初始化副作用

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
// Worker saga will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
 try {
 const user = yield call(Api.fetchUser, action.payload.userId);
 yield put({type: "USER_FETCH_SUCCEEDED", user: user});
 } catch (e) {
 yield put({type: "USER_FETCH_FAILED", message: e.message});
 }
}
// Starts fetchUser on each dispatched USER_FETCH_REQUESTED action
// Allows concurrent fetches of user
function* mySaga() {
 yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

3、dispath an action:组件中使用

 ...
 onSomeButtonClicked() {
 const { userId, dispatch } = this.props
 dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
 }
 ...
}

4、more:takeEvery、takeLatest、take、put、call、fork、select

♥︎♥︎♥︎ redux 和 mobx 的区别

1、Redux的编程范式是函数式的而Mobx是面向对象的

2、因此数据上来说Redux理想的是immutable的,每次都返回一个新的数据,而Mobx从始至终都是一份引用。因此Redux是支持数据回溯的

3、然而和Redux相比,使用Mobx的组件可以做到精确更新,这一点得益于Mobx的observable;对应的,Redux是用dispatch进行广播,通过Provider和connect来比对前后差别控制更新粒度,有时需要自己写SCU;Mobx更加精细一点

4、Mobx-react vs React-rdux:

redux,采取Provider和connect方式,mobx采取Provider和inject、observer

♥︎♥︎♥︎ react中如何实现命名插槽

由于在React组件中写的内容会被挂载到props中,以此来实现类似vue中的插槽功能

这是最外层代码

import React, { Component } from 'react'
import NavBar from './NavBar'
import NavBar2 from './NavBar2'

export default class App extends Component {
  render() {
    return (
      <div>
        <NavBar>
          <span>aaa</span>
          <strong>bbb</strong>
          <a href="/#">ccc</a>
        </NavBar>

        <NavBar2 leftslot={<span>aaa</span>}
        centerslot={<strong>bbb</strong>}
        rightslot={<a href="/#">ccc</a>}/>
      </div>
    )
  }
}

1.用this.props.children[index]

import React, { Component } from 'react'

import './style.css'
export default class NavBar extends Component {
  render () {
    return (
      <div className="nav-bar">
        <div className="nav-left">
          {this.props.children[0]}
        </div>
        <div className="nav-center">
          {this.props.children[1]}
        </div>
        <div className="nav-right">
          {this.props.children[2]}
        </div>
      </div>
    )
  }
}

2.用直接命名方式

import React, { Component } from 'react'

import './style.css'
export default class NavBar extends Component {
  render () {
    const {leftslot, centerslot,rightslot} = this.props
    return (
      <div className="nav-bar">
        <div className="nav-left">
          {leftslot}
        </div>
        <div className="nav-center">
          {centerslot}
        </div>
        <div className="nav-right">
          {rightslot}
        </div>
      </div>
    )
  }
}

♥︎♥︎♥︎ 简单说一下,如何在react中实现瀑布流加载?(左右两列的一个商品长列表)

根据红线,将数据分为两部分,然后根据两边的高度(哪边少往那边加内容)去渲染两个盒子,然后达到一个瀑布流的效果

import React, { Component,Fragment } from 'react';
import {connect} from'react-redux'
import Axios from '_axios@0.19.0@axios';
class Waterfall extends Component {
 constructor(props) {
 super(props);
 this.state = { 
 data:[],//整体的数据
 leftData:[],//左边的数据
 rightData:[]//右边的数据
 }
 }
 getHW(data){
 let heightDate = [0,0];//接收累计高度的容器数组
 let rightData =[]//渲染右侧盒子的数组
 let leftData = []//渲染左侧盒子的数组
 data.forEach(item => {
 let height = item.src.replace('http://dummyimage.com/','').substr(0,7).split('x')[1]*1;//对url地址进行一
个截取,拿到高度
 let minNum = Math.min.apply(null,heightDate)// 从heighetData筛选最小项
 let minIndex = heightDate.indexOf(minNum);// 获取 最小项的小标 准备开始进行累加
 heightDate[minIndex] = heightDate[minIndex] + height;//从 heightData 中找到最小的项后进行累加,
 if(minIndex===0){//[0]加到left [1]加到 right
 leftData.push(item)
 }else{
 rightData.push(item)
 }
 })
 this.setState({ leftData,rightData });//重新set state
 }
 render() { 
 let {leftData,rightData} = this.state;
 console.log(leftData,rightData)
 return ( 
 
 
{ leftData && leftData.map((item,index)=>{ return }) } { rightData && rightData.map((item,index)=>{ return }) }
); } componentDidMount(){ Axios.get('/api/data').then(res=>{ this.props.dispatch({ type:'SET_DATA',
data:res.data.data }) this.getHW(this.props.data) //调用 }) } }
export default connect(
 (state)=>{
 return{
 data:state.data,
 }
 }
)(Waterfall);

♥︎♥︎♥︎♥︎ React hooks 用过吗,为什么要用?

Hooks 是React在16.8版本中出的一个新功能,本质是一种函数,可以实现组件逻辑复用,让我们在函数式组件中使用类组件中的状态、生命周期等功能,hooks的名字都是以use开头。

1、useState——创建状态 接收一个参数作为初始值;返回一个数组,第一个值为状态,第二个值为改变状态的函数 
2、useEffect——副作用(数据获取、dom操作影响页面——在渲染结束之后执行)
        (1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函 数,当页面刷新的时候先执行返回函数再执行参数函数 
        (2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调 用useEffect的回调函数 
3、useRef 返回一个可变的ref对象,此索引在整个生命周期中保持不变。可以用来获取元素或组件的实例,用来做 输入框的聚焦或者动画的触发。 
4、useMemo——优化函数组件中的功能函数——在渲染期间执行 
    (1)接收一个函数作为参数,同样接收第二个参数作为依赖列表,返回值可以是任何,函数、对象等都可 以 
    (2)这种优化有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算 
5、useContext——获取上下文注入的值 
        (1)接受一个context 对象,并返回该对象<MyContext.Provider> 元素的 value值; const value = useContext(MyContext); 
6、useLayoutEffect——有DOM操作的副作用——在DOM更新之后执行 和useEffet类似,但是执行时机不同,useLayoutEffect在DOM更新之后执行, useEffect在render渲染结束后执行,也就是说useLayoutEffect比useEffect先执行,这是因为DOM更 新之后,渲染才结束或者渲染还会结束 
7、useCallback——与useMemo类似
useMemo与useCallback相同,接收一个函数作为参数,也同样接收第二个参数作为依赖列 表;useCallback是对传过来的回调函数优化,返回的是一个函数
react-router:
 被route包裹的组件,可以直接使用props进行路由相关操作,但是没有被route包裹的组件只能用withRouter高阶组件修饰或者使用hooks进行操作
 1、useHistory——跳转路由
 2、useLocation——得到url对象
 3、useParams——得到url上的参数

react-redux:
 1、useSelector——共享状态——从redux的store中提取数据
 2、useDispatch——共享状态——返回redux的store中对dispatch的引用

♥︎♥︎♥︎♥︎ 虚拟DOM的优劣如何?实现原理?

虚拟dom是用js模拟一颗dom树,放在浏览器内存中,相当于在js和真实dom中加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。

优点:

(1)虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能;

(2)虚拟DOM不会立马进行排版与重绘操作,对虚拟DOM进行频繁修改,最后一次性比较并修改真实DOM中需要改的部分;

(3)虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部;

缺点:

(1)首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢;

React组件的渲染过程: 
(1)使用JSX编写React组件后所有的JSX代码会通过Babel转化为 React.createElement执行; 
(2)createElement函数对 key和 ref等特殊的 props进行处理,并获取 defaultProps对默认 props进行赋值,并且对传入的子节点进行处理,最终构造成一个 ReactElement对象(所谓的虚拟 DOM)。
(3)ReactDOM.render将生成好的虚拟 DOM渲染到指定容器上,其中采用了批处理、事务等机制并且 对特定浏览器进行了性能优化,最终转换为真实 DOM。

虚拟DOM的组成——ReactElementelement对象结构: 
(1)type:元素的类型,可以是原生html类型(字符串),或者自定义组件(函数或class) 
(2)key:组件的唯一标识,用于Diff算法,下面会详细介绍 
(3)ref:用于访问原生dom节点 
(4)props:传入组件的props,chidren是props中的一个属性,它存储了当前组件的孩子节点,可 以是数组(多个孩子节点)或对象(只有一个孩子节点) 
(5)owner:当前正在构建的Component所属的Component 
(6)self:(非生产环境)指定当前位于哪个组件实例 
(7)_source:(非生产环境)指定调试代码来自的文件(fileName)和代码行数(lineNumber)

♥︎♥︎♥︎♥︎ React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?

react的diff算法只需要O(n),这是因为react对树节点的比较做了一些前提假设,限定死了一些东西,不做过于复杂的计算操作,所以降低了复杂度。react和vue做了以下的假设,这样的话diff运算时只进行同层比较,每一个节点只遍历了一次。

(1)Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计;

(2)拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构;

(3)对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。

而传统的diff运算时间复杂度为O(n^3),这是因为传统的树节点要做非常完整的检查,首先需要节点之 间需要两两比较,找到所有差异,这个对比过程时间复杂度为O(n^2),找到差异后还要计算出最小的转换方式,最终复杂度为O(n^3)

♥︎♥︎♥︎♥︎ 请列举react生命周期函数。

阶段 生命周期
第一阶段:装载阶段3 constructor() render() componentDidMount()
第二阶段:更新阶段2 shouldComponentUpdate() render() componentDidUpdate()
第三阶段:卸载阶段1 componentWillUnmount()

constructor生命周期:

(1)当react组件实例化时,是第一个运行的生命周期;

(2)在这个生命周期中,不能使用this.setState();

(3)在这个生命周期中,不能使用副作用(调接口、dom操作、定时器、长连接等);

(4)不能把props和state交叉赋值;

componentDidMount生命周期:

(1)相当于是vue中的mounted;

(2)它表示DOM结构在浏览器中渲染已完成;

(3)在这里可以使用任何的副作用;

shouldComponentUpdate(nextProps,nextState)生命周期:

(1)相当于一个开关,如果返回true则更新机制正常执行,如果为false则更新机制停止;

(2)在vue中是没有的;

(3)存在的意义:可以用于性能优化,但是不常用,最新的解决方案是使用PureComponent;

(4)理论上,这个生命周期的作用,用于精细地控制声明式变量的更新问题,如果变化的声明式变量参与了视图渲染则返回true,如果被变化的声明式变量没有直接或间接参与视图渲染,则返回false;

componentDidUpdate生命周期:

(1)相当于vue中的updated();

(2)它表示DOM结构渲染更新已完成,只发生在更新阶段;

(3)在这里,可以执行大多数的副作用,但是不建议;

(4)在这里,可以使用this.setState(),但是要有终止条件判断。

componentWillUnmount生命周期:

(1)一般在这里清除定时器、长连接等其他占用内存的构造器;render生命周期:

(1)render是类组件中唯一必须有的生命周期,同时必须有return(return 返回的jsx默认只能是单一根节点,但是在fragment的语法支持下,可以返回多个兄弟节点);

(2)Fragment碎片写法: <React.Fragment></React.Fragment> 简写成<></>;

(3)return之前,可以做任意的业务逻辑,但是不能使用this.setState(),会造成死循环;

(4)render()在装载阶段和更新阶段都会运行;

(5)当render方法返回null的时候,不会影响生命周期函数的正常执行。

♥︎♥︎♥︎♥︎ 新出来两个钩子函数?和砍掉的will系列有啥区别?

/ react16 中废弃了三个钩子
componentWillMount // 组件将要挂载的钩子
componentWillReceiveProps // 组件将要接收一个新的参数时的钩子
componentWillUpdate // 组件将要更新的钩子
/ 新增了方法
getDerivedStateFromProps // 静态方法
getSnapshotBeforeUpdate

在16.8版本以后,react将diff运算改进为Fiber,这样的话当我们调用setState方法进行更新的时候,在reconciler 层中js运算会按照节点为单位拆分成一个个小的工作单元,在render前可能会中断或恢复,就有可能导致在render前这些生命周期在进行一次更新时存在多次执行的情况,此时如果我们在里面使用ref操作dom的话,就会造成页面频繁重绘,影响性能。 所以废弃了这几个will系列的勾子,增加了 getDerivedStateFromProps这个静态方法,这样的话我们就不能在其中使用this.refs以及this上的方法了;getSnapshotBeforeUpdate 这个方法已经到了commit阶段,只会执行一次,给想读取 dom 的用户一些空间。

♥︎♥︎♥︎♥︎ react中 setState 之后做了什么?

如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法,渲染数据;

如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;

♥︎♥︎♥︎♥︎ redux本来是同步的,为什么它能执行异步代码?中间件的实现原理是什么?

当我们需要修改store中值的时候,我们是通过 dispatch(action)将要修改的值传到reducer中的,这个过程是同步的,如果我们要进行异步操作的时候,就需要用到中间件;中间件其实是提供了一个分类处理action的机会,在 middleware 中,我们可以检阅每一个流过的action,并挑选出特定类型的action进行相应操作,以此来改变 action;

applyMiddleware 是个三级柯里化的函数。它将陆续的获得三个参数:第一个是 middlewares 数 组,第二个是 Redux 原生的 createStore,最后一个是 reducer;然后applyMiddleware会将不同的 中间件一层一层包裹到原生的 dispatch 之上; 
redux-thunk 中间件的作用就是让我们可以异步执行redux,首先检查参数 action 的类型,如果 是函数的话,就执行这个 action这个函数,并把 dispatch, getState, extraArgument 作为参数传 递进去,否则就调用next让下一个中间件继续处理action。
// redux-thunk部分源码
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

♥︎♥︎♥︎♥︎ 列举重新渲染 render 的情况

  1. this.setState()
  2. this.forceUpdate()
  3. 接受到新的props
  4. 通过状态管理,mobx、redux等
  5. 改变上下文

♥︎♥︎♥︎♥︎ 类组件怎么做性能优化?函数组件怎么做性能优化?

类组件:

 (1)使用shouldComponentUpdate:这个生命周期可以让我们决定当前状态或属性的改变是否重新渲染组件,默认返回ture,返回false时不会执行render,在初始化渲染或使用forceUpdate()时不会调用;如果在shouldComponentUpdate比较的值是引用类型的话,可能达不到我们想要的效果,因为引用类型指向同一个地址;当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化;

 (2)React.PureComponent:基本上和Component用法一致,不同之处在于 PureComponent不需要开发者自己设置shouldComponentUpdate,因为PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate;但是如果props和state对象包含复杂的数据结构,它可能会判断错误(表现为对象深层的数据已改变,视图却没有更新);

 (4)使用Immutable:immutable是一种持久化数据,一旦被创建就不会被修改,修改immutable对象的时候返回新的immutable;也就是说在使用旧数据创建新数据的时候,会保证旧数据同时可用且不变;为了避免深度复制所有节点的带来的性能损耗,immutable使用了结构共享,即如果对象树中的一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点仍然共享;

 (5)bind函数:在react中改变this的指向有三种方法,a)constructor中用bind绑定; b)使用时通过bind绑定; c)使用箭头函数;选择第一种只在组件初始化的时候执行一次,第二种组件在每次render都要重新绑定,第三种在每次render时候都会生成新的箭头函数,所以选择第一种;

函数组件:

 (1)useCallback:接收一个函数作为参数,接收第二个参数作为依赖列表,返回值为函数,有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算;可以使用useCallback把要传递给子组件的函数包裹起来,这样父组件刷新的时候,传递给子组件的函数指向不会发生改变,可以减少子组件的渲染次数;

 const handleUseCallback=useCallback(handleClick,[])

 (2)useMemo:useMemo的使用和useCallback差不多,只是useCallback返回的是一个函数,useMemo返回值可以是函数、对象等都可以;

两者都可使用:

(1)React.memo:React.memo 功能同React.PureComponent,但React.memo是高阶组件,既可以用在类组件中也可以用在函数组件中;memo还可以接收第二个参数,是一个可定制化的比较函数,其返回值与 shouldComponentUpdate的相反;

(2)使用key:在列表渲染时使用key,这样当组件发生增删改、排序等操作时,diff运算后可以根据key值直接调整DOM顺序,避免不必要的渲染而避免性能的浪费;

(3)不要滥用props:尽量只传需要的数据,避免多余的更新,尽量避免使用{…props};

♥︎♥︎♥︎♥︎ 什么是 prop drilling,如何避免?

从一个外部组件一层层将prop传递到内部组件很不方便,这个问题就叫做 prop drilling;主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护,代码看起来也变得冗余,不优雅;

为了避免prop drilling,一种常用的方法是使用React Context。通过定义提供数据的Provider组件,并允许嵌套的组件通过 Consumer组件或 useContext Hook 使用上下文数据。

♥︎♥︎♥︎♥︎ 什么是 React Context?

React Context源码解析

1、What

Context提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的功能

2、Why

某些全局属性,通过父子props传递太过繁琐,Context提供了一种组件之间共享此类值的方式,而不必显式的通过组件树逐层传递props

3、When

共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或者首选语言等

4、Where

Context应用场景在于很多不同层级的组件访问同样的数据,这样也使得组件的复用性变差。

如果你只是想避免层层传递一些属性,组件组合有时候是一个比Context更好的方案,也就是直接传递组件

所以一个技术方案的选定需要针对不同的场景具体分析,采取合适的方案

5、How

// ①创建

const ThemeContext = React.createContext('xxx')

// ②注入---提供者 在入口或者你想要注入的父类中,且可以嵌套,里层覆盖外层

return (
<ThemeContext.Provider value="yyy">
{children}
<ThemeContext.Provider>
)

// ③使用---消费者 需要使用共享数据的子类中

// 方式一
static contextType = ThemeContext
// 方式二
Class.contextType = ThemeContext 
render() {
let value = this.context
/* 基于这个值进行渲染工作 */ 
}
//方式三
return(
<ThemeContext.Consumer>
{ value => /* 基于 context 值进行渲染*/ }
</ThemeContext.Consumer>
)

6、More

动态Context—类似父子组件

// ①创建

const ThemeContext = React.createContext({
value: 'xxx',
changeFunc: () => {} /*通过context传递这个函数,让consumers组件更新context*/
})

// ②注入
return (
<ThemeContext.Provider value="yyy">
<ThemeContext.Provider>
)

// ③消费

return(
<ThemeContext.Consumer>
{ ({value, changeFunc}) => /* 基于 context 值进行渲染,同时把changeFunc绑定*/ }
 </ThemeContext.Consumer>
)
消费多个Context、注意事项等参考React中文网

♥︎♥︎♥︎♥︎ Hooks 会取代 render props 和高阶组件吗?

可以取代,但没必要

在Hook的渐进策略中也有提到,没有计划从React中移除class,在新的代码中同时使用Hook和class,所以这些方案目前还是可以有勇武之地

1、What

为什么要把这3种技术拿过来对比?

都在处理同一个问题,逻辑复用

高阶组件HOC—不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。

高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)

Render Props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的 简单技术

Hooks 是React16.8新增的特性,是一些可以让你在函数组件里“钩入”React state及生命周期等特性的函数

虽然 HOC & Render Props 能处理逻辑复用的问题,但是却存在各自的问题。

HOC 存在的问题

  • 写法破坏了原来组件的结构,DevTools中组件会形成“嵌套地狱”
  • 不要在 render 方法中使用 HOC 每次调用render函数会创建一个新的高阶组件导致该组件及其子组件的状态丢失
  • 需要修复静态方法,即拷贝原组件的静态方法到高级组件中
  • 如需传递Ref则需要通过React.forwardRef创建组件

Render Props 存在的问题

  • 同样的写法会破坏原来组件的结构,DevTools中组件会形成“嵌套地狱”
  • 与React.PureComponent组件使用有冲突
  • Hook 目前最优雅的实现,React为共享状态逻辑提供最好的原生途径
  • 没有破坏性改动,完全可选,100%向后兼容
  • 解决复杂组件,中逻辑状态、副作用和各种生命周期函数中逻辑代码混在一起,难以拆分,甚至形成bug的问题

2、When

  • 在函数组件中意识到要向其添加一些state—useState
  • 有副作用的行为时

3、Where

只能在函数最外层调用Hook,不要在循环、条件判断或者子函数中调用

只能在函数组件或者自定义Hook中调用Hook

♥︎♥︎♥︎♥︎ 当调用setState时,React render 是如何工作的?

调用setState()

  1. 检查上下文环境生成更新时间相关参数并判定事件优先级(fiber,currenttime,expirationtime等…)
  2. 根据优先级相关参数判断更新模式是sync(同步更新)或是batched(批量处理)
  3. 加入执行更新事件的队列,生成事件队列的链表结构
  4. 根据链表顺序执行更新
  5. setState既是同步的,也是异步的。同步异步取决于setState运行时的上下文。且setState 只在合成事件和钩子函数中是“异步”的,在原生DOM事件和 setTimeout 中都是同步的

render如何工作

  1. React在props或state发生改变时,会调用React的render方法,创建一颗不同的树
  2. React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI diff算法,将两颗树完全比较更新的算法从O(n^3^),优化成O(n);
  3. 同层节点之间相互比较,不会跨节点比较
  4. 不同类型的节点,产生不同的树结构
  5. 设置key来指定节点在不同的渲染下保持稳定

♥︎♥︎♥︎♥︎ diff复杂度原理及具体过程画图

  • React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
  • React 通过分层求异的策略,对 tree diff 进行算法优化;
  • React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
  • React 通过设置唯一 key的策略,对 element diff 进行算法优化;
  • 建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;
  • 建议,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

♥︎♥︎♥︎♥︎ shouldComponentUpdate的作用是什么?

shouldComponentUpdate的作用

1、What

不常用的生命周期方法,能影响组件是否重新渲染

在更新阶段,当有new props 或者 调用了 setState()方法,在render方法执行前会执行到,默认返回值为true,如果返回false则不刷新组件

2、Why & When & Where

  1. 如果你知道在什么情况下组件不需要更新,你可以让其返回值为false跳过整个渲染过程
  2. 次方法仅作为 性能优化方式 而存在,不要企图靠此方法来阻止渲染,
  3. 大部分情况下,使用PureComponent代替手写shouldComponentUpdate,仅浅层对比
  4. 不建议在shoulComponentUpdate中进行深层或者使用JSON.stringify(),这样非常影响效率和性能
  5. 作为React组件中不常用的生命周期函数,能影响组件是否重渲染
  6. 建议做浅层次的比较,来优化性能,当然这里也可以用PureComponent组件代替
  7. 如果有较深层次的比较则可能会导致更严重的性能问题,因此在这种情况下不要靠手动管理组件的重新渲染来优化性能,要找其他方式

♥︎♥︎♥︎♥︎ 什么是高阶组件、受控组件及非受控组件?都有啥区别

1、定义

高阶组件HOC—不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。

高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)

2、受控组件

在表单元素中,state是唯一数据源,渲染表单的React组件控制着用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素叫做受控组件

3、非受控组件

表单数据由DOM节点来处理,而不是用state来管理数据,一般可以使用ref来从DOM节点中获取表单数据

‼️区别

  • 受控组件和非受控组件是表单中的组件,高阶组件相当于对某个组件注入一些属性方法
  • 高阶组件是解决代码复用性问题产生的技术
  • 受控组件必须要有一个value,结合onChange来控制这个value,取值为event.target.value/event.target.checked
  • 非受控组件相当于操作DOM,一般有个defaultValue,通过onBlur触发响应方法

♥︎♥︎♥︎♥︎ vue react都怎么检测数据变化

React

React默认是通过比较引用的方式(diff)进行的,不精确监听数据变化,如果不优化可能导致大量不必要的VDOM重新渲染

16之前 componentWillReveiveProps 监听 props 变化

16之后 getDerivedStateFromProps 监听 props

Vue

vue监听变量变化依靠 watch Object.defineProperty,Vue通过“getter/setter”以及一些函数的劫持,能精确知道数据变化

♥︎♥︎♥︎♥︎ 什么是 immutable?为什么要使用它?

immutable是一种持久化数据。一旦被创建就不会被修改。修改immutable对象的时候返回新的immutable。但是原数据不会改变。

在Rudux中因为深拷贝对性能的消耗太大了(用到了递归,逐层拷贝每个节点)。 但当你使用immutable数据的时候:只会拷贝你改变的节点,从而达到了节省性能。

总结:immutable的不可变性

让纯函数更强大,每次都返回新的immutable的特性让程序员可以对其进行链式操作,用起来更方便。

因为在react中,react的生命周期中的setState()之后的shouldComponentUpdate()阶段默认返回true,所以会造成本组件和子组件的多余的render,重新生成virtual dom,并进行virtual dom diff,所以解决办法是我们在本组件或者子组件中的shouldComponentUpdate()函数中比较,当不需要render时,不render。

当state中的值是对象时,我们必须使用深拷贝和深比较!

如果不进行深拷贝后再setState,会造成this.state和nextState指向同一个引用,所以shouldComponentUpdate()返回值一定是false,造成state值改了,而组件未渲染(这里不管shouldComponentUpdate中使用的是深比较还是浅比较)。所以必须深拷贝。

如果不在shouldComponentUpdate中进行深比较,会造成即使state中的对象值没有改变,因为是不同的对象,而在shouldComponentUpdate返回true,造成不必要的渲染。

所以只能是深拷贝和深比较。

♥︎♥︎♥︎♥︎ React路由懒加载的实现

原理

webpack代码分割

React利用 React.lazy与import()实现了渲染时的动态加载

利用Suspense来处理异步加载资源时页面应该如何显示的问题

1.React.lazy

通过lazy() api来动态import需要懒加载的组件

import的组件目前只支持export default的形式导出

Suspense来包裹懒加载的组件进行加载,可以设置fallback现实加载中效果

React.lazy可以结合Router来对模块进行懒加载。

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'))
const AnyComponent = lazy(() => import('./routes/AnyComponent'))
...
return (
 <Suspense fallback={
Loading...
}> ...

2.react-loadable

react-loadable是以组件级别来分割代码的,这意味着,我们不仅可以根据路由按需加载,还可以根据组件按需加载,使用方式和路由分割一样,只用修改组件的引入方式即可

// 路由懒加载(异步组件)
import Loadable from 'react-loadable';
//通用过场组件
const LoadingComponent = () => {
 return (
loading
) } ... export default (loader, loading=LoadingComponent) => { return Loadable({ loader, loading }) }
//Route中调用
import { BrowserRouter, Route } from 'react-router-dom'
const loadable from './loadable';
const AnyComponent = loadable(() => import('./AnyComponent'))
const Routes = () => (
);
export default Routes;

以下是老版中的方法

3.webpack配置中使用lazyload-loader

// webpack 配置中
module: {
 rules: [
 {
 test: /.(js|jsx)$/,,
 use: [
 'babel-loader',
 'lazyload-loader'
 ]
},
// 业务代码中
// 使用lazy! 前缀 代表需要懒加载的Router
 import Shop from 'lazy!./src/view/Shop';
 // Router 正常使用

4.import() webpack v2+

符合ECMAScript提议的import()语法,该提案与普通 import 语句或 require 函数的类似,但返回一个 Promise 对象

function component() {
 return import( /* webpackChunkName: "lodash" / 'lodash').then(_ => {
 var element = document.createElement('div');
 element.innerHTML = _.join(['Hello', 'webpack'], ' ');
 return element;
 }).catch(error => 'An error occurred while loading the component');
}
// 或者使用async
async function getComponent() {
 var element = document.createElement('div');
 const _ = await import(/ webpackChunkName: "lodash" */ 'lodash');
 element.innerHTML = _.join(['Hello', 'webpack'], ' ');
 return element;
}

5.requre.ensure webpack v1 v2

require.ensure([], function(require){
 var list = require('./list');
 list.show();
,'list');
const Foo = require.ensure([], () => {
 require("Foo");
}, err => {
 console.error("We failed to load chunk: " + err);
}, "chunk-name");
//react-router2 or 3

♥︎♥︎♥︎♥︎ React-router-dom内部是怎么样实现的,怎么做路由守卫?

1、总

react-router-dom利用了Context API,通过上下文对象将当前路由信息对象注入到 Router 组件中,所以 Router组件中 render() 渲染的内容就是 ContextAPI 提供的 Provider 组件,然后接收 Router 组件中的当前路由信息对象。 这样 Router 组件下的所有组件都能通过上下文拿到当前路由信息对象,即其中的Switch 、 Route 、 Link 、Redirect 等组件都可以拿到当前路由信息对象,然后通过改变当前路由信息来实现动态切换 Route 组件的渲染。

2、分

RouterContext:react-router使用context实现跨组件间数据传递,所以react-router定义了一个routerContext作为数据源,

Router:BrowserRouter和HashRouter将当前路由注入到上下文中,同时路由信息包含location、match、history

Route:路由规则,获取RouterContext的信息(location对象),获取path和component属性,判断path和当前的location是否匹配,如果匹配,则渲染component,否则返回null,不渲染任何内容

Switch:遍历所有子元素(Route),判断Route的path和location是否匹配,如果匹配,则渲染,否则不渲染

Redireact:未能配则重定向到指定页面

Link/NavLink: Link组件本质就是a标签,它修改了a标签的默认行为,当点击Link时,会导航到对应的路由,导致locaiton对象的改变,出发组件的更新

withRouter:对传入的组件进行加强,功能就是获取routerContext上面的信息,然后作为props传给需要加强的组件

3、怎么做路由守卫

路由里设置meta元字符实现路由拦截

React Router 4.0之前也像vue中一样有个钩子函数 onEnter 可实现

ReactRouter 4.0开始自己实现如下

// routerMap.js中
import Index from './page/index'
export default [
 { path:'/', name: 'App', component:Index, auth: true },
 ...
]
//入口文件 app.js中
import { BrowserRouter as Router, Switch } from "react-router-dom";
import FrontendAuth from "./FrontendAuth";
import routerMap from "./routerMap";
...
return (
 
 
)
// 高阶组件FrontendAuth 处理路由跳转,即路由守卫功能
//FrontendAuth.js
import React, { Component } from "react";
import { Route, Redirect } from "react-router-dom";
class FrontendAuth extends Component {
 // eslint-disable-next-line no-useless-constructor
 constructor(props) {
 super(props);
 }
 render() {
 const { routerConfig, location } = this.props;
 const { pathname } = location;
 const isLogin = sessionStorage.getItem("username");
 console.log(pathname, isLogin);
 console.log(location);
 // 如果该路由不用进行权限校验,登录状态下登陆页除外
 // 因为登陆后,无法跳转到登陆页
 // 这部分代码,是为了在非登陆状态下,访问不需要权限校验的路由
 const targetRouterConfig = routerConfig.find(
 (item) => item.path === pathname
 );
 console.log(targetRouterConfig);
 if (targetRouterConfig && !targetRouterConfig.auth && !isLogin) {
 const { component } = targetRouterConfig;
 return ;
 }
 if (isLogin) {
 // 如果是登陆状态,想要跳转到登陆,重定向到主页
 if (pathname === "/login") {
 return ;
 } else {
 // 如果路由合法,就跳转到相应的路由
 if (targetRouterConfig) {
 return (
 
 );
 } else {
 // 如果路由不合法,重定向到 404 页面
 return ;
 }
 }
 } else {
 // 非登陆状态下,当路由合法时且需要权限校验时,跳转到登陆页面,要求登陆
 if (targetRouterConfig && targetRouterConfig.auth) {
 return ;
 } else {
 // 非登陆状态下,路由不合法时,重定向至 404
 return ;
 }
 }
 }
}
export default FrontendAuth;

总结一下,实现路由守卫需要考虑到以下的问题:

未登录情况下,访问不需要权限校验的合法页面:允许访问

未登录情况下,访问需要权限校验的页面:禁止访问,跳转至登陆页

未登录情况下,访问所有的非法页面:禁止访问,跳转至 404

登陆情况下,访问登陆页面:禁止访问,跳转至主页

登陆情况下,访问除登陆页以外的合法页面:允许访问

登陆情况下,访问所有的非法页面:禁止访问,跳转至 404

♥︎♥︎♥︎♥︎ redux中sages和thunk中间件的区别,优缺点

1、区别

  • redux-thunk异步采取 async/await
  • redux-saga采取generate函数

2、优缺点

redux-thunk

  • 优点: 库小,代码就几行
  • 缺点:代码臃肿,reducer不再是纯粹函数,直接返回对象,违背了当初的设计原则;action的形式不统一,异步操作太为分散,分散在了各个action中

redux-saga

  • 优点: 将异步与reducer区分开了,更加优雅,适合大量APi请求,而且每个请求之间存在复杂的依赖关系
  • 缺点:学习曲线比较陡,理解async await;而且库也比较大,即使发布的最小也有25kb,gzip压缩后也有7KB,React压缩后才45kb

♥︎♥︎♥︎♥︎ React复用组件的状态和增强功能的方法

  • render props模式
  • 高阶组件(HOC)

注意:以上两种方式不是新的API,而是演化而成的一种固定模式(写法)

  • 思路:将要复用的state和操作state的方法封装在一个组件里面(在组件中提供复用的状态逻辑代码,即状态和操作状态的方法)

  • 这时,我们就要思考两个问题:(1)状态是组件内部私有的,那么如何在复用组件的时候拿到组件内部的state呢?—-> 可以在使用组件的时候,添加一个值为函数的props,那么就可以通过函数参数来获取组件内部的state。(2)复用组件时,需要渲染的UI结构会不一样,那么怎么在复用组件时实现渲染任意的UI呢?—–>将函数的返回值作为要渲染的UI

  • 注意:复用的组件并没有渲染任何的UI结构,而且通过函数的返回值来渲染的。

    以下通过一个简单的例子来演示使用render props模式实现的组件复用(一个效果是随着鼠标移动,获取鼠标的位置,另一个效果是图片随着鼠标移动而移动,这两个效果的实现都要获取x和y坐标,所以可以考虑用组件的复用来实现)

Mouse组件(要复用的组件)

import React from "react";
class Mouse extends React.Component {
    //复用的state
    state = {
        x: 0,
        y: 0
    }
    //操作state的方法
    handleMouseMove = e => {
        this.setState({
            x: e.clientX,
            y: e.clientY
        })
    }
    //监听鼠标移动事件
    componentDidMount() {
        window.addEventListener("mousemove", this.handleMouseMove)
    }
    render(){
        return this.props.render(this.state)   //通过函数参数暴露组件内部的状态
    }
}
export default Mouse;

Mouse_Tree组件(复用Mouse组件的组件)

import React from "react";
import Mouse from "./Mouse.js";
import img from "./images/tree.PNG"

class Mouse_Cat extends React.Component {
    render(){
        return(
            <div>
                <Mouse render={ mouse => {
                    return(
                        <p>X坐标为:{mouse.x} Y坐标为:{mouse.y}</p>
                    )
                }}/>  
                <Mouse render={ mouse => {
                    return(
                        <img src={img} alt="树" style={{
                            position: 'absolute',
                            top: mouse.y,
                            left: mouse.x
                        }}/>
                    )
                }}/>    
            </div>
        )
    }
}
export default Mouse_Cat;

♥︎♥︎♥︎♥︎♥︎ React有哪些性能优化的手段?

1、使用纯组件;

2、使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对于相同的输入,不重复执行;

3、如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;

4、路由懒加载;

5、使用 React Fragments 避免额外标记;

6、不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);

7、避免在Willxxx系列的生命周期中进行异步请求,操作dom等;

8、如果是类组件,事件函数在Constructor中绑定bind改变this指向;

9、避免使用内联样式属性;

10、优化 React 中的条件渲染;

11、不要在 render 方法中导出数据;

12、列表渲染的时候加key;

13、在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;

14、类组件中使用immutable对象;

♥︎♥︎♥︎♥︎♥︎ 调用this.setState之后,React都做了哪些操作?怎么拿到改变后的值?

如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法;因为是同步执行,可以直接获取改变后的值;

如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;setState接受第二个参数,是一个回调函数,可以在这里获取改变后的state值;

触发render执行后,会生成一个新的虚拟dom结构,然后触发diff运算,找到变化的地方,重新渲染;

♥︎♥︎♥︎♥︎♥︎ 什么是 React Fiber?

React Fiber En

Fiber是React16中新的协调引擎,它的主要目的是使Virtual DOM可以进行增量式渲染,让界面渲染更流畅一种流程控制原语,也称为协程,可以类比es6中的generator函数;React渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。

一个执行单元,每次执行完一个“执行单元”,React就会检查现在还剩多少时间,如果没有时间就将控制权让出去。

目标

  • 把可中断的工作拆分成小任务
  • 对正在做的工作调整优先次序、重做、复用上次(做了一半的)成果
  • 在父子任务之间从容切换(yield back and forth),以支持React执行过程中的布局刷新
  • 支持render()返回多个元素
  • 更好地支持error boundary

特性

  • 增量渲染(把渲染任务拆分成块,匀到多帧)
  • 更新时能够暂停,终止,复用渲染任务
  • 给不同类型的更新赋予优先级
  • 并发方面新的基础能力

♥︎♥︎♥︎♥︎♥︎ 简述一下 memoization

memoization最初是用来优化计算机程序使之计算的更快的技术,是通过存储调用函数的结果并且在同样参数传进来的时候返回结果。大部分应该是在递归函数中使用。memoization 是一种优化技术,避免一些不必要的重复计算,可以提高计算速度。

以阶乘函数为例:

1.不使用memoization

const factorial = n => {
  if (n === 1) {
    return 1
  } else {
    return factorial(n - 1) * n
  }
}

2.使用memoization

const cache = [] // 定义一个空的存放缓存的数组
const factorial = n => {
  if (n === 1) {
    return 1
  } else if (cache[n - 1]) { // 先从cache数组里查询结果,如果没找到的话再计算
    return cache[n - 1]
  } else {
    let result = factorial(n - 1) * n
    cache[n - 1] = result
    return result
  }
}

3.搭配闭包使用memoization

const factorialMemo = () => {
  const cache = []
  const factorial = n => {
    if (n === 1) {
      return 1
    } else if (cache[n - 1]) {
      console.log(`get factorial(${n}) from cache...`)
      return cache[n - 1]
    } else {
      let result = factorial(n - 1) * n
      cache[n - 1] = result
      return result
    }
  }
  return factorial
}
const factorial = factorialMemo()

4.总结

memorization 可以把函数每次的返回值存在一个数组或者对象中,在接下来的计算中可以直接读取已经计算过并且返回的数据,不用重复多次相同的计算。是一个空间换时间的方式,这种方法可用于部分递归中以提高递归的效率。

♥︎♥︎♥︎♥︎♥︎ 自定义hooks(重点❗️❗️❗️)

[实现自定义hooks案例]: “https://juejin.cn/post/6844904074433789959

前端面试大全Vue-Vue3

前端面试题大全(Vue + Vue3)

前端面试题类目分类

  • HTML5 + CSS3
  • JavaScript
  • Vue + Vue3
  • React
  • Webpack
  • 服务端

考点频率 :♥︎ 、 ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎ ♥︎

Vue + Vue3

♥︎♥︎vue-router 3.1.0新增的v-slot属性怎么用?

router-link 通过一个作用域插槽暴露底层的定制能力。这是一个更高阶的 API,主要面向库作者,但也可以为开发者提供便利,多数情况用在一个类似 NavLink 这样的自定义组件里。

在使用 v-slot API 时,需要向 router-link 传入一个单独的子元素。否则 router-link 将会把子元素包裹在一个 span 元素内。

♥︎♥︎说一下$root,$parent,$refs

$root,和$parent都能访问父组件的属性和方法,区别在于如果存在多级子组件,通过parent 访问得到的是它最近一级的父组件,通过root 访问得到的是根父组件。

通过在子组件标签定义 ref 属性,在父组件中可以使用$refs 访问子组件实例。

♥︎♥︎对比 jQuery,Vue 有什么不同

jQuery 专注视图层,通过直接操作 DOM 去实现页面的一些逻辑渲染;

Vue 专注于数据层,通过数据的双向绑定,最终表现在 DOM 层面,减少了 DOM 操作。

Vue 使用了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发

♥︎♥︎ Vue中如何实现子组件内的css样式名在项目中绝对唯一性

在style标签上加上scoped属性

♥︎♥︎v-model是什么?Vue中标签怎么绑定事件?

v-model是一个语法糖,这一个指令可以分为几个指令,它内部已经帮我们处理整合了。对于普通的文本框来说,v-model = v-bind:value + @input。对单选框和复选框来说,v-model = v-bind:checked +@change,并且它还有一个非常重要的功能,就是解决父子组件之间的通讯问题,可以提升我们的开发效率。在vue中通过使用<v-on:事件名 = 函数名>的方式来绑定事件。

♥︎♥︎Vue生命周期通常使用哪些

常用的生命周期有,beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed

♥︎♥︎Vue深层次的组件怎么和父组件通讯

使用$attrs和$listeners

//C
Vue.component('C', {
 template: <div> <p>我是C组件</p> <input type='text' v-model='$attrs.msgc' @input='$emit("getC", $attrs.msgc)' /> </div>
})

//B
Vue.component('B', {
 /**
 给C组件绑定$attrs属性和$listeners事件,C组件可以直接获取到A组件中传递下来的props(除了B组
件中props声明的)
 */
 template: <div> <p>我是B组件</p> <input type='text' v-model='mymsg1' @input="$emit('getChild', mymsg1)" /><C v-bind='$attrs' v-on='$listeners'/> </div> ,
 props: ['msg1'],
 data () {
 return {
 mymsg1: this.msg1
 }
 }
})

Vue.component('A', {
 template: <div id='app'> <p>我是A组件</p> <B:msg1='msg1' :msgc='msgc' @getChild='getChild' @getC='getC' /> </div> ,
 data () {
 return {
 msg1: 'A',
 msgc: 'hello c!'
 }
 },
 methods: {
 getChild (val) {
 console.log( val )
 },
 getC (val) {
 console.log( val )
 }
 }
})
const app = new Vue({
 el: '#app',
 template: <A />
})

♥︎♥︎♥︎如何再Vue的单文件组件里的样式定义全局CSS?

在style标签上不加上scoped的属性,默认为全局css样式

♥︎♥︎♥︎如何实现一个路径渲染多个组件?

可以通过命名视图(router-view),它容许同一界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。通过设置components即可同时渲染多个组件。

♥︎♥︎♥︎ 如何实现多个路径共享一个组件?

只需将多个路径的component字段的值设置为同一个组件即可。

♥︎♥︎♥︎如何监测动态路由的变化

可以通过watch方法来对$route进行监听,或者通过导航守卫的钩子函数beforeRouteUpdate来监听它的变化。

♥︎♥︎♥︎对MVC,MVP,MVVM的理解

mvc 和 mvvm 其实区别并不大。都是一种设计思想。主要就是 mvc 中 Controller 演变成 mvvm 中的viewModel。mvvm 主要解决了 mvc 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到 View 。

MVVM 是 Model-View-ViewModel 的缩写。mvvm 是一种设计思想。

1:Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑;View 代表 UI 组件,它负责将数据模型转化成 UI 展现出来,ViewModel 是一个同步 View 和 Model 的对象。

2:在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,Model和 ViewModel 之间的交互是双向的, 因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。

3:ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作 DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

MVP与MVC很接近,P指的是Presenter,presenter可以理解为一个中间人,它负责着View和Model之间的数据流动,防止View和Model之间直接交流。

MVP

presenter负责和Model进行双向交互,还和View进行双向交互。这种交互方式,相对于MVC来说少了一些灵活,Vlew变成了被动视图,并且本身变得很小。虽然它分离了View和Model。但是presenter除了要做事件监听,还要更新DOM等各种事情,应用逐渐变大之后,导致presenter的体积增大,难以维护。

♥︎♥︎♥︎什么情况下使用 Vuex

如果应用够简单,最好不要使用 Vuex,一个简单的 store 模式即可,需要构建一个中大型单页应用时,使用Vuex能更好地在组件外部管理状态

♥︎♥︎♥︎Vuex可以直接修改state的值吗?

可以直接修改,但是极其不推荐,state的修改必须在mutation来修改,否则无法被devtool所监测,无法监测数据的来源,无法保存状态快照,也就无法实现时间漫游/回滚之类的操作。

♥︎♥︎♥︎v-model和vuex有冲突吗?

当我们v-model的数据是存储在vuex中时写法如下时会报错

computed:{
  editableTabsValue2:{
     return this.$store.state.activeName
  }
}

报错:Computed property “editableTabsValue2” was assigned to but it has no setter

原因:v-model是双向数据绑定 vue实例的data改变或者用户输入的改变都会触发视图更新。vuex是一个状态的存储,对于里面的状态的改变都是通过commit mutation 所以当用户输入直接修改editableTabsValue2,又不是通过commit更改状态时会报错

解决方法:

computed:{
  editableTabsValue2:{
    get:function () {
      return this.$store.state.activeName
    },
    set:function () {
    }
  }
}

♥︎♥︎♥︎解释单向数据流和双向数据绑定

对于 Vue 来说,组件之间的数据传递具有单向数据流这样的特性称为单向数据流,单向数据流(Unidirectional data flow)方式使用一个上传数据流和一个下传数据流进行双向数据通信,两个数据流之间相互独立,单向数据流指只能从一个方向来修改状态。

而双向数据绑定即为当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化,两个数据流之间互为影响。

♥︎♥︎♥︎$route和 $router的区别

$route用来获取路由的信息的,它是路由信息的一个对象,里面包含路由的一些基本信息,包括name、meta、path、hash、query、params、fullPath、matched、redirectedFrom等。

而$router主要是用来操作路由的,它是VueRouter的实例,包含了一些路由的跳转方法,钩子函数等

♥︎♥︎♥︎Vue 中怎么自定义指令

通过directive来自定义指令,自定义指令分为全局指令和局部指令,自定义指令也有几个的钩子函数,常用的有bind和update,当 bind 和 update 时触发相同行为,而不关心其它的钩子时可以简写。一个表达式可以使用多个过滤器。过滤器之间需要用管道符“|”隔开。其执行顺序从左往右。

♥︎♥︎♥︎Vue中怎么自定义过滤器

通过filter来定义过滤器,过滤器分为全局和局部过滤器,过滤器的主体为一个普通的函数,来对数据进行处理,可以传递参数。当有局部和全局两个名称相同的过滤器时候,会以就近原则进行调用,即:局部过滤器优先于全局过滤器被调用。

♥︎♥︎♥︎Vue等单页面应用的优缺点

优点

1、单页应用的内容的改变不需要重新加载整个页面,web应用更具响应性和更令人着迷。

2、单页应用没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象

3、单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提

高几倍。

4、良好的前后端分离。后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代

码,不用修改就可以用于Web界面、手机、平板等多种客户端。

缺点

1、首次加载耗时比较多。

2、SEO问题,不利于百度,360等搜索引擎收录。

3、容易造成Css命名冲突。

4、前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂度很高,需要一定的技能水平和开

发成本高。

♥︎♥︎♥︎Vue-router使用params与query传参有什么区别

// 用法上

query要用path来引入,params要用name来引入,接收参数都是类似的,分别是 this.$route.query.name 和this.$route.params.name。

// 展示上

1:query更加类似于我们ajax中get传参,params则类似于post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示

2:params是路由的一部分,必须要有。query是拼接在url后面的参数,没有也没关系。

3:params、query不设置也可以传参,params不设置的时候,刷新页面或者返回参数会丢失

♥︎♥︎♥︎Vue中keep-alive的作用

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。一旦使用keepalive包裹组件,此时mouted,created等钩子函数只会在第一次进入组件时调用,当再次切换回来时将不会调用。此时如果我们还想在每次切换时做一些事情,就需要用到另外的周期函数,actived和deactived,这两个钩子函数只有被keepalive包裹后才会调用。

♥︎♥︎♥︎Vue如何实现单页面应用

通常的url 地址由以下内容构成:协议名 域名 端口号 路径 参数 哈希值,当哈希值改变,页面不会发生跳转,单页面应用就是利用了这一点,给window注册onhashchange事件,当哈希值改变时通过location.hash就能获得相应的哈希值,然后就能跳到相应的页面。

♥︎♥︎♥︎说出至少4种Vue当中的指令和它的用法?

v-if(判断是否隐藏,用来判断元素是否创建)
v-show(元素的显示隐藏,类似css中的display的block和hidden)
v-for(把数据遍历出来)
v-bind(绑定属性)
v-model(实现双向绑定)

♥︎♥︎♥︎ Vuex是什么?怎么使用?描述使用它实现登录功能的流程?

Vuex是一个专为Vue.js应用程序开发的状态管理模式;它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex可以管理复杂应用的数据状态,比如兄弟组件的通信、多层嵌套的组件的传值等等。

如何使用

1.安装vuex
按照官网给出的步骤
npm install vuex --save  //安装vuex

2.Vuex 依赖 Promise  浏览器支持promise的此步骤可省略
注意:Vuex 依赖 Promise (opens new window)。如果你支持的浏览器并没有实现 Promise (比如 IE),那么你可以使用一个 polyfill 的库,例如 es6-promise (opens new window)。

你可以通过 CDN 将其引入:
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js">
</script>
然后 window.Promise 会自动可用。
如果你喜欢使用诸如 npm 或 Yarn 等包管理器,可以按照下列方式执行安装:
npm install es6-promise --save # npm
yarn add es6-promise # Yarn
或者更进一步,将下列代码添加到你使用 Vuex 之前的一个地方:
import 'es6-promise/auto'

3.在项目引用
找到项目中的main.js文件,将内容引入
import store from './store'
.use(store)

4.使用方法
首先,在项目中找到store文件夹,如果没有就自己建一个文件夹,在文件夹内再新建一个js文件。

import { createStore } from 'vuex'
 
export default createStore({
  state: {  //数据存放的位置
        data:''
  },
  mutations: {//在mutations写方法更改state中的值
      data(state,data){
        state.data=data
   }
  },
  actions: {//通过actions触发mutations的方法
      dataone(context,data){
          context.commit('data',data);//带引号的data是mutation里的,第二个data,是页面上
                                      //传过来的参数
    }
  },
  modules: {
      
  }
})
然后,将要保存的数据从页面传过来
这是页面:
import {useStore} from 'vuex'
    export default{
        setup(){
            let A =124
            const store=useStore();
            store.dispatch('dataone',A)
              //括号内第一个参数为actions中的方法名,方法名不能与mutations中重复    
              //第二个为需要存储改变的值的变量
        }
    }

♥︎♥︎♥︎Vue-loader解释一下

解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理。

♥︎♥︎♥︎ 用过插槽吗?用的是具名插槽还是匿名插槽

用过,都使用过。插槽相当于预留了一个位置,可以将我们书写在组件内的内容放入,写一个插槽就会将组件内的内容替换一次,两次则替换两次。为了自定义插槽的位置我们可以给插槽取名,它会根据插槽名来插入内容,一一对应。

♥︎♥︎♥︎Vue路由守卫

vue-router 提供的导航守卫主要用来对路由的跳转进行监控,控制它的跳转或取消,路由守卫有全局的,单个路由独享的, 或者组件级的。导航钩子有3个参数:

1、to:即将要进入的目标路由对象;

2、from:当前导航即将要离开的路由对象;

3、next :调用该方法后,才能进入下一个钩子函数(afterEach)。

♥︎♥︎♥︎请你说一下Vue中create和mount的区别

create为组件初始化阶段,在此阶段主要完成数据观测(data observer),属性和方法的运算,watch/event 事件回调。

然而,挂载阶段还没开始,此时还未生成真实的DOM,也就无法获取和操作DOM元素。

而mount主要完成从虚拟DOM到真实DOM的转换挂载,此时html已经渲染出来了,所以可以直接操作dom节点。

♥︎♥︎♥︎路由懒加载

把不同路由对应的组件分割成不同的代码块,然后当路由被访问时才加载对应的组件即为路由的懒加载,可以加快项目的加载速度,提高效率。通过这种格式来导入组件const foo = () =>import(‘./foo.vue’);

♥︎♥︎♥︎Vue中computed的原理

computed是vue中的计算属性,在依赖的值发生变化的时候进行重新计算,否则使用缓存。

前面说到computed只有在依赖发生变化才会重新计算,那么如何得知computed的值发生了变化呢

这主要是Watcher中的dirty属性,dirty属性为true时,说明computed中的值需要重新计算,dirty为false时,则说明依赖没有变化,不需要重新计算

当计算属性的值发生变化时,计算属性的watcher和组件的watcher都会得到通知。

计算属性的watcher会将dirty置为true,组件的Watcher得到通知,同样将dirty属性置为false,重新计算值,用于本次渲染。

简单来说,computed就是定义在vm上的一个getter属性,这个getter属性被触发时会做两件事

  1. 计算当前属性的值,此时会使用Watcher去观察计算属性中用到的所有其他数据的变化。同时将计算属性的Watcher的dirty属性设置为false.

  2. 当计算属性中用到的数据发生变化时,将得到通知从而进行重新渲染

Watcher中的depend和evaluate方法是专门用于实现计算属性的两个API

export default class Watcher {
     constructor(vm, expOrFn, cb, options) {
         if(options) {
             this.lazy = !!options.lazy;
         }  else {
             this.lazy = false;
         }
         this.dirty = this.lazy;
         this.value = this.lazy ? undefined : this.get()
     }
     evaluate() {
        this.value = this.get()
        this.dirty = false; 
    }
    depend() {
        let i = this.deps.length
        while(i--) {
            this.deps[i].depend()
        }
    }
}

执行depend方法可以将组件中的watcher实例添加到dep实例的依赖列表中。换句话说,this.deps是计算属性中用到的所有状态的dep实例,而依次执行了dep实例的depend方法就是将组件的watcher依次加入到这些dep实例的依赖列表。这就实现了让组件的watcher观察计算属性中用到的所有的状态的变化。

Computed逻辑变化

computed在vue2.5.2中的实现发生了一些变化,因为之前的computed的计算存在一些逻辑上的漏洞,因为只要依赖的值发生了变化,vue就认为值发生了变化,组件会重新走一遍渲染的流程,但实际上UI不会由变化,浪费了一些性能。

改动之后的逻辑:

组件的watcher不再监听计算属性的变化,而是让计算属性的watcher得到通知后,计算一次计算属性的值,如果发现这一次计算出来的值与上一次计算出来的值不一样,再去主动通知组件的watcher进行重新渲染。

♥︎♥︎♥︎Vuex的缺点

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的,并且state中的值会伴随着浏览器的刷新而初始化,无缓存。

♥︎♥︎♥︎Vue路由传参,刷新后还有吗

通过params传参会出现参数丢失的情况,可以通过query的传参方式或者在路由匹配规则加入占位符即可以解决参数丢失的情况。

♥︎♥︎♥︎Vue组件如何引入使用

  1. 定义组件并抛出

  2. import引入,并在component里面定义

  3. 使用组件(注意首字母大写)

♥︎♥︎♥︎ Vue $forceUpdate的原理

1、作用:

迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

2、内部原理:

Vue.prototype.$forceUpdate = function () {
 const vm: Component = this
 if (vm.watcher) {
 vm.watcher.update()
 }
}

实例需要重新渲染是在依赖发生变化的时候会通知watcher,然后通知watcher来调用update方法,就是这么简单。

♥︎♥︎♥︎v-for key

key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异.

diff程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.

准确: 如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug.

快速: key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n),Map的时间复杂度仅仅为O(1)

建议使用id,不建议使用索引

♥︎♥︎♥︎为什么要设置key值,可以用index吗?为什么不能?

虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。

用index作为key可能会引发的问题:
1.若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。

开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。

♥︎♥︎♥︎ Vue的虚拟dom

从本质来说,Virtual Dom 是一个 JavaScript 对象,通过对象的方式来表示 DOM 结构。将页面的状态抽象为 JS 对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将多次 DOM 修改的结果一次性更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。

虚拟 DOM 是对 DOM 的抽象,这个对象是更加轻量级的对 DOM 的描述。它设计的最初目的就是更好的跨平台,比如 Node.js 就没有 DOM,如果想实现 SSR,那么就只能借助虚拟 DOM,因为虚拟 DOM 本身就是 JS 对象,在代码渲染到页面之前,Vue 和 React 会把代码转化成一个对象(虚拟 DOM),以对象的形式来描述真实 DOM 结构,最终渲染到页面。在每次数据发生变化前,虚拟 DOM 都会缓存一份,变化之时,现在的虚拟 DOM 会与缓存的虚拟 DOM 进行比较。

Vue 和 React 内部都封装了 diff 算法用来进行比较渲染时的变化(具体 diff 算法内容后续重点分析),精准更新发生变化的节点,而没有发生变化的直接通过原先的数据进行渲染。

另外现代前端框架基本都要求无须手动操作 DOM,一方面是因为手动操作 DOM 无法保证程序的性能,多人协作的项目中如果 code review 不严格,很容易出现性能较低的代码,另一方面更重要的是省略手动操作 DOM 可以大大提高开发效率。

♥︎♥︎♥︎diff复杂度原理及具体过程画图

diff算法是一种通过同层的树节点进行比较的高效算法,避免了对树进行逐层搜索遍历,所以时间复杂度只有 O(n)。

diff算法有两个比较显著的特点:

1、比较只会在同层级进行, 不会跨层级比较。

2、在diff比较的过程中,循环从两边向中间收拢。

diff流程: 首先定义 oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 分别是新老两个 VNode的两边的索引。

接下来是一个 while 循环,在这过程中,oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 会逐渐向中间靠拢。while 循环的退出条件是直到老节点或者新节点的开始位置大于结束位置。

while 循环中会遇到四种情况:

情形一:当新老 VNode 节点的 start 是同一节点时,直接 patchVnode 即可,同时新老 VNode 节点的开始索引都加 1。

情形二:当新老 VNode 节点的 end 是同一节点时,直接 patchVnode 即可,同时新老 VNode 节点的结束索引都减 1。

情形三:当老 VNode 节点的 start 和新 VNode 节点的 end 是同一节点时,这说明这次数据更新后oldStartVnode 已经跑到了 oldEndVnode 后面去了。这时候在 patchVnode 后,还需要将当前真实dom 节点移动到 oldEndVnode 的后面,同时老 VNode 节点开始索引加 1,新 VNode 节点的结束索引减 1。

情形四:当老 VNode 节点的 end 和新 VNode 节点的 start 是同一节点时,这说明这次数据更新后oldEndVnode 跑到了 oldStartVnode 的前面去了。这时候在 patchVnode 后,还需要将当前真实 dom节点移动到 oldStartVnode 的前面,同时老 VNode 节点结束索引减 1,新 VNode 节点的开始索引加1。

while 循环的退出条件是直到老节点或者新节点的开始位置大于结束位置。

情形一:如果在循环中,oldStartIdx大于oldEndIdx了,那就表示oldChildren比newChildren先循环完毕,那么newChildren里面剩余的节点都是需要新增的节点,把[newStartIdx, newEndIdx]之间的所有节点都插入到DOM中

情形二:如果在循环中,newStartIdx大于newEndIdx了,那就表示newChildren比oldChildren先循环完毕,那么oldChildren里面剩余的节点都是需要删除的节点,把[oldStartIdx, oldEndIdx]之间的所有节点都删除

♥︎♥︎♥︎怎么修改Vuex中的状态?Vuex中有哪些方法

  • 通过this.$store.state.属性的方法来访问状态
  • 通过this.$store.commit(‘mutation中的方法’) 来修改状态
// App.vue
<template>
  <div id="app">
    <p>{{count}}</p>
    <button @click="addition">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  computed:{
    count() {
      return this.$store.state.count
    }
  },
  methods:{
    addition() {
      this.$store.commit('increment')
    },
    decrement() {
      this.$store.commit('decrement')
    }
  }
}
</script>

♥︎♥︎♥︎vue-router路由传参的方式

query

this.$router.push({
 path: 'blogDetail', 
 query: { 
 id: item.id,
 views: item.views
 }
})

params

this.$router.push({
 name: 'blogDetail', 
 params: { 
 id: item.id,
 views: item.views
 }
})

♥︎♥︎♥︎ hash history区别

hash history
url显示 有#,很Low 无#,好看
回车刷新 可以加载到hash值对应页面 一般就是404掉了
支持版本 支持低版本浏览器和IE浏览器 HTML5新推出的API

♥︎♥︎♥︎ 用过beforeEach吗?

每次通过vue-router进行页面跳转,都会触发beforeEach这个钩子函数,这个回调函数共有三个参数,to,from,next这三个参数,to表示我要跳转的目标路由对应的参数,from表示来自那个路由,就是操作路由跳转之前的,即将离开的路由对应的参数,next是一个回调函数,一定要调用next方法来resolve这个钩子函数;

♥︎♥︎♥︎ Vnode的优缺点

优点

1、降低浏览器性能消耗

因为Javascript的运算速度远大于DOM操作的执行速度,因此,运用patching算法来计算出真正需要更新的节点,最大限度地减少DOM操作,从而提高性能。

在vnode技术出现之前,我们要改变页面展示的内容只能通过遍历查询 dom 树的方式找到需要修改的 dom,然后修改样式行为或者结构,来达到更新 ui的目的。这种方式相当消耗计算资源,因为每次查询 dom 几乎都需要遍历整颗 dom树。

在vnode技术出现之后,我们建立一个虚拟 dom 对象来对应真实的 dom 树,那么每次 dom 的更改就变成了 js 对象的属性的更改 ,这样一来就能查找 js 对象的属性变化要比查询 dom 树的 性能开销小。

2、diff算法,减少回流和重绘

通过diff算法,优化遍历,对真实dom进行打补丁式的新增、修改、删除,实现局部更新,减少回流和重绘。

vnode优化性能核心思想,就是每次更新 dom 都尽量避免刷新整个页面,而是有针对性的 去刷新那被更改的一部分,来释放掉被无效渲染占用的 gpu,cup性能。同时,也减少了大量的dom操作,减少了浏览器的回流和重绘。

3、跨平台

虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM ,可以进行更方便地跨平台操作,例如:服务器渲染、weex 开发等等

缺点

  • 首次显示要慢些:
    首次渲染大量DOM时,由于多了一层虚拟DOM的计算, 会比innerHTML插入慢
  • 无法进行极致优化
    虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中 无法进行针对性的极致优化。

♥︎♥︎♥︎Vue中的单项数据流

单向数据流指只能从一个方向来修改状态。

数据从父级组件传递给子组件,只能单向绑定。

子组件内部不能直接修改从父级传递过来的数据。

♥︎♥︎♥︎Vue组件中的Data为什么是函数,根组件却是对象呢?

如果data是一个函数的话,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

所以说vue组件的data必须是函数。这都是因为js的特性带来的,跟vue本身设计无关。

♥︎♥︎♥︎介绍下vue单页面和多页面区别

单页应用 页面跳转—->js渲染 优点:页面切换快 缺点:首屏加载稍慢,seo差

多页应用 页面跳转—->返回html 优点:首屏时间快,seo效果好 缺点:页面切换慢

♥︎♥︎♥︎介绍下vue父子组件生命周期的执行顺序

挂载阶段

执行顺序为:
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

更新阶段

执行顺序为:
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated

销毁阶段

执行顺序为:
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

规律就是:父组件先开始执行,然后等到子组件执行完,父组件收尾。

♥︎♥︎♥︎Vue中key的作用?不加会怎么样?

vue中列表循环需加:key=”唯一标识” 唯一标识可以是item里面id index等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件 key的作用主要是为了高效的更新虚拟DOM.

♥︎♥︎♥︎Vue的常用修饰符

  1. 事件修饰符 - 处理 DOM 事件细节
  2. 按键修饰符 - 为 v-on 在监听键盘事件时添加按键修饰符
  3. 系统修饰键 - 实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
  4. 鼠标按钮修饰符 - 限制处理函数仅响应特定的鼠标按键
  5. 表单修饰符 - v-model增强的修饰符

一、事件修饰符

  1. .stop - 阻止单击事件继续传播, 也就是阻止冒泡

  2. .prevent - 提交事件不再重新加载页面, 可以用来阻止表单提交的默认行为

注意点: .stop和.prevent可以串联在一起使用,都会生效

  1. .capture - 内部元素触发的事件先在此处理,然后才交由内部元素进行处理

  2. .self - 只当在 event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的

注意点: 使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

  1. .once - 点击事件将只会触发一次, 不只能对原生DOM时间生效,还可以用在自定义组件上

  2. .passive - 立即触发默认行为,能够提升移动端性能,和.prevent一起使用时.prevent会被忽略并警告

  3. .native - 使用时将被当做原生的HTML标签看待,绑定事件可以生效

二、按键修饰符

  1. .keyup - 键盘抬起

  2. .keydow - 键盘按下

  3. 按键码 - 在按键修饰符后面添加,用于监听键盘按下哪个键

常用按键码:

  • .enter

  • .tab

  • .delete (捕获“删除”和“退格”键)

  • .esc

  • .space

  • .up

  • .down

  • .left

  • .right

三、系统修饰键

  • .ctrl

  • .alt

  • .shift

  • .meta

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

注意点: 修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。
.exact 修饰符 - 使用系统修饰符时使用可以单击系统修饰键触发,不适用系统修饰符时使用

四、鼠标按键修饰符

.left - 只有鼠标左键点击触发
.right - 只有鼠标右键点击触发
.middle - 只有鼠标中键点击触发

五、表单修饰符

  1. .lazy - 在表单输入时不会马上显示在页面,而是等输入完成失去焦点时才会显示在页面

  2. .trim - 过滤表单输入时前后的空格

  3. .number - 限制输入数字或将输入的数据转为数字

注意点: 如果先输入数字,会限制只能输数字, 如果先输字符串则相当于没加.number

♥︎♥︎♥︎Vue计算属性和 Watch 的区别

methods(方法):

只要进行调用就会执行,不管依赖的值有没有改变。无缓存。

computed(计算属性):

监听其所有依赖的变化,如果有变化会执行,没有变化不执行。有缓存,不用每次重新算。不支持异步。

详解:在vue的 模板内是可以写一些简单的js表达式的 ,很便利。但是如果在页面中使用大量或是复杂的表达式去处理数据,对页面的维护会有很大的影响。这个时候就需要用到computed 计算属性来处理复杂的逻辑运算。

1.优点:

在数据未发生变化时,优先读取缓存。computed 计算属性只有在相关的数据发生变化时才会改变要计算的属性,当相关数据没有变化是,它会读取缓存。而不必想 motheds方法 和 watch 方法是否每次都去执行函数。

2.setter 和 getter方法:(注意在vue中书写时用set 和 get)

setter 方法在设置值是触发。

getter 方法在获取值时触发。

watch(侦听属性):

观察某一个变量,发生变化会执行。支持异步。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。

一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。

❕小结:

1.主动调用的方法写在methods里,依据某些变量的更新进行某种操作用computed或者watch。

2.computed和watch:如果要异步,只能用watch。如果是计算某个值推荐用computed,比如购物车全选单选功能,购物车计算总价小计功能。

♥︎♥︎♥︎Vue中v-on可以绑定多个方法吗?

vue中v-on是可以绑定多个方法的

1、v-on绑定多个方法(采用的是对象形式)

<button v-on="{click: clickMethods, mousemove: mouseMethods}">按钮<button>

2.v-on 一个事件绑定多个方法 (语法糖 @)

<button @click="click1,click2">按钮</button>

♥︎♥︎♥︎Vue中template的编译过程

vue template模板编译的过程经过parse()生成ast(抽象语法树),optimize对静态节点优化,generate()生 成render字符串 之后调用new Watcher()函数,用来监听数据的变化,render 函数就是数据监听的回调,所调用的,其结果便是重新生成 vnode。 当这个 render 函数字符串在第一次 mount、或者绑定的数据更新的时候,都会被调用,生成 Vnode。 如果是数据的更新,那么 Vnode 会与数据改变之前的 Vnode做 diff,对内容做改动之后,就会更新到 我们真正的 DOM

♥︎♥︎♥︎Vue中router-link和传统a链接的区别?

组件支持用户在具有路由功能的应用中 (点击) 导航。

通过 to 属性指定目标地址,默认渲染成带有正确链接的 标签,可以通过配置 tag 属性生成别的标签.。

通过router-link进行跳转不会跳转到新的页面,也不会重新渲染,它会选择路由所指的组件进行渲染,避免了重复渲染的“无用功”。

总结:对比,router-link组件避免了不必要的重渲染,它只更新变化的部分从而减少DOM性能消耗

♥︎♥︎♥︎ Vue插槽是什么?

slot用于封装组件中,写在子组件 接收父组件动态传递子组件内容片断

slot插槽的使用方法其实就是类似于一个子组件或者标签的引用的过程,在父组件里面定义一个slot,给她起个name,然后组件引入到子组件,子组件里面有个元素的属性slot值等于name,然后父组件里面没有值的时候就可以显示子组件里面的信息了(切记:插槽slot要写在父组件里面!!!)

♥︎♥︎♥︎ 说一下Vue-router守卫有哪些。如何实现路由懒加载?

  • router.beforeEach 全局前置守卫 进入路由之前
  • router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用
  • router.afterEach 全局后置钩子 进入路由之后

1、 Vue异步加载技术

1:vue-router配置路由,使用vue的异步组件技术,可以实现懒加载,此时一个组件会生成一个js文件。

2:component: resolve => require([‘放入需要加载的路由地址’], resolve)

2、 ES6推荐方式imprort ()—-推荐使用

import Vue from 'vue';
import Router from 'vue-router';
// 官网可知:下面没有指定webpackChunkName,每个组件打包成一个js文件。
const Foo = () => import('../components/Foo')
const Aoo = () => import('../components/Aoo')
// 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。
// const Foo = () => import(/* webpackChunkName: 'ImportFuncDemo' / '../components/Foo')
// const Aoo = () => import(/ webpackChunkName: 'ImportFuncDemo' */ '../components/Aoo')
export default new Router({
 routes: [
 {
 path: '/Foo',
 name: 'Foo',
 component: Foo
 },
 {
 path: '/Aoo',
 name: 'Aoo',
 component: Aoo
 }
 ]
})

3、 webpack提供的require.ensure()实现懒加载

  • 1:vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。
  • 2:这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
  • 3:require.ensure可实现按需加载资源,包括js,css等。他会给里面require的文件单独打包,不会和主文件打包在一起。
  • 4:第一个参数是数组,表明第二个参数里需要依赖的模块,这些会提前加载。
  • 5:第二个是回调函数,在这个回调函数里面require的文件会被单独打包成一个chunk,不会和主文件打包在一起,这样就生成了两个chunk,第一次加载时只加载主文件。
  • 6:第三个参数是错误回调。
  • 7:第四个参数是单独打包的chunk的文件名
import Vue from 'vue';
import Router from 'vue-router';
const HelloWorld=resolve=>{
 require.ensure(['@/components/HelloWorld'],()=>{
 resolve(require('@/components/HelloWorld'))
 })
 }
Vue.use('Router')
export default new Router({
 routes:[{
 {path:'./',
 name:'HelloWorld',
 component:HelloWorld
 }
 }]
})

♥︎♥︎♥︎什么是Vue.js动态组件与异步组件?

1.动态组件

  • :is = “component-name” 用法
  • 需要根据数据,动态渲染的场景。即组件类型不确定
//代码实例
<template>
  <div>
      <p>vue高级特性</p>
      <hr>
      <!-- 动态组件 -->
      <component :is="NextTickName"></component>
  </div>
</template>
<style>
</style>
<script>
import NextTick from './nextTick'
export default {
  components: {
    NextTick
  },
  data () {
    return {
      name: '夹心',
      website: {
        url: 'http://imooc.com/',
        title: 'imooc',
        subTitle: '程序员的梦工厂'
      },
      NextTickName: 'NextTick'
    }
  }
}
</script>

2.异步组件

  • import() 函数
  • 按需加载,异步加载大组件
export default {
  components: {
    FormDemo: () => import('../components/FormDemo')
  }
}

♥︎♥︎♥︎说一下Vue中路由跳转和传值的方式

1、路由跳转

router-link组件 默认会被渲染成一个 标签,进行跳转,在组件中可以通过绑定to属性来指定要跳转的链接;tag属性指本来的标签

$router.push()方法

2、路由传参

query

this.$router.push({
 path: 'blogDetail', 
 query: { 
 id: item.id,
 views: item.views
 }
})

params

this.$router.push({
 name: 'blogDetail', 
 params: { 
 id: item.id,
 views: item.views
 }
})

♥︎♥︎♥︎最近关注了什么新技术吗,简单说下你的理解(Vue3.0与Vue2.0对比)

[Vue3.0与Vue2.0区别]: “https://zhuanlan.zhihu.com/p/389421424

一、六大亮点

性能比vue2.x快1.2~2倍

支持tree-shaking,按需编译,体积比vue2.x更小

支持组合API

更好的支持TS

更先进的组件

二、性能比vue2.x快1.2~2倍如何实现的呢

1.diff算法更快

vue2.0是需要全局去比较每个节点的,若发现有节点发生变化后,就去更新该节点

vue3.0是在创建虚拟dom中,会根据DOM的的内容会不会发生内容变化,添加静态标记, 谁有flag!比较谁。

2、静态提升

vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染 vue3中对于不参与更新的元素,会做静态提升,只被创建一次,在渲染时直接复用即可

3、事件侦听缓存默认情况下,onclick为动态绑定,所以每次都会追踪它的变化,但是因为是同一函数,没有必要追踪变化,直接缓存复用即可在之前会添加静态标记8 会把点击事件当做动态属性 会进行diff算法比较, 但是在事件监听缓存之后就没有静态标记了,就会进行缓存复用

三、为什么vue3.0体积比vue2.x小

在vue3.0中创建vue项目 除了vue-cli,webpack外还有 一种创建方法是Vite Vite是作者开发的一款有意取代webpack的工具,其实现原理是利用ES6的import会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间

四、vue3.0组合API

1.说一说vue3.0的组合API跟之前vue2.0在完成业务逻辑上的区别:

在vue2.0中: 主要是往data 和method里面添加内容,一个业务逻辑需要什么data和method就往里面添加,而组合API就是 有一个自己的方法,里面有自己专注的data 和method。

2.再说一下组合API的本质是什么: 首先composition API(组合API) 和 Option API(vue2.0中的data和method)可以共用 composition API(组合API)本质就是把内容添加到Option API中进行使用

五、ref和reactive的简单理解

1.ref和reactive都是vue3的监听数据的方法,本质是proxy

2.ref 基本类型复杂类型都可以监听(我们一般用ref监听基本类型),reactive只能监听对象(arr,json)

3.ref底层还是reactive

六、对生命周期的监听(ref获取属性)

七、proxy响应式本质

♥︎♥︎♥︎v-if和v-show的区别

1、手段:v-if是动态的向DOM树添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显示和隐藏。

2、编译过程:v-if切换有一个局部编译/卸载的过程, 切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换。

3、编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留

4、性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗 使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换

6、相同点: v-show 都可以动态控制着dom元素的显示隐藏

​ 不同点: v-if 的显示隐藏是将DOM元素整个添加或删除,v-show 的显示隐藏是为DOM元素添

7、加css的样式display,设置none或者是block,DOM元素是还存在的

8、在渲染多个元素的时候,可以把一个 元素作为包装元素,并使用v-if 进行条件判断,最终的渲染不会包含这个元素,v-show是不支持 语法

♥︎♥︎♥︎ 简述Vue每个生命周期具体适合哪些场景

钩子函数 具体发生了什么
beforeCreate 初始化界面前 : 在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问,都还没有完成初始化。
created 初始化界面后 : 在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数,也就是不会更新视图。实例的data数据和methods方法都已经被初始化完毕了,可以正常访问
beforeMount 挂载前 :完成模板编译,虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。数据还没有更新到页面上去。当编译完成之后,只是在内存中已经有了编译好的页面,但并未渲染。
mounted 挂载完成 : 将编译好的模板挂载到页面 (虚拟DOM挂载) ,可以在这进行异步请求以及DOM节点的访问,在vue用$ref操作
beforeUpdate 更新数据前 : 组件数据更新之前调用,数据都是新的,页面上数据都是旧的。将要根据最新的data数据,重新解析所有指令,从而重新渲染浏览器页面。
updated 组件更新后 : render重新渲染 , 此时数据和界面都是新的 ,要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新
beforeDestroy 组件卸载前 : 实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器
destroyed 组件卸载后 : 组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
activited keep-alive 专属 , 组件被激活时调用
deactivated keep-alive 专属 , 组件被销毁时调用

♥︎♥︎♥︎ 什么是v-model?它的原理是什么?

1、v-model本质上是一个语法糖,可以看成是value + input 方法的语法糖。可以通过model的prop属性和event事件来进行自定义。

2、v-model是vue的双向绑定的指令,能将页面上控件输入的值同步更新到相关绑定的data属性, 也会在更新data绑定属性时候,更新页面上输入控件的值。

♥︎♥︎♥︎简单说一下 微信小程序 与Vue的区别(了解即可,具体区别请看各自具体部分)

1、生命周期:

小程序的钩子函数要简单得多 。 vue的钩子函数在跳转新页面时,钩子函数都会触发,但是小程序的钩子函数,页面不同的跳转方式,触发的钩子并不一样。

在页面加载请求数据时,两者钩子的使用有些类似,vue一般会在created或者mounted中请求数据,而在小程序,会在onLoad或者onShow中请求数据。

2、数据绑定:

vue动态绑定一个变量的值为元素的某个属性的时候,会在变量前面加上冒号:

小程序 绑定某个变量的值为元素属性时,会用两个大括号括起来,如果不加括号,为被认为是字符串

3、列表循环

4、显示与隐藏元素

vue中,使用v-if 和v-show控制元素的显示和隐藏

小程序中,使用wx-if和hidden控制元素的显示和隐藏

5、事件处理

vue:使用v-on:event绑定事件,或者使用@event绑定事件

小程序中,全用bindtap(bind+event),或者catchtap(catch+event)绑定事件

6、数据的双向绑定

在vue中,只需要再表单元素上加上v-model,然后再绑定data中对应的一个值,当表单元素内容发生变化时,data中对应的值也会相应改变 。

当表单内容发生变化时,会触发表单元素上绑定的方法,然后在该方法中,通过this.setData({key:value})来将表单上的值赋值给data中的对应值 。

7、绑定事件传参

在vue中,绑定事件传参挺简单,只需要在触发事件的方法中,把需要传递的数据作为形参传入就可以了

在小程序中,不能直接在绑定事件的方法中传入参数,需要将参数作为属性值,绑定到元素上的data-属性上,然后在方法中,通过e.currentTarget.dataset.*的方式获取

8、父子组件通信

父组件向子组件传递数据,只需要在子组件通过v-bind传入一个值,在子组件中,通过props接收,即可完成数据的传递父组件向子组件通信和vue类似,但是小程序没有通过v-bind,而是直接将值赋值给一个变量 在子组件properties中,接收传递的值

♥︎♥︎♥︎Vue的组件通信

1、props和$emit

父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件

2、$attrs和$listeners

3、中央事件总线 bus

上面两种方式处理的都是父子组件之间的数据传递,而如果两个组件不是父子关系呢?这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件。

4、provide和inject

父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。

5、v-model

父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值

6、$parent和$children

7、boradcast和dispatch

8、vuex处理组件之间的数据交互 如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

♥︎♥︎♥︎Vue如何在用户没登陆的时候重定向登录界面?

现在 我们需要实现这样 一个功能,登录拦截,其实就是 路由拦截,首先在定义路由的时候就需要多添加一个自定义字段requireAuth,用于判断该路由的访问是否需要登录。如果用户已经登录,则顺利进入路由, 否则就进入登录页面。在路由管理页面添加meta字段

{
 path:'/manage',
 name:'manage',
 component:manage,
 meta:{requireAuth:true}
}

在入口文件中,添加路由守卫

先判断该路由是否需要登录权限

判断本地是否存在token,如果存在token就next(),不存在token重定向到登录页

♥︎♥︎♥︎Vuex和redux有什么区别?他们的共同思想。

Redux和Vuex区别

Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值就可以

Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的state就可以

Vuex数据流的顺序是:View调用store.commit提交对应的请求到Store中对应的mutation函数 – store改变(vue检测到数据变化自动渲染)

共同思想

  • 单一的数据源
  • 变化可以预测
  • 本质上:Redux和Vuex都是对MVVM思想的服务,将数据从视图中抽离的一种方案
  • 形式上:Vuex借鉴了Redux,将store作为全局的数据中心,进行数据管理

♥︎♥︎♥︎♥︎Composition API的出现带来哪些新的开发体验,为啥需要这个?

1:在Compostion API 中时根据逻辑相关组织代码的,提高可读性和可维护性,类似于react的hook写法。

2:更好的重用逻辑代码,在Options API中通过MIxins重用逻辑代码,容易发生命名冲突且关系不清。

3:解决在生命周期函数经常包含不相关的逻辑,但又不得不把相关逻辑分离到了几个不同方法中的问题,如在mounted中设置定时器,但需要在destroyed中来清除定时器,将同一功能的代码拆分到不同的位置,造成后期代码维护的困难。

♥︎♥︎♥︎♥︎为什么Vuex的mutation不能做异步操作

Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,否则无法被devtools所监测。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

♥︎♥︎♥︎♥︎说明一下封装vue组件的原则和方法

[封装vue组件的原则和技巧]: “https://blog.csdn.net/leilei__66/article/details/119348108

♥︎♥︎♥︎♥︎Object.defineProperty有什么缺点

1:无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;

2:只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。

♥︎♥︎♥︎♥︎axios是什么?怎么使用?描述使用它实现登录功能的流程?

axios 是请求后台资源的模块。

通过npm install axios -S来安装,在大多数情况下我们需要封装拦截器。

在实现登录的过程中我们一般在请求拦截器中来加入token,在响应请求器中通过判断后端返回的状态码来对返回的数据进行不同的处理。如果发送的是跨域请求,需在配置文件中 config/index.js 进行代理配置。

♥︎♥︎♥︎♥︎ computed和watcher的区别?watch实现原理?watch有几种写法?

计算属性computed :

  1. 支持缓存,只有依赖数据发生改变,才会重新进行计算
  2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
  3. computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
  4. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
  5. 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。

侦听属性watch:

  1. 不支持缓存,数据变,直接会触发相应的操作;
  2. watch支持异步;
  3. 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
  4. 当一个属性发生变化时,需要执行对应的操作;一对多;
  5. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,

immediate:组件加载立即触发回调函数执行,

deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。

注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。

watch工作原理:

watch在一开始初始化的时候,会读取一遍监听的数据的值,此时那个数据就收集到watch的watcher了,然后你给watch设置的handler,watch 会放入watcher的更新函数中,当数据改变时,通知watch的watcher进行更新,于是你设置的handler就被调用了。

♥︎♥︎♥︎♥︎如果你是leader,做管理系统项目Vue和React怎么选择?

评估项目成员的水平,如果成员js基础较好、编码能力较强则选择React,否则Vue。

评估系统的大小,如果想构建生态系统,则选择React,如果要求而快,简单和“能用就行”,则选择Vue。

评估系统运行环境,如果你想要一个同时适用于Web端和原生APP的框架,请选择React(RN)。

♥︎♥︎♥︎♥︎Vue和React区别

1:Vue 使用的是 web 开发者更熟悉的模板与特性,Vue的API跟传统web开发者熟悉的模板契合度更高,比如Vue的单文件组件是以模板+JavaScript+CSS的组合模式呈现,它跟web现有的HTML、JavaScript、CSS能够更好地配合。React 的特色在于函数式编程的理念和丰富的技术选型,Vue更加注重web开发者的习惯。

2:Vue跟React的最大区别在于数据的reactivity,就是反应式系统上。Vue提供反应式的数据,当数据改动时,界面就会自动更新,而React里面需要调用方法SetState。我把两者分别称为Push-based和Pull-based

♥︎♥︎♥︎♥︎Vue路由实现的底层原理

在Vue中利用数据劫持defineProperty在原型prototype上初始化了一些getter,分别是router代表当前Router的实例 、route 代表当前Router的信息。在install中也全局注册了router-view,router-link,其中的Vue.util.defineReactive, 这是Vue里面观察者劫持数据的方法,劫持route,当route触发setter方法的时候,则会通知到依赖的组件。

接下来在init中,会挂载判断是路由的模式,是history或者是hash,点击行为按钮,调用hashchange或 者popstate的同时更*route,*route的更新会触发route-view的重新渲染。

♥︎♥︎♥︎♥︎如何封装一个通用组件

通用组件的封装就是对可复用组件的解耦和样式复用,为了解耦一般数据都是通过父组件传递过来,在子组件中进行数据处理,对于一些较为复杂的数据可能还需要做数据验证,为了避免高耦合,逻辑最好放在父组件中,通过自定义事件将数据回传,子组件只是一个承载体,这样既降低耦合,保证子组件中数据和逻辑不会混乱。如果同一组件需要适应不同需求时,我们需要配合slot来使用,可以通过具名插槽灵活地解决了不同场景同一组件不同配置的问题。

♥︎♥︎♥︎♥︎Vue proxy的原理

主要通过Proxy对对象进行绑定监听处理,通过new Map对对象的属性操作进行处理,将要执行的函数匹配到存到对应的prop上面,通过每次的访问触发get方法,进行存方法的操作,通过修改触发set的方法,此时执行回调监听的函数,这样达到修改数据和视图的

♥︎♥︎♥︎♥︎ defineProperty在数据劫持后是如何通知数据的更新和视图的更新的

vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情

我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

2.实现一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图。

3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)。

♥︎♥︎♥︎♥︎Vue是怎么做虚拟DOM的diff的

[Vue虚拟DOM以及diff算法]: “https://blog.csdn.net/wytraining/article/details/110831015

♥︎♥︎♥︎♥︎Vuex如何实现跨组价的数据监听

[Vuex跨组价的数据监听]: “https://blog.csdn.net/Flowering_Vivian/article/details/108868907

♥︎♥︎♥︎♥︎axios谁封装的,怎么封装的

// 使用axios用于对数据的请求
import axios from 'axios'
// 创建axios实例
const instance = axios.create({
 baseURL: baseURL + version,
 timeout: 5000
})
// 创建请求的拦截器
instance.interceptors.request.use(config => {
 config.headers['Authorization'] = localStorage.getItem('token')
 return config
}, error => {
 return Promise.reject(error)
})
// 创建响应的拦截器
instance.interceptors.response.use(response => {
 let res = null
 // 对相应的数据进行过滤
 if (response.status === 200) {
 if (response.data && response.data.err === 0) {
 res = response.data.data
 } else if (response.data.err === -1) {
 return alert('token无效')
 }
 } else {
 return alert('请求失败')
 }
 return res
}, error => {
 return Promise.reject(error)
})
export default instance

♥︎♥︎♥︎♥︎虚拟dom为什么会提高性能?

1、虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom 的 diff 算法避免了没有必要的 dom 操作,从而提高性能。
2、用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中,当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。

♥︎♥︎♥︎♥︎Vue的computed的原理

1.computed大致流程

computed是vue中的计算属性,在依赖的值发生变化的时候进行重新计算,否则使用缓存。

前面说到computed只有在依赖发生变化才会重新计算,那么如何得知computed的值发生了变化呢

这主要是Watcher中的dirty属性,dirty属性为true时,说明computed中的值需要重新计算,dirty为false时,则说明依赖没有变化,不需要重新计算

当计算属性的值发生变化时,计算属性的watcher和组件的watcher都会得到通知。

计算属性的watcher会将dirty置为true,组件的Watcher得到通知,同样将dirty属性置为false,重新计算值,用于本次渲染。

简单来说,computed就是定义在vm上的一个getter属性,这个getter属性被触发时会做两件事

  1. 计算当前属性的值,此时会使用Watcher去观察计算属性中用到的所有其他数据的变化。同时将计算属性的Watcher的dirty属性设置为false.

2.当计算属性中用到的数据发生变化时,将得到通知从而进行重新渲染

Watcher中的depend和evaluate方法是专门用于实现计算属性的两个API

export default class Watcher {
     constructor(vm, expOrFn, cb, options) {
         if(options) {
             this.lazy = !!options.lazy;
         }  else {
             this.lazy = false;
         }
         this.dirty = this.lazy;
         this.value = this.lazy ? undefined : this.get()
     }
     evaluate() {
        this.value = this.get()
        this.dirty = false; 
    }
    depend() {
        let i = this.deps.length
        while(i--) {
            this.deps[i].depend()
        }
    }
}

执行depend方法可以将组件中的watcher实例添加到dep实例的依赖列表中。换句话说,this.deps是计算属性中用到的所有状态的dep实例,而依次执行了dep实例的depend方法就是将组件的watcher依次加入到这些dep实例的依赖列表。这就实现了让组件的watcher观察计算属性中用到的所有的状态的变化。

2.Computed逻辑变化

computed在vue2.5.2中的实现发生了一些变化,因为之前的computed的计算存在一些逻辑上的漏洞,因为只要依赖的值发生了变化,vue就认为值发生了变化,组件会重新走一遍渲染的流程,但实际上UI不会由变化,浪费了一些性能。

改动之后的逻辑:

组件的watcher不再监听计算属性的变化,而是让计算属性的watcher得到通知后,计算一次计算属性的值,如果发现这一次计算出来的值与上一次计算出来的值不一样,再去主动通知组件的watcher进行重新渲染。

♥︎♥︎♥︎♥︎Vue中子组件是否可以修改props,如果想修改的话如何修改

vue是单向数据流,父组件通过props传值给子组件,如果在子组件中修改会报错,一般是不在子组件中修改props的,但偶尔有需要在子组件有修改props,这里介绍三种可以修改子组件props的方法。

1.父组件用sync修饰,子组件通过$emit('update:参数',值)函数去修改。在项目中通常可以用改方法关闭弹框

<template>
    //父组件
   <CommonDialog
     :title="dialogTitle"
     :showDialog.sync="isShowDialog"
     :footer="true"
     :width="dialogWidth"
   >
      ....
   </CommonDialog>
</template>
//子组件 弹框是否打开props: showDialog
<el-dialog :title="title" :visible="showDialog" :show-close="false" :width="width">
      <i class="el-dialog__headerbtn" @click="closeModal">
        <span class="iconfont iconclose"></span>
      </i>
      <div class="dialog-body">
          <slot></slot>
      </div>
      <div v-if="!footer" slot="footer" class="dialog-footer">
        <slot name="footer"></slot>
      </div>
    </el-dialog>
//关闭弹框------子组件修改props
 closeModal() {
   this.$emit("update:showDialog", false);
 },

2.如果props是对象,在子组件中修改props

<template>
  <div class="csd-select">
    <el-popover
      placement="bottom"
      :width="width"
      :trigger="config.trigger"
      :config="config"
      transition="fade-in"
      popper-class="casade-selector"
      v-model="options_show"
    >
    ...
    </el-popover>
  </div>
</template>
<script>
export default {
  name: "CasadeSelector",
  props: {
    config: {
      type: Object,
      //让props默认返回
      default() {
        return {};
      }
    },
  },
</script>

3.props是双向绑定的

<template>
    <control v-model="deviceF"></control>
</template>

 //v-model里面写上我们要传给子组件,并且还会在子组件中改变这个值
 <script>
 import control from '@/components/control'
 export default {
    name:"test",
    components: {
        control
    },
    data () {
        return {
        deviceF: true,
        }
    }
}
 </script>
<template>
   <div>
       {{device}}
       <button @click="look">改变值</button>
   </div>
</template>
<script>
export default {
 data () {
   return {
     device: this.value,  //定义一下
   }
 },
 props: ['value'],//因为想要改变device,所以device要写成value,这里是写死的
 components: {
 },
 methods: {
   look () {
     this.device = !this.device
     this.$emit('input', this.device)  //这样this.device就会被修改;前面的input是固定写死的
   }
 }
}
</script>

♥︎♥︎♥︎♥︎Vuex刷新页面数据会丢失吗?咋解决的?

1、问题描述:页面刷新的时候vuex里的数据会重新初始化,导致数据丢失。因为vuex里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,vuex里面的数据就会被重新赋值。

2、解决思路:

办法一:将vuex中的数据直接保存到浏览器缓存中(sessionStorage、localStorage、cookie)

办法二:在页面刷新的时候再次请求远程数据,使之动态更新vuex数据 办法三:在父页面向后台请求远程数据,并且在页面刷新前将vuex的数据先保存至sessionStorage(以防请求数据量过大页面加载时拿不到返回的数据)

3、 解决过程:

3.1、选择合适的浏览器存储

3.2、解决方案由于state里的数据是响应式,所以sessionStorage存储也要跟随变化,而且只能通过mutations来改变state中的值。 首先在用户登录成功之后,然后把用户信息,菜单信息通过actions分发保存至vuex中。然后在菜单页面计算vuex中state的菜单数据,将数据解析组装成前端组件所需的格式,然后渲染组件,生成菜单树。如果页面没有刷新,则一切正常。

监听浏览器刷新前事件,在浏览器刷新之前就把vuex里的数据保存至sessionStorage中,刷新成功后如果异步请求的数据还没返回则直接获取sessionStorage里的数据,否则获取vuex里的数据。

♥︎♥︎♥︎♥︎什么是Vue.nextTick()?

$nextTick 是在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM,意思是 等你dom加载完毕以后再去调用nextTick()里面的数据内容


♥︎♥︎♥︎♥︎♥︎你知道Vue响应式数据原理吗?Proxy与Object.defineProperty优劣对比?

1、响应式原理

vue的响应式实现主要是利用了Object.defineProperty的方法里面的setter 与getter方法的观察者模式来实现。在组件初始化时会给每一个data属性注册getter和setter,然后再new 一个自己的Watcher对象,此时watcher会立即调用组件的render函数去生成虚拟DOM。在调用render的时候,就会需要用到data的属性值,此时会触发getter函数,将当前的Watcher函数注册进sub里。当data属性发生改变之后,就会遍历sub里所有的watcher对象,通知它们去重新渲染组件。

2、proxy的优势如下:

Proxy 可以直接监听对象而非属性,可以直接监听数组的变化;

Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是

Object.defineProperty 不具备的;

Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;

3、Object.defineProperty 的优势如下:

兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill(垫片)来弥补

♥︎♥︎♥︎♥︎♥︎ vue-router路由实现原理

[vue-router路由模式]: “https://blog.csdn.net/weixin_43638968/article/details/109303363

♥︎♥︎♥︎♥︎♥︎ Vue3中的双向数据绑定proxy

Proxy相当于在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,我们可以这样认为,Proxy是Object.defineProperty的全方位加强版,它解决了之前defineProperty无法监听到数组变化等缺点。

♥︎♥︎♥︎♥︎♥︎ Vue和React中diff算法区别

vue和react的diff算法,都是忽略跨级比较,只做同级比较。vue diff时调动patch函数,参数是vnode和oldVnode,分别代表新旧节点。

1.vue对比节点。当节点元素相同,但是classname不同,认为是不同类型的元素,删除重建,而react认为是同类型节点,只是修改节点属性。

2.vue的列表对比,采用的是两端到中间比对的方式,而react采用的是从左到右依次对比的方式。当一个集合只是把最后一个节点移到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移到第一个。总体上,vue的方式比较高效。

♥︎♥︎♥︎♥︎♥︎Vue 响应式原理

[Vue 响应式原理]: “https://juejin.cn/post/6844903561327820808

♥︎♥︎♥︎♥︎♥︎ nextTick知道吗、实现的原理是什么?是宏任务还是微任务?

微任务

原理:nextTick方法主要是使用了宏任务和微任务,定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空队列。

作用: nextTick用于下次Dom更新循环结束之后执行延迟回调,在修改数据之后使用nextTick用于下次Dom更新循环结束之后执行延迟回调,在修改数据之后使用nextTick用于下次Dom更新循环结束之后执行延迟回调,在修改数据之后使用nextTick,则可以在回调中获取更新后的DOM。

♥︎♥︎♥︎♥︎♥︎ Vue项目常见优化点

1、首屏加载优化

2、路由懒加载

{ 
 path: '/',
 name: 'home', 
 component: () => import('./views/home/index.vue'), 
 meta: { isShowHead: true }
}

3、开启服务器 Gzip

开启 Gzip 就是一种压缩技术,需要前端提供压缩包,然后在服务器开启压缩,文件在服务器压缩后传给浏览器,浏览器解压后进行再进行解析。首先安装 webpack 提供的compression-webpack-plugin进行压缩,然后在 vue.config.js:

const CompressionWebpackPlugin = require('compression-webpack-plugin')
const productionGzipExtensions = ['js', 'css']......plugins: [ 
 new CompressionWebpackPlugin(
 { 
 algorithm: 'gzip', 
 test: new RegExp('\.(' + productionGzipExtensions.join('|') + ')$'), 
 threshold: 10240, 
 minRatio: 0.8 
 }
)]....

4、启动 CDN 加速

我们继续采用 cdn 的方式来引入一些第三方资源,就可以缓解我们服务器的压力,原理是将我们的压力分给其他服务器点。

5、代码层面优化

computed 和 watch 区分使用场景

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

watch:类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

v-if 和 v-show 区分使用场景 v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show则适用于需要非常频繁切换条件的场景。这里要说的优化点在于减少页面中 dom 总数,我比较倾向于使用 v-if,因为减少了 dom 数量。

v-for 遍历必须为 item 添加 key,且避免同时使用 v-if v-for 遍历必须为 item 添加 key,循环调用子组件时添加 key,key 可以唯一标识一个循环个体,可以使用例如 item.id 作为 key 避免同时使用 v-if,vfor 比 v-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度。

6、Webpack 对图片进行压缩

7、避免内存泄漏

8、减少 ES6 转为 ES5 的冗余代码

前端面试大全javaScript

前端面试题大全(JavaScript)

前端面试题类目分类

  • HTML5 + CSS3
  • JavaScript
  • Vue + Vue3
  • React
  • Webpack
  • 服务端

考点频率 :♥︎ 、 ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎ ♥︎

JavaScript

♥︎ a 标签中,如何禁用 href 跳转页面或定位链接?

在做页面时,如果想做一个链接点击后不做任何事情,或者响应点击而完成其他事情,可以设置其属性 href = “#”,但是,这样会有一个问题,就是当页面有滚动条时,点击后会返回到页面顶端,用户体验不好。

一、阻止默认事件

阻止默认事件的发生有两个方法:

return false
e.preventDefault()

通过阻止a标签默认事件就可以禁止a标签跳转!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <a href="http://www.baidu.com/">去百度</a>
    <!-- 阻止默认事件两种方法 -->
    <a href="http://www.baidu.com/" onclick="return false">百度1</a>
    <a href="http://www.baidu.com/" id="go">百度1</a>
    <script>
        //获取元素节点
        let go = document.querySelector("#go")
        //点击事件
        go.onclick = function(e){
            //阻止默认事件
            e.preventDefault();
        }
    </script>
</body>
</html>

二、javascript:void(0)少用

javascript:void(0)这是一种伪协议,void(0)在IE中可能会引起一些问题,比如:造成gif动画停止播放等。

void是javascipt自身的操作符,它表示的是只执行表达式,但没有返回值。

//这两种写法都可以,都表示一个死链接,点击无效果
<a href="javascript:void(0)">点击无法跳转(javascript:void(0))</a>
<a href="javascript:void(null)">点击无法跳转(javascript:void(null))</a>

三、javascript:;

javascript:; 执行一段空白的javascript语句,返回空或者false值,从而防止链接跳转

javascript:; 写法比 javascript:void(0) 好,后者存在浏览器兼容bug

<a href="javascript:;">点击无法跳转(javascript:;)</a>

四、href(####)

使用2个到4个#,见的大多是”####”,也有使用”#all”等其他的。一个无意义的标签指定,不做任何处理。(这种方法会在浏览器地址栏上出现“####”)

<a href="####">点击无法跳转</a>

‼️注:制是用一个“#”是不行的,会默认刷新页面回到页面顶部

总结:

<a href="javascript:void(0)">点击无法跳转(javascript:void(0))</a>
<a href="javascript:void(null)">点击无法跳转(javascript:void(null))</a>
<a href="javascript:;">点击无法跳转(javascript:;)</a>
<a href="####">点击无法跳转</a>
<a href="http://www.baidu.com/" onclick="return false">百度1</a>
<a href="http://www.baidu.com/" id="go">百度1</a>
<script>
        //获取元素节点
        let go = document.querySelector("#go")
        //点击事件
        go.onclick = function(e){
            //阻止默认事件
            e.preventDefault();
        }
</script>

♥︎♥︎ 介绍一下JS的内置类型有哪些?

JS中共有七种内置数据类型

基本数据类型:null、undefined、boolean、string、number、symbol
引用数据类型:object(function、regexp、array、date)

‼️注:
1.Js中的数字类型是浮点型,不是整型
2.NaN属于数字类型

主要区别:存储位置不同。
1.基本数据类型存储在栈中,占据空间小,大小固定,可以被频繁使用;
2.引用数据类型存储在堆中,栈中存放指针,指针指向堆中该实体的起始地址,占据空间大,大小不固定,不适合被频繁使用。

♥︎♥︎ 解释下 let 和 const 的块级作用域?

let

ES6中新增let关键字,用于声明变量。其用法类似var,但是所声明的变量只在let命令所在的块内有效。(块级作用域文章后面会进行讲解)

//let的特点
1.只作用于块级作用域。
2.不存在变量提升。
3.不允许重复声明。
4.暂时性死区(TDZ)。

1.只作用于块级作用域。

{
    let a=10;
    var b=5;
    console.log(a,b) //输出a=10 b=5;
}
console.log(b); //输出 b=5;
console.log(a); //报错 a is not defined

上述代码let声明的变量在块级作用域{ }中是有效的,但是在最后一行的输出就会直接报错,因为没有在块作用域{}中,a就没有被定义。

let命令更适合for循环

{
    for(let i=0;i<5;i++){
        console.log(i); //输出  0,1,2,3,4
    }
    console.log(i);//报错 i is not defined
}
{
    for(var i=0;i<5;i++){
        console.log(i);  //输出  0,1,2,3,4
    }
    console.log(i);  //输出 5
}

变量i是let声明的 当前i只在本轮循环有效,且只在本块级作用域有效,所以每一次循环的i其实都是一个新的变量。

你可能会问:如果每一轮循环的变量都是重新声明的 那它怎么知道上一轮循环的值,从而计算出本轮的值呢?

这是因为javascript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算

另外,for循环还有一个特别之处,for一个整体算一个块级作用域,那么设置循环变量那部分是一个父级作用域,而循环体内部是一个单独的子作用域

2.不存在变量提升

let与var的区别,var存在变量提升,但是let不存在变量提升。
那么什么是变量提升呢?
变量提升:就是变量在声明前可以使用,而且不会报错,但是值为undefined。
意思是var声明的变量在其声明前使用是可以的不会报错,
但是let声明的变量一定要在其声明之后使用,否则就会报错。

{
    console.log(a);//输出 undefined
    var a=2;
}
{
    console.log(b) //报错 Cannot access 'b' before initialization
    let b=5;
}

上诉代码用var声明的a会发生变量提升,那么值为undefined。
而有let声明的b就不发生变量提升,在声明前使用,b就是一个不存在的,那么就直接报错。

3.不允许重复声明

let不允许在相同的作用域内重复声明同一个变量

{
    var a=10;
    let a=5;//直接报错 Identifier 'a' has already been declared
}
{
    let b=10;
    let b=5; //直接报错 Identifier 'b' has already been declared
}
{
    const c=10;
    let c=5; //直接报错 Identifier 'c' has already been declared
}
{
    function f1(foo){
    let foo; //报错  同时也不能在函数内部重新声明参数
    }
}

4.暂时性死区

只要用let声明的变量,那么这个变量就绑定这个块级作用域,不再受外部的影响;

{
    var tmp=123;
    if(true){
        tmp="abc";//这里将会报错
        let tmp;
    }
}

因为let存在暂时性死区,只要区块中存在let与const命令,就算在最开始用var定义了tmp,但是在if里又是一个块级作用域,在if这个区域就形成一个封闭作用域,然后在let声明前对tmp的使用就会报错。

const

const声明一个只读常量,一旦声明,常量的值就不能被改变。
const的与let的特点基本相同,同样不存在变量提升,存在暂时性死区,只能在声明后使用.

{
    const p=8.12;
    p=10;//报错 Assignment to constant variable.
}

const实际并不是变量的值不能改动,而是指向的那个内存地址所保存的数据不得改动;数据一半存储在堆中,
对于简单的基本数据类型(string number boolean null undefined)值保存在指向的那个内存地址,因此等同于常量。
但对于引用数据类型(object array ),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能够控制了。

{
    const foo={};
    foo.num=123;//这里就会成功不会报错,可以添加新的属性。
}

块级作用域

ES5中只有全局作用域和函数作用域,没有块级作用域,ES6新增了块级作用域。

{
    function f1(){
    let a=5;
    if(true){
        let a=10;
        }
    console.log(a);//输出 a=5。
}

上述代码有2个代码块,f1是以个大的块作用域,里面嵌套了一个if小的块作用域,在if之外输出a是不会被影响的,从而输出a=5。

块级作用域是可以嵌套的。

嵌套的作用域中外层的作用域不能访问内层作用域的变量,但是内层可以访问外层的.

//例子1
{
    function f1(){
        let a=10;
        if(true){
            console.log(a);
        }
    }
    f1();//输出a=10;
}
//例子2
{
    function f1(){
        let b=0;
        console.log(b);//输出b=0;
        function f2(){
            let c=10;
        }
        console.log(c);
    }
    f1();//报错  c没有定义
}

♥︎♥︎ 将 [1,2] 与 [3,[4]] 合并为 [1,2,3,[4]]

JS数组合并方法:

let arr3 = [1,2].concat([3,[4]]); //[1,2,3,[4]]

♥︎♥︎ Array.forEach() 与 Array.map() 的区别,Array.slice() 与 Array.splice() 的区别?

forEach

forEach不支持return (没有返回值),对原来的数组也没有影响。但是我们可以自己通过数组的索引来修改原来的数组

var ary = [12,23,24,42,1];
var res = ary.forEach(function (item,index,input) {
 input[index] = item10;
})
console.log(res); //-->undefined;
console.log(ary); //-->会对原来的数组产生改变;

map

map支持return返回值,也不影响原数组,但是会返回一个新的数组

var ary = [12,23,24,42,1];
var res = ary.map(function (item,index,input) {
 return item*10;
})
console.log(res);//-->[120,230,240,420,10];
console.log(ary);//-->[12,23,24,42,1];

slice

array.slice(start,end)函数是取array数组中指定的一些元素:
根据数组下标start和end,两个参数为取值的开始和结束下标,取出的值不包括end位置的值,生成一个新数组作为返回值;
这里end可以为空,为空则取从start位置到数组结尾的元素,生成新数组。

splice

array.splice(start,length,insert_one......)函数则是直接在原数组进行删除、添加、替换元素的操作:
start为数组删除元素的开始下标,
length为从start位置开始array删除元素的个数,
后面参数为删除之后array重新插入的数据内容,插入位置为删除位置,而非数组开头或末尾,
返回值为array删除的元素组成的数组。
显而易见,splice函数可以用来对数组元素进行替换。由splice操作后的数组array,数组中内容如果已
经改变,就再也找不回array在splice之前的模样。

♥︎♥︎ 将 1234567 转换为 1,234,567

千位分割符方法有以下几种

只适用整数

function format (num) {
    var reg=/\d{1,3}(?=(\d{3})+$)/g; 
    return (num + '').replace(reg, '$&,');
}
console.log(format(1233453464)) //1,233,453,464
function format (num) {
    num=num+'';//数字转字符串
     var str="";//字符串累加
     for(var i=num.length- 1,j=1;i>=0;i--,j++){
         if(j%3==0 && i!=0){//每隔三位加逗号,过滤正好在第一个数字的情况
             str+=num[i]+",";//加千分位逗号
             continue;
         }
         str+=num[i];//倒着累加数字
     }
     return str.split('').reverse().join("");//字符串=>数组=>反转=>字符串
   }
console.log(format(12312432343543)) //12,312,432,343,543

整数部分加小数部分不动

var format = (str) => str.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
console.log(format('12313453.63')) //12,313,453.63

一整套千分位转换

    // 将金额类型转为数字类型
    function toNum(str) {
        return str.replace(/\,|\¥/g, "");
    }

    // 保留两位小数(四舍五入)
    function toPrice(num) {
        num = parseFloat(toNum(num)).toFixed(2).toString().split(".");
        num[0] = num[0].replace(new RegExp('(\\d)(?=(\\d{3})+$)','ig'),"$1,");
        return "¥" + num.join(".");
    }
    
    // 保留两位小数(不四舍五入)
    function toPrice1(num) {
        num = parseFloat(toNum(num).replace(/(\.\d{2})\d+$/,"$1")).toFixed(2).toString().split(".");
        num[0] = num[0].replace(new RegExp('(\\d)(?=(\\d{3})+$)','ig'),"$1,");
        return "¥" + num.join(".");;
    }
    
    // 不处理小数部分
    function toPrice2(num) {
        var source = toNum(num).split(".");
        source[0] = source[0].replace(new RegExp('(\\d)(?=(\\d{3})+$)','ig'),"$1,");
        return "¥" + source.join(".");
    }
    console.log(toNum('1,234,345.4')) //1234345.4
    console.log(toPrice('12123213.43545')) //¥12,123,213.44
    console.log(toPrice1('12312312.43645')) //¥12,312,312.43
    console.log(toPrice2('12312345.324')) //¥12,312,345.324

♥︎♥︎ Promise.resolve(Promise.resolve(1)).then(console.log) 输出?

Promise.resolve(Promise.resolve(1)).then(console.log) //1

♥︎♥︎ 使用 setTimeout 模拟 setInterval 的功能做一个60秒的倒数计时

var num=60;
var max=0;
function incrementNumber(){
  num--;
  if(num>max){
      setTimeout(incrementNumber,1000);
      console.log(num);
  }
  else{
    console.log('Done');
  }
}
setTimeout(incrementNumber,1000);

♥︎♥︎ 写一个 function,清除字符串前后的空格。(兼容所有的浏览器)

function trim(str) {
    if (str && typeof str === "string") {
    return str.replace(/(^\s)|(\s)$/g,""); //去除前后空白符
    }
   }
console.log('  2342.4  ')//  2342.4
console.log(trim('  2342.4  '))//2342.4

♥︎♥︎ 使用正则表达式验证邮箱格式。

var reg = /^(\w)+(\.\w+)*@(\w)+((\.\w{2,3}){1,3})$/;
var email = "1246815351@qq.com";
console.log(reg.test(email)); //true

♥︎♥︎ JavaScript 中 callee 和 caller 的作用

1.callee

callee是对象的一个属性,该属性是一个指针,指向参数arguments对象的函数

作用:就是用来指向当前对象

返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文.

callee是arguments 的一个属性成员,它表示对函数对象本身的引用,这有利于匿名

函数的递归或者保证函数的封装性

2.caller

caller是函数对象的一个属性,该属性保存着调用当前函数的函数的引用(指向当前函数的直接父函数)

返回一个对函数的引用,该函数调用了当前函数。

functionName.caller

functionName 对象是所执行函数的名称。

‼️注意:

对于函数来说,caller 属性只有在函数执行时才有定义。 如果函数是由 Javascript 程序的顶层调用的,

那么 caller 包含的就是 null 。

♥︎♥︎ 统计字符串中字母个数或统计最多的字母数。

function count(){
    var str="aaaabbbbsssseeeeqqaaasss";
    var obj={};
    for(var i=0;i<str.length; i++){
        if(obj[str.charAt(i)]==undefined){  
        //对象初始化;如果key在对象中找不到,那么会返回undefined,反向思维
            obj[str.charAt(i)]= 1;
        } else{
            obj[str.charAt(i)]++;
        }
    }
    return obj;  //{ a: 7, b: 4, s: 7, e: 4, q: 2 }
    //取出各个字母和它的个数,作为一个新对象保存在obj对象;
}

function numberCount(obj){
    var mm="";
    for(var m in obj){
        if(mm==""){
            mm=new Object();
            mm[m]=obj[m];
        }else{
            for(var j in mm){
               if(mm[j]<obj[m]){
                   //清空原来的内容
                   mm=new Object();
                   //放入新的内容
                   mm[m]=obj[m];
               }
            }
        }
    }
    console.log(mm); //{ a: 7 }
}
numberCount(count());

♥︎♥︎ 简述一下面象对象的六法则

  1. 单一职责原则:一个类只做它该做的事情

  2. 开闭原则:软件实体应当对扩展开放,对修改关闭

  3. 依赖倒转原则:面向接口编程

  4. 接口隔离原则:接口要小而专,绝不能大而全

  5. 合成聚合复用原则:优先使用聚合或合成关系复用代码

  6. 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解(低耦合)

♥︎♥︎ jQuery 优点和缺点

优点

1.出色的浏览器兼容性
2、出色的DOM操作的封装,使他具备强大的选择器,可以进行快速的DOM元素操作
3、可靠的事件处理机制、jq在处理事件绑定的时候是相当的可靠
4、完善的ajax(对ajax的封装非常好,不需要考虑复杂的浏览器的兼容和XMLhttprequest对象的创建
和使用)
5、支持链式操作(什么是链式操作?通过‘.’来操作)和隐士迭代
6、减少服务器的压力和带宽并且加快了加载速度(为什么这么说?原因就是:当你打开网页之前打开了
其他的网页,并且该网页也用了cdn的方式来
// 调用父类原型push方法 return super.push(...args)
加载相同版本的jq文件,那么,浏览器就不会加载第二次,为啥舍近求远呢,和生活中的道理一样一样
的!)
7、支持丰富的插件,当然你也可以自定义插件,再加上jq的文档也很丰富,对于程序员来说,是一件非常美好的事情

缺点

1.不能向后兼容。
每一个新版本不能兼容早期的版本。举例来说,有些新版本不再支持某些selector,新版jQuery却没有保留对它们的支持,而只是简单的将其移除。这可能会影响到开发者已经编写好的代码或插件。
2.插件兼容性。
与上一点类似,当新版jQuery推出后,如果开发者想升级的话,要看插件作者是否支持。通常情况下,在最新版jQuery版本下,现有插件可能无法正常使用。开发者使用的插件越多,这种情况发生的几率也越高。我有一次为了升级到jQuery 1.3,不得不自己动手修改了一个第三方插件。
3.多个插件冲突。
在同一页面上使用多个插件时,很容易碰到冲突现象,尤其是这些插件依赖相同事件或selector时最为明显。这虽然不是jQuery自身的问题,但却又确实是一个难于调试和解决的问题。
4.jQuery的稳定性。
jQuery没有让浏览器崩溃,这里指的是其版本发布策略。jQuery 1.3版发布后仅过数天,就发布了一个漏洞修正版1.3.1。他们还移除了对某些功能的支持,可能会影响许多代码的正常运行。我希望类似修改不要再出现。
5.对动画和特效的支持差。
在大型框架中,jQuery核心代码库对动画和特效的支持相对较差。但是实际上这不是一个问题。目前在这方面有一个单独的jQuery UI项目和众多插件来弥补此点。

♥︎♥︎ AMD 怎么加载文件的?

AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。它是一个在浏览器端模块化开发的规范

由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出

requireJS主要解决两个问题

1.多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器

2.js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长

require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

♥︎♥︎ jQuery 怎么找到事件源元素

$(".btn").click(function(e){
 // e 就是事件对象
 e.target; // 事件的目标 dom
 e.currentTarget; // 事件处理程序正在处理事件的那个元素
 e.srcElement; // 事件的目标 ie
});

♥︎♥︎ 2018/01/01 转换成 2018年/1月/1日

function fun(str){
    var date = new Date(str)
    return date.getFullYear()+'年/'+ date.getMonth()+'月/'+date.getDate()+'日' 
}
console.log(fun('2018/03/01'))//2018年/2月/1日

♥︎♥︎ for-in 循环会遍历出原型上的属性吗?怎么避免遍历到原型上的属性

使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问

只遍历对象自身的属性,而不遍历继承于原型链上的属性,需要使用hasOwnProperty 方法过滤一下。

Object.prototype.say="cgl";
 var person ={
 age: 18
 };
 for (var key in person) {
 if(person.hasOwnProperty(key)){
 console.log(key, eval("person."+key));//age 18
 }
 }

♥︎♥︎ ES6 箭头函数和普通函数有什么差异?

  1. 相比普通函数更简洁的语法

  2. 没有this,捕获其所在上下文的 this 值,作为自己的 this 值

  3. 不能使用new,箭头函数作为匿名函数,是不能作为构造函数的,不能使用new

  4. 不绑定arguments,用rest参数…解决

  5. 使用call()和apply()调用:由于 this 已经在词法层面完成了绑定,通过 call() 或 apply() 方法调用一个函数时,只是传入了参数而已,对 this 并没有什么影响:

  6. 箭头函数没有原型属性

  7. 不能简单返回对象字面量

let fun5 = ()=>({ foo: x }) //如果x => { foo: x } //则语法出错

  1. 箭头函数不能当做Generator函数,不能使用yield关键字

  2. 箭头函数不能换行

♥︎♥︎ import export commonJS 对比区别

ES6和commonJS的一些区别

从语法的角度上看,ES6模块化的import 和 export 是一个内置的标识,而commonJS的

module.exports 和 require 分别是js对象和方法。其ES6模块化和commonJS的实现方式不同。

1.ES6是在编译的时候导入文件,而commonJS是编译完成后,在通过require方法导入,并读取文件导出的文件,并返回一个module.exports对象

2.在ES6模块的内部this是问undefined,而commonJS的this为一个空对象

3.ES6模块输出的是一个引用,而commonJS模块输出的是一个值的引用

♥︎♥︎ 为什么 JavaScript 是单线程

js作为主要运行在浏览器的脚本语言,js主要用途之一是操作DOM。

举一个栗子,如果js同时有两个线程,同时对同一个dom进行操作,这时浏览器应该听哪个线程的,如何判断优先级?

为了避免这种问题,js必须是一门单线程语言

♥︎♥︎ JS的基本数据类型判断有什么方法?

1.typeof

typeof'';// string 有效
 typeof 1;// number 有效
 typeof Symbol();// symbol 有效
 typeof true;//boolean 有效
 typeof undefined;//undefined 有效
 typeof new Function();// function 有效
 typeof null;//object 无效
 typeof [] ;//object 无效
 typeof new Date();//object 无效
 typeof new RegExp();//object 无效

2.instanceof

instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型

3.constructor

当一个函数 F被定义时,JS引擎会为F添加 prototype 原型,然后再在 prototype上添加一个constructor 属性

4.toString

toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。
 Object.prototype.toString.call('') ; // [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(undefined) ;// [object Undefined]
 Object.prototype.toString.call(null) ;// [object Null]
 Object.prototype.toString.call(newFunction()) ;// [object Function]
 Object.prototype.toString.call(newDate()) ;// [object Date]
 Object.prototype.toString.call([]) ;// [object Array]
 Object.prototype.toString.call(newRegExp()) ;// [object RegExp]
 Object.prototype.toString.call(newError()) ;// [object Error]
 Object.prototype.toString.call(document) ;// [object HTMLDocument]
 Object.prototype.toString.call(window) ;//[object global] window 是全局对象 global 的引用

♥︎♥︎ 介绍下事件代理,主要解决什么问题

  1. 绑定事件太多,浏览器占用内存变大,严重影响性能

  2. Ajax出现,局部刷新盛行,每次加载完,都要重新绑定事件

  3. 部分浏览器移除元素时,绑定的事件没有被及时移除,导致内存泄漏,严重影响性能

  4. Ajax中重复绑定,导致代码耦合性过大,影响后期维护

♥︎♥︎ new 的原理是什么?通过 new 的方式创建对象和通过字面量创建有什么区别?

new操作符的作用如下:

1.创建一个空对象

2.由this变量引用该对象

3.该对象继承该函数的原型

4.把属性和方法加入到this引用的对象中

5.新创建的对象由this引用,最后隐式地返回this。

区别:

字面量创建不会调用 Object构造函数, 简洁且性能更好;

♥︎♥︎ 数组去重的方法

方法一:双层循环,外层循环元素,内层循环时比较值

如果有相同的值则跳过,不相同则push进数组

Array.prototype.distinct = function(){
 var arr = this,
 result = [],
 i,
 j,
 len = arr.length;
 for(i = 0; i < len; i++){
 for(j = i + 1; j < len; j++){
 if(arr[i] === arr[j]){
 j = ++i;
 }
 }
 result.push(arr[i]);
 }
 return result;
}
var arra = [1,2,3,4,4,1,1,2,1,1,1];
arra.distinct(); //返回[3,4,2,1]

方法二:利用splice直接在原数组进行操作

双层循环,外层循环元素,内层循环时比较值 值相同时,则删去这个值

注意点:删除元素之后,需要将数组的长度也减1.

优点:简单易懂

缺点:占用内存高,速度慢

Array.prototype.distinct = function (){
 var arr = this,
 i,
 j,
 len = arr.length;
 for(i = 0; i < len; i++){
 for(j = i + 1; j < len; j++){
 if(arr[i] == arr[j]){
 arr.splice(j,1);
 len--;
 j--;
 }
 }
 }
 return arr;
};
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,];
var b = a.distinct();
console.log(b.toString()); //1,2,3,4,5,6,56

方法三:利用对象的属性不能相同的特点进行去重

Array.prototype.distinct = function (){
 var arr = this,
 i,
 obj = {},
 result = [],
 len = arr.length;
 for(i = 0; i< arr.length; i++){
 if(!obj[arr[i]]){ //如果能查找到,证明数组元素重复了
 obj[arr[i]] = 1;
 result.push(arr[i]);
 }
 }
 return result;
};
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,];
var b = a.distinct();
console.log(b.toString()); //1,2,3,4,5,6,56

方法四:数组递归去重

运用递归的思想

先排序,然后从最后开始比较,遇到相同,则删除

Array.prototype.distinct = function (){
 var arr = this,
 len = arr.length;
 arr.sort(function(a,b){ //对数组进行排序才能方便比较
 return a - b;
 })
 function loop(index){
 if(index >= 1){
 if(arr[index] === arr[index-1]){
 arr.splice(index,1);
 }
 loop(index - 1); //递归loop函数进行去重
 }
 }
 loop(len-1);
 return arr;
};
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,56,45,56];
var b = a.distinct();
console.log(b.toString()); //1,2,3,4,5,6,45,56

方法五:利用indexOf以及forEach

Array.prototype.distinct = function (){
 var arr = this,
 result = [],
 len = arr.length;
 arr.forEach(function(v, i ,arr){ //这里利用map,filter方法也可以实现
 var bool = arr.indexOf(v,i+1); //从传入参数的下一个索引值开始寻找是否存在重复
 if(bool === -1){
 result.push(v);
 }
 })
 return result;
};
var a = [1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,3,2,3,3,2,2,1,23,1,23,2,3,2,3,2,3];
var b = a.distinct();
console.log(b.toString()); //1,23,2,3

方法六:利用ES6的set

Set数据结构,它类似于数组,其成员的值都是唯一的。

利用Array.from将Set结构转换成数组

function dedupe(array){
 return Array.from(new Set(array));
}
dedupe([1,1,2,3]) //[1,2,3]
//拓展运算符(...)内部使用for...of循环
//123
let arr = [1,2,3,3];
let resultarr = [...new Set(arr)]; 
console.log(resultarr); //[1,2,3]

♥︎♥︎ 并行和并发的区别是什么?

并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度

并发表示多个任务同时都要执行

♥︎♥︎ 是否所有函数都有 prototype 一说?

1. 使用Function.prototype.bind创建的函数对象
function abc(){console.log('abc')}
var binded = abc.bind(null)
binded() //abc
console.log(binded.prototype) //undefined

2. 箭头函数也没有
var abc = ()=>{console.log('abc')}
abc() //abc
console.log(abc.prototype) //undefined

♥︎♥︎ 为什么 await 在 forEach 中不生效?如何解决?

lodash的forEach和[].forEach不支持await, forEach 只支持同步代码。

解决方法1:使用 for…of

解决方法2:使用 for循环

解决方法3:让forEach支持async await

forEach 在正常情况像下面这么写肯定是做不到同步的,程序不会等一个循环中的异步完成再进行下一个循环。原因很明显,在下面的模拟中,while 循环只是简单执行了 callback,所以尽管 callback 内使用了 await ,也只是影响到 callback 内部。

arr.myforeach(async v => {
 await fetch(v);
});

要支持上面这种写法,只要稍微改一下就好

Array.prototype.myforeach = async function (fn, context = null) {
 let index = 0;
 let arr = this;
 if (typeof fn !== 'function') {
 throw new TypeError(fn + ' is not a function');
 }
 while (index < arr.length) {
 if (index in arr) {
 try {
 await fn.call(context, arr[index], index, arr);
 } catch (e) {
 console.log(e);
 }
 }
 index ++;
 }
};

♥︎♥︎ 请描述一下 cookies,sessionStorage 和 localStorage 的区别?

1.存储大小

cookie数据大小不能超过4k。

sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。

2.有效时间

localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;

sessionStorage 数据在当前浏览器窗口关闭后自动删除。

cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
  1. 数据与服务器之间的交互方式
cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端

sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。

♥︎♥︎♥︎ 介绍一下类型转换

/——————-显式转换———————/

1. toString() // 转化为字符串,不可以转null和underfined
2. Number() // 转换为数字,字符串中有一个不是数值的字符,返回NaN
3. parseInt() // 转换为数字,第一个字符不是数字或者符号就返回NaN
4. String() // 转换为字符串,
5. Boolean() // 转换为布尔值

/——————-隐式转换(+-)———————/

当 JavaScript 尝试操作一个 “错误” 的数据类型时,会自动转换为 “正确” 的数据类型

1. num + "" -> String
2. num + bool -> num
// 当加号运算符时,String和其他类型时,其他类型都会转为 String;其他情况,都转化为Number类型
3. string - num -> num
// 其他运算符时, 基本类型都转换为 Number,String类型的带有字符的比如:
4. 'a1' - num -> NaN
// 与undefined 一样。

/——————-隐式转换(逻辑表达式)———————/

1. 对象和布尔值比较
对象和布尔值进行比较时,对象先转换为字符串,然后再转换为数字,布尔值直接转换为数字
[] == true; //false []转换为字符串'',然后转换为数字0,true转换为数字1,所以为false
2. 对象和字符串比较
对象和字符串进行比较时,对象转换为字符串,然后两者进行比较。
[1,2,3] == '1,2,3' // true [1,2,3]转化为'1,2,3',然后和'1,2,3', so结果为true;
3. 对象和数字比较
对象和数字进行比较时,对象先转换为字符串,然后转换为数字,再和数字进行比较。
[1] == 1; // true `对象先转换为字符串再转换为数字,二者再比较 [1] => '1' => 1 所以结果为true
4. 字符串和数字比较
字符串和数字进行比较时,字符串转换成数字,二者再比较。
'1' == 1 // true
5. 字符串和布尔值比较
字符串和布尔值进行比较时,二者全部转换成数值再比较。
'1' == true; // true
6. 布尔值和数字比较
布尔值和数字进行比较时,布尔转换为数字,二者比较。
true == 1 // true###

♥︎♥︎♥︎ 对闭包的看法,为什么要用闭包?说一下闭包的原理以及应用场景?闭包的 this 指向问题?

闭包的作用:

  1. 在外部访问函数内部的变量

  2. 让函数内的局部变量可以一直保存下去

  3. 模块化私有属性和公共属性

闭包的原理:

全局变量生存周期是永久,局部变量生存周期随着函数的调用介绍而销毁。

闭包就是 在函数中定义且成为该函数内部返回的函数的自由变量 的变量,该变量不会随着外部函数调用

结束而销毁。

(注:不光是变量,函数内声明的函数也可以形成闭包)

当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭

包。

♥︎♥︎♥︎ 简述闭包的问题以及优化

闭包的缺点:占用内层空间 大量使用闭包会造成 栈溢出

由于闭包会一直占用内存空间,直到页面销毁,我们可以主动将已使用的闭包销毁:

将闭包函数赋值为null 可以销毁闭包。

♥︎♥︎♥︎ 如何确定 this 指向?改变 this 指向的方式有哪些?

this 指向:

1. 全局上下文(函数外)
无论是否为严格模式,均指向全局对象。注意:严格模式下全局对象为undifined

2. 函数上下文(函数内)
默认的,指向函数的调用对象,且是最直接的调用对象:
简单调用,指向全局对象注意:严格模式下全局对象为undifined,某些浏览器未实现此标准也可能会是window

改变this指向的方式:

1. 第一种: new关键字改变this指向
//构造函数版this
function Fn(){
 this.user = "李某";
}
var a = new Fn();
console.log(a.user); //李某
/----------------------------------------/ 

2. 第二种: call()
// 把b添加到第一个参数的环境中,简单来说,this就会指向那个对象
var a = {
 user:"李某",
 fn:function(){
 console.log(this.user); //李某
 }
}
var b = a.fn;
b.call(a); //若不用call,则b()执行后this指的是Window对象
/----------------------------------------/ 

3. 第三种:apply()
// apply方法和call方法有些相似,它也可以改变this的指向,也可以有多个参数,但是不同的是,第二个参数必须是一个数组
var a = {
 user:"李某",
 fn:function(){
 console.log(this.user); //李某
 }
}
var b = a.fn;
b.apply(a);
/----------------------------------------/ 

4. 第四种:bind()
// bind方法返回的是一个修改过后的函数,
// bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。
var a = {
 user:"李某",
 fn:function(){
 console.log(this.user); //李某
 }
}
var b = a.fn;
var c = b.bind(a);
c();

♥︎♥︎♥︎ 介绍箭头函数的 this

由于箭头函数不绑定this, 它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值

1. 所以 call() / apply() / bind() 方法对于箭头函数来说只是传入参数,对它的 this 毫无影响。
2. 考虑到 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略

作为方法的箭头函数this指向全局window对象,而普通函数则指向调用它的对象

♥︎♥︎♥︎ 谈一下你对原型链的理解,画一个经典的原型链图示

[原型及原型链]: “https://segmentfault.com/a/1190000021232132

♥︎♥︎♥︎ ES5/ES6 的继承除写法以外还有什么区别?

1. ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加 到 this 上
(Parent.apply(this)).
2. ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必 须先调用父类的super()方 法),然后再用子类的构造函数修改 this。 
3. ES5 的继承时通过原型或构造函数机制来实现。
4. ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关 键字实现继承。
5. 子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因 为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。 如果不调用 super 方法,子类得不到this 对象。
6. 注意 super 关键字指代父类的实例,即父类的 this 对象。 注意:在子类构造函数中,调用 super 后,才可使用 this关键字,否则报错

♥︎♥︎♥︎ 异步解决方案有哪些?

//---------1.回调函数callback:----------//
被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数。如setTimeOut,ajax请求,readFile等。
例:
function greeting(name) {
 alert('Hello ' + name);
}
function processUserInput(callback) {
 var name = prompt('请输入你的名字。');
 callback(name);
}
processUserInput(greeting);
优点:
解决了异步的问题。
缺点:
回调地狱:多个回调函数嵌套的情况,使代码看起来很混乱,不易于维护。

//---------2.事件发布订阅:---------//
当一个任务执行完成后,会发布一个事件,当这个事件有一个或多个‘订阅者’的时候,会接收到这个事件的发布,执行相应的任务,这种模式叫发布订阅模式。如node的events,dom的事件绑定
例:
document.body.addEventListener('click',function(){
 alert('订阅了');
},false);
document.body.click(); 
优点:
时间对象上的解耦。
缺点:
消耗内存,过度使用会使代码难以维护和理解

//---------3.Promise:---------//
Promise是es6提出的异步编程的一种解决方案。
Promise 对象有三种状态:
pending: 初始状态,既不是成功,也不是失败状态。
fulfilled: 意味着操作成功完成。
rejected: 意味着操作失败。
promise的状态只能从pending变成fulfilled,和pending变成rejected,状态一旦改变,就不会再改变,且只有异步操作的结果才能改变promise的状态。
例:
let promise = new Promise(function (resolve, reject) {
 fs.readFile('./1.txt', 'utf8', function (err, data) {
 resolve(data)
 })
})
promise
 .then(function (data) {
 console.log(data)
 })
优点:
解决了回调地狱的问题,将异步操作以同步操作的流程表达出来。
缺点:
无法取消promise。如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。当执行多个Promise时,一堆then看起来也很不友好。

//---------4.Generator:---------//
Generator是es6提出的另一种异步编程解决方案,需要在函数名之前加一个*号,函数内部使用yield语句。Generaotr函数会返回一个遍历器,可以进行遍历操作执行每个中断点yield。
例:
function * count() {
 yield 1
 yield 2
 return 3
}
var c = count()
console.log(c.next()) // { value: 1, done: false }
console.log(c.next()) // { value: 2, done: false }
console.log(c.next()) // { value: 3, done: true }
console.log(c.next()) // { value: undefined, done: true }
优点:
没有了Promise的一堆then(),异步操作更像同步操作,代码更加清晰。
缺点:
不能自动执行异步操作,需要写多个next()方法,需要配合使用Thunk函数和Co模块才能做到自动执
行。

//---------5.async/await:---------//
async是es2017引入的异步操作解决方案,可以理解为Generator的语法糖,async等同于Generator和co模块的封装,async 函数返回一个 Promise。
例:
async function read() {
 let readA = await readFile('data/a.txt')
 let readB = await readFile('data/b.txt')
 let readC = await readFile('data/c.txt')
 console.log(readA)
 console.log(readB)
 console.log(readC)
}
read()
优点:
内置执行器,比Generator操作更简单。async/await比*yield语义更清晰。返回值是Promise对象,可以用then指定下一步操作。代码更整洁。可以捕获同步和异步的错误。

♥︎♥︎♥︎ async 和 await 、promise的区别 和 这两个的本质

Promise概念:

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,简单地说,Promise好比容器,里面存放着一些未来才会执行完毕(异步)的事件的结果,而这些结果一旦生成是无法改变的

async await概念:

async await也是异步编程的一种解决方案,他遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。

两者的区别:
Promise的出现解决了传统callback函数导致的“地域回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
async await与Promise一样,是非阻塞的。
async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。

♥︎♥︎♥︎ 移动端点击事件 300ms 延迟如何去掉?原因是什么?

300毫秒原因:

当用户第一次点击屏幕后,需要判断用户是否要进行双击操作,于是手机会等待300毫秒。

解决方法:FastClick.js

FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。

FastClick的实现原理是在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click

事件,并把浏览器在300ms之后的click事件阻止掉。

属性 属性作用
name 字段为一个cookie的名称。
value 字段为一个cookie的值。
domain 字段为可以访问此cookie的域名。
path 字段为可以访问此cookie的页面路径。 比如domain是abc.com,path是/test,那么只有/test路径下的页面可以读取此cookie。
expires/Max-Age 字段为此cookie超时时间。若设置其值为一个时间,那么当到达此时间后,此cookie失效。不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。
Size 字段 此cookie大小。
http 字段 cookie的httponly属性。若此属性为true,则只有在http请求头中会带有此cookie的信息,而不能通过document.cookie来访问此cookie。
secure 字段 设置是否只能通过https来传递此条cookie
1、secure属性
当设置为true时,表示创建的 Cookie 会被以安全的形式向服务器传输,也就是只能在 HTTPS 连接中被浏览器传递到服务器端进行会话验证,如果是 HTTP 连接则不会传递该信息,所以不会被窃取到Cookie
的具体内容。
2、HttpOnly属性
如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,
这样能有效的防止XSS攻击。
3、Expire属性
设置Cookie的失效时间:

♥︎♥︎♥︎ var let const的区别

  1. var声明的变量会挂载在window上,而let和const声明的变量不会

  2. var声明变量存在变量提升,let和const不存在变量提升

  3. let和const声明形成块作用域

  4. 同一作用域下let和const不能声明同名变量,而var可以

  5. 使用let/const声明的变量在当前作用域存在暂存死区

  6. const一旦声明必须赋值,不能使用null占位,声明后不能再修改,如果声明的是复合类型数据,可以修改其属性

♥︎♥︎♥︎ document load 和 documen ready的区别

DOM文档解析:

解析html结构
加载脚本和样式文件
解析并执行脚本
构造html的DOM模型 //ready
加载图片等外部资源文件
页面加载完毕 //load

document load:

load是当页面所有资源全部加载完成后(包括DOM文档树,css文件,js文件,图片资源等),执行一个函数,load方法就是onload事件。

documen ready:

构造html的DOM模型加载完毕后触发

♥︎♥︎♥︎ 如何用 setTImeout 来实现 setInterval?

为什么要使用setTImeout 来实现 setInterval?

1.不去关心回调函数是否还在运行

在某些情况下,函数可能需要比间隔时间更长的时间去完成执行。比如说是用setInterval每隔5秒对远端

服务器进行轮询,网络延迟,服务器无响应以及其他因素将会阻止请求按时按成。结果会导致返回一串

无必要的排成队列请求。

2.忽视错误

因为某些原因,setInterval调用的代码中会出现一个错误,但是代码并不会中止执行而是继续执行错误

的代码。

3.缺乏灵活性

除了前面提到的缺点之外,我非常希望setInterval方法能有一个表明执行次数的参数而不是无休止的执

行下去。

function interval(func, w, t){
 var interv = function(){
 if(typeof t === "undefined" || t-- > 0){
 setTimeout(interv, w);
 try{
 func.call(null);
 }
 catch(e){
 t = 0;
 throw e.toString();
 }
 }
 };
 setTimeout(interv, w);
};

♥︎♥︎♥︎ 如何判断 user 对象里有没有 a 这个属性?如果把user对象中所有的属性都输出出来?(var user ={‘a’: ‘19’, ‘b’: ‘18’, ‘c’: ‘16’})

(var user = {'a': '19', 'b': '18', 'c': '16'})
如何判断 user 对象里有没有 a 这个属性?

1.js对象的Object.hasOwnProperty()方法
返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。
let obj = new Object();
obj.a = "123";
console.log(obj.hasOwnProperty('a')) // true
console.log(obj.hasOwnProperty('b')) // false

2.把user对象中所有的属性都输出出来
for(item for user){
 console.log(item)
}

♥︎♥︎♥︎ 实现一个函数 add()

function curry(func) {

    return function curried(...args) {
      if (args.length >= func.length) {
        return func.apply(this, args);
      } else {
        return function(...args2) {
          return curried.apply(this, args.concat(args2));
        }
      }
    };
  
  }

  function sum(a, b, c) {
    return a + b + c;
  }
  
  let curriedSum = curry(sum);
  
  console.log(curriedSum(1, 2, 3)); // 6,仍然可以被正常调用
  console.log( curriedSum(1)(2,3) ); // 6,对第一个参数的柯里化
  console.log( curriedSum(1)(2)(3) ); // 6,全柯里化

♥︎♥︎♥︎ 如何避免回调地狱?

1. Promise 对象就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将
回调函数的嵌套,改成链式调用。
promise只有两个状态resolve和reject,当它触发任何一个状态后,它会将当前的值缓存起来,并在有
回调函数添加进来的时候尝试调用回调函数,如果这个时候还没有触发resolve或者reject,那么回调函
数会被缓存,等待调用,如果已经有了状态(resolve或者reject),则立刻调用回调函数。并且所有回调函
数在执行后都立即被销毁。

2. ES6 co/yield方案
yield: Generator 函数是协程在 ES6 的实现,而yield是 Generator关键字, 异步操作需要暂停的
地方,都用yield语句注明。
co: co 模块是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函
数的自动执行。

3. ES7 async/await 方案
async/await是es7的新标准,并且在node7.0中已经得到支持。
它就是 Generator 函数的语法糖,async函数就是将 Generator 函数的星号(*)替换成async, 将yield替换成await,仅此而已。可以理解官方对co和Generator 封装方案。

♥︎♥︎♥︎ 简述同步和异步的区别

同步:

同步的思想是:所有的操作都做完,才返回给用户。这样用户在线等待的时间太长,给用户一种卡死了

的感觉(就是系统迁移中,点击了迁移,界面就不动了,但是程序还在执行,卡死了的感觉)。这种情

况下,用户不能关闭界面,如果关闭了,即迁移程序就中断了。

异步:

将用户请求放入消息队列,并反馈给用户,系统迁移程序已经启动,你可以关闭浏览器了。然后程序再

慢慢地去写入数据库去。这就是异步。但是用户没有卡死的感觉,会告诉你,你的请求系统已经响应

了。你可以关闭界面了。

同步和异步本身是相对的:

同步就相当于是 当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当

服务端做完了才返回到客户端。这样的话客户端需要一直等待。用户使用起来会有不友好。

异步就是,当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样

节约了时间,提高了效率。

存在就有其道理 异步虽然好 但是有些问题是要用同步用来解决,比如有些东西我们需要的是拿到返回的

数据在进行操作的。这些是异步所无法解决的。

♥︎♥︎♥︎ jQuery 的事件委托方法 on,live,delegate之间有区别?

live 把事件委托交给了document(根节点),document 向下去寻找符合条件的元素(), 不用等待
document加载结束也可以生效。
delegate可指定事件委托对象,相比于live性能更优,直接锁定指定选择器;
on事件委托对象选填,如果不填,即给对象自身注册事件,填了作用和delegate一致。

♥︎♥︎♥︎ 简述下 Promise 对象

Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结
果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
Promise对象有以下2个特点:
1.对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、
Resolved(已完成)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操
作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法
改变。
2.一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可
能:从Pending变为Resolved;从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会
再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象田静回调函数,也会立即得
到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果
的。
有了Promise对象,就可以把异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此
外,Promise对象提供了统一的接口,使得控制异步操作更加容易。

♥︎♥︎♥︎ 数组扁平化,不用 api

function myFlat(arr){
    let res = [];
    for(let i=0; i<arr.length; i++){ 
    if(arr[i] instanceof Array){
    res = res.concat(myFlat(arr[i]));
    }else {
    res.push(arr[i]);
    }
    }
    return res;
   }
   let arr = [1,[2,3,[4,5]]];
   console.log(myFlat(arr)) //[ 1, 2, 3, 4, 5 ]

♥︎♥︎♥︎ 用 JavaScript 实现观察者模式

function BusinessOne(name){
 this.name = name;
 //订阅者的集合
 this.subscribers = new Array();
}
//订阅者的发送消息的方法(推模式)
BusinessOne.prototype.delive = function(news){
 var self = this;
 //给每一个订阅者发送消息
 this.subscribers.forEach(
 function(fn){
 //调用接受者处理信息的函数
 fn(news,self);
 }
 )
}
//扩展公共订阅的函数,和取消订阅的函数
Function.prototype.subscribe = function(publisher){
 var that = this;
 //some 访问数组度i型并且以参数的形式传回回调函数中
 //只要至少有一次返回是true那么some就是true
 var alreadyExists = publisher.subscribers.some(
 function(el){
 //处理不能重复订阅的功能
 if(el == that){
 return;
 }
 }
 );
 //没用订阅你就可以订阅
 if(!alreadyExists){
 publisher.subscribers.push(that);
 }
 return this;
}
//取消
Function.prototype.unsubscribe = function(publisher){
 var that = this;
 publisher.subscribers = publisher.subscribers.filter(
 function(el){
 if(el !== that){
 return el;
 }
 }
 );
 return this;
};

♥︎♥︎♥︎ 谈谈垃圾回收机制方法以及内存管理

垃圾回收方式

① 标记清除
工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离
开环境”。标记“离开环境”的就回收内存。

② 引用计数
工作原理:跟踪记录每个值被引用的次数。一旦没有引用,内存就直接释放了。

内存管理

什么时候触发垃圾回收?

垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就

变成了一个值得思考的问题。

1、合理的GC方案:(1)、遍历所有可访问的对象; (2)、回收已不可访问的对象。

2、GC缺陷: (1)、停止响应其他操作;

3、GC优化策略: (1)、分代回收(Generation GC);(2)、增量GC

♥︎♥︎♥︎ 开发过程中遇到内存泄漏的问题都有哪些?

  1. 当页面中元素被移除或替换时,若元素绑定的事件仍没被移除,在IE中不会作出恰当处理,此时要

先手工移除事件,不然会存在内存泄露。

  1. 由于是函数内定义函数,并且内部函数–事件回调的引用外暴了,形成了闭包。闭包可以维持函数

内局部变量,使其得不到释放。

♥︎♥︎♥︎ 请编写获取当前窗口地址中查询参数name的值,当前窗口地址为:https://foo.com/?id=1&name=tom

function GetQueryString(name){
 var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
 var r = window.location.search.substr(1).match(reg);
 if(r!=null)
 return unescape(r[2]); 
 return null;
}

♥︎♥︎♥︎ 已知a,b两个构造函数,现在 let c = new a(),如何在c的存储地址不变的情况下,改变c的继承(c->a 转为 c->b)

改变原型链:通过改变C的prototype为b,实现内存地址不动,改变继承

♥︎♥︎♥︎ 浏览器有哪些兼容问题,你封装过什么插件

//1.滚动条到顶端的距离(滚动高度)
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

//2.滚动条到左端的距离
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;

//3. IE9以下byClassName
function byClassName(obj,className){
 //判断是否支持byClassName
 if(obj.getElementsByClassName){
 //支持
 return obj.getElementsByClassName(className);
 }else{
 //不支持
 var eles = obj.getElementsByTagName('*'); //获取所有的标签
 var arr = []; //空数组,准备放置找到的对象
 //遍历所有的标签
 for(var i = 0,len = eles.length;i < len;i ++){
 //找出与我指定class名相同的对象
 if(eles[i].className === className){
 arr.push(eles[i]); //存入数组
 }
 }
 return arr; //返回
 }
}

//4. 获取非行内样式兼容 IE:currentStyle 标准:getComputedStyle
function getStyle(obj,attr){
 return window.getComputedStyle ? getComputedStyle(obj,true)[attr] : obj.currentStyle[attr];
}
//div.style.width = '';设置样式
//obj['属性']: 对象是变量时,必须用对象['属性']获取。

//5. 获取事件对象的兼容
evt = evt || window.event

//6. 获取鼠标编码值的兼容
function getButton(evt){
 var e = evt || window.event;
 if(evt){
 return e.button;
 }else if(window.event){
 switch(e.button){
 case 1 : return 0;
 case 4 : return 1;
 case 2 : return 2;
 }
 }
}

//7. 获取键盘按键编码值的兼容
var key = evt.keyCode || evt.charCode || evt.which;

//8. 阻止事件冒泡的兼容
e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;

//9. 阻止超链接的默认行为的兼容
evt.preventDefault ? evt.preventDefault() : evt.returnValue = false;

//10. 添加事件监听器的兼容
function addEventListener(obj,event,fn,boo){
 if(obj.addEventListener){
 obj.addEventListener(event,fn,boo);
 }else if(obj.attachEvent){
 obj.attachEvent('on' + event,fn);
 }
}

//11. 移除事件监听器的兼容
function removeEventListener(obj,event,fn,boo){
 if(obj.removeEventListener){
 obj.removeEventListener(event,fn,boo);
 }else if(obj.detachEvent){
 obj.detachEvent('on' + event,fn);
 }
}

//12. 获取事件源的兼容
var target = event.target || event.srcElement;

♥︎♥︎♥︎ 如何判断一个对象是否为数组,函数

方法一: instanceof:
var arr=[];
console.log(arr instanceof Array) //返回true

方法二: constructor:
console.log(arr.constructor == Array); //返回true

方法三: Array.isArray()
console.log(Array.isArray(arr)); //返回true

♥︎♥︎♥︎ 写一个函数,接受可变个数参数,且每个参数均为数字,返回参数的最大值。

function myMax(){
 return Math.max(arguments)
}

♥︎♥︎♥︎ 请写出 ES6 Array.isArray()

if (!Array.isArray){
 Array.isArray = function(arg){
 return Object.prototype.toString.call(arg) === '[object Array]';
 };
}

♥︎♥︎♥︎ 实现一个函数 clone,可以对 JavaScript 中的5种主要数据类型进行值复制。

// 方法一:
Object.prototype.clone = function() {
 var o = this.constructor === Array ? [] : {};
 for (var e in this) {
 o[e] = typeof this[e] === "object" ? this[e].clone() : this[e];
 }
 return o;
};

//方法二:
/**
 * 克隆一个对象
 * @param Obj
 * @returns
 */
function clone(Obj) {
 var buf;
 if (Obj instanceof Array) {
 buf = []; //创建一个空的数组
 var i = Obj.length;
 while (i--) {
 buf[i] = clone(Obj[i]);
 }
 return buf;
 } else if (Obj instanceof Object) {
 buf = {}; //创建一个空对象
 for (var k in Obj) {
 //为这个对象添加新的属性
 buf[k] = clone(Obj[k]);
 }
 return buf;
 } else {
 //普通变量直接赋值
 return Obj;
 }
}

♥︎♥︎♥︎ 假如A页面我定义了一个定时器,然后跳到B页面如果让A页面的定时器暂停

方法1:在beforeDestroy()等生命周期结束阶段内清除定时器:
beforeDestroy() {
 clearInterval(this.timer);
 this.timer = null;
}

方法2:通过$once这个事件侦听器器在定义完定时器之后的位置来清除定时器。
const timer = setInterval(() =>{
 // 某些定时器操作
}, 500);
// 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
this.$once('hook:beforeDestroy', () => {
 clearInterval(timer);
})

♥︎♥︎♥︎ promise的实现原理,如果我现在向服务器发送一个请求,但是我后悔了,不想让服务器返回数据,去实现一个delay

取消结束Promise的方法?

1. 返回一个pending状态的Promise,原Promise链会终止
Promise.resolve().then(() => {
 console.log('ok1')
 return new Promise(()=>{}) // 返回“pending”状态的Promise对象
}).then(() => {
 // 后续的函数不会被调用
 console.log('ok2')
}).catch(err => {
 console.log('err->', err)
})

2. Promise.race竞速方法
let p1 = new Promise((resolve, reject) => {
 resolve('ok1')
})
let p2 = new Promise((resolve, reject) => {
 setTimeout(() => {resolve('ok2')}, 10)
})
Promise.race([p2, p1]).then((result) => {
 console.log(result) //ok1
}).catch((error) => {
 console.log(error)
})

3. 当Promise链中抛出错误时,错误信息沿着链路向后传递,直至捕获
Promise.resolve().then(() => {
 console.log('ok1')
 throw 'throw error1'
}).then(() => {
 console.log('ok2')
}, err => { 
 // 捕获错误
 console.log('err->', err)
}).then(() => { 
 // 该函数将被调用
 console.log('ok3')
 throw 'throw error3'
}).then(() => {
 // 错误捕获前的函数不会被调用
 console.log('ok4')
}).catch(err => {
 console.log('err->', err)

Axios如何取消请求?

//1.第一种通过CancelToken.source工厂方法创建cancel token
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/user/12345', {
 cancelToken: source.token
}).catch(function(thrown) {
 if (axios.isCancel(thrown)) {
 console.log('Request canceled', thrown.message);
 } else {
 // 处理错误
 }
});
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

//2.第二种通过传递executor函数到CancelToken的构造函数来创建cancel token
var CancelToken = axios.CancelToken;
var cancel;
axios.get('/user/12345', {
 cancelToken: new CancelToken(function executor(c) {
 // executor 函数接收一个 cancel 函数作为参数
 cancel = c;
 })
});
// 取消请求
cancel();

♥︎♥︎♥︎ CommonJS 和 RequireJS 的实现原理

commonjs是通过module.exports导出模块,用require引入一个模块,原理:闭包

requirejs是通过define定义导出模块,用require引入模块。

♥︎♥︎♥︎ 面向对象编程与面向过程编程的区别?

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设

计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。

而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消

息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

♥︎♥︎♥︎ eval 是做什么的?性能怎么样?安全如何?

它的功能是把对应的字符串解析成js代码并运行,

应该避免使用eval,因为不安全,非常耗性能(2次,一次解析成js语句,一次执行)

‼️注意:在项目里写js代码的时候,禁止使用的,因为有安全因素。

♥︎♥︎♥︎ 数据类型(判断,==和===)堆栈、内存泄漏及垃圾回收机制

1,== 判断值是否相等; === 判断值和数据类型是否严格相等

2,Javascript堆栈和垃圾回收机制

//堆栈溢出

当储存的数据导到某一限制时就会造成堆栈溢出

//内存泄漏

当不断向堆中存储数据,而不进行清理,这就是内存泄漏

//垃圾回收机制(清除孤儿机制)

语言当中一般分两种,一种是自动清理,一种是手动清理(GC),js中只有自动清理

垃圾回收机制就是将引用对中的地址的对象设置为null,并且将所有引用该地址的对象都设置为null,并且移除事件侦听,不会即时清除,垃圾回收车会根据内存的情况在适当的时候进行清除堆中的对象 内存到达一定程度了才会进行回收

♥︎♥︎♥︎ swiper 插件从后台获取数据没问题,css 代码啥的也没问题,但是图片不动,应该怎么解决?

主要原因:

swiper提前初始化了,而这个时候,数据还没有完全出来。

解决方法

从swiper 入手,在swiper中写 observer:true/observeParents:true

 let myswiper = new Swiper(".swiper-container" , {
 autoplay: true,
 loop: true,
 // observer 修改swiper子元素时自动初始化swiper
 observer:true,
 // observeParents 包括当前父元素的swiper发生变更时也会初始化swiper
 observeParents:true,
 })

从 Vue 入手,vue中专门提供了提供了一个方法nextTick() 用于解决dom的先后执行问题。

 mounted(){
 this.$nextTick(function(){
 // ...操作
 let myswiper = new Swiper(".swiper-container" , {
 autoplay: true,
 loop: true
 })
 })
 }

♥︎♥︎♥︎ ES6 class 关键字原理跟 function 什么区别?

function 可以用call apply bind 的方式 来改变他的执行上下文
但是class 却不可以 class 虽然本质上也是一个函数 但是 其内(babel)部做了一层代理 来禁止了这种
行为

关于构造器constructor
 在function定义的构造函数中,其prototype.constructor属性指向构造器自身
 在class定义的类中,constructor其实也相当于定义在prototype属性上

重复定义
 function会覆盖之前定义的方法
 class会报错

原型或者类中方法的枚举
 class中定义的方法不可用Object.keys(Point.prototype)枚举到
 function构造器原型方法可被Object.keys(Point.prototype)枚举到,除过constructor
 所有原型方法属性都可用Object.getOwnPropertyNames(Point.prototype)访问到

♥︎♥︎♥︎ iframe 跨域问题,页面之间怎么传值?

一般有两个解决方案,一个是建立一个代理页面,通过代理页面传值,

另一个方法是通过H5的postMessage方法传值,今天用的是第二种。

首先,在父页面A中建立一个iframe,其中src要写好子页面B的地址,然后在A页面中写如下方法:

var iframe = document.getElementById("onemap"); var msg = {loginName:'arcgis',loginPassword:'Esri1234'}; var childDomain = "https://geoplat.training.com"; iframe.contentWindow.postMessage(msg,childDomain);

记住,childDomain与A的iframe的src地址不一样,childDomain是域,而src是域中的一个页面,msg是传输的信息,可以是字符串,也可以是对象。

上面的方法一定要写在一个函数中,并通过点击事件调用,如果希望iframe开始为空,点击后在设置src,可以在设置src之后,通过setTimeout设置一定时间后在传输信息。

在子页面B中,通过对window添加事件获取传输过来的信息:

window.addEventListener("message",function(obj){ 
  var name = obj.data.loginName; 
  var password = obj.data.loginPassword; login.iframeChildLogin(name,password); },false);

这样就完成了从不同域的父页面向子页面传值的过程

♥︎♥︎♥︎ 简述 commonJS、AMD 和 CMD

CommonJS导出模块的方法是exports,导入模块的是require,具体规范如下
1)如果一个JS文件中存在exports或require,该JS文件是一个模块
2)模块内的所有代码均为隐藏代码,包括全局变量、全局函数,这些全局的内容均不应该对全局变量造成任何污染
3)如果一个模块需要暴露一些API提供给外部使用,需要通过exports导出,exports是一个空的对象,你可以为该对象添加任何需要导出的内容
4)如果一个模块需要导入其他模块,通过require实现,require是一个函数,传入模块的路径即可返回该模块导出的整个内容

【注】CommonJS只是一个规范,相当于告诉你按什么标准制造汽车,但是具体怎么制造还是得看生产商。因此,有了规范以后,nodejs就去实现模块化了

AMD
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
AMD 推崇依赖前置。

CMD
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
CMD 推崇依赖就近

♥︎♥︎♥︎ require.js 源码看过吗?怎么做到异步加载的

/*
Creates the node for the load command. Only used in browser envs.
*/
req.createNode = function (config, moduleName, url) {
var node = config.xhtml ?
 document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true;
return node;
};

 requirejs 导入模块的方式实际就是创建脚本标签,一切的模块都需要经过这个方法创建。requirejs使用 onload 事件来处理回调函数:

♥︎♥︎♥︎ 0.1+0.2 等不等于 0.3?自己封装一个让他们相等的方法

在正常的数学逻辑思维中,0.1+0.2=0.3这个逻辑是正确的,但是在JavaScript中0.1+0.2!==0.3,这是

为什么呢?这个问题也会偶尔被用来当做面试题来考查面试者对JavaScript的数值的理解程度。

在JavaScript中的二进制的浮点数0.1和0.2并不是十分精确,在他们相加的结果并非正好等于0.3,

而是一个比较接近的数字 0.30000000000000004 ,所以条件判断结果为false。

方法1:设置一个误差范围值,通常称为”机器精度“,而对于Javascript来说,这个值通常是2^-52,而在
ES6中,已经为我们提供了这样一个属性:Number.EPSILON,而这个值正等于2^-52。这个值非常非常
小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0,。这个时候我们只要判断
(0.1+0.2)-0.3小于Number.EPSILON,在这个误差的范围内就可以判定0.1+0.2===0.3为true。
function numbersequal(a,b){ 
 return Math.abs(a-b)<Number.EPSILON;
}

方法2:转为整数运算

♥︎♥︎♥︎ 跨域是什么?有哪些解决跨域的方法和方案?

什么是跨域?

所谓的同源是指,域名、协议、端口均为相同。
所谓的跨域,不同的域名、协议、端口皆为不同域
一个域与另一个域名、协议或者端口不同的域的之间访问都叫跨域

解决跨域的方法和方案:

1:通过服务端代理请求。如PHP,服务端语言php是没有跨域限制的,让服务器去别的网站获取内容然
后返回给页面。

2:第二种:jsonp跨域
 1. jsonp跨域就是利用script标签的跨域能力请求资源
 2. jsonp与ajax没有半毛钱关系!!
 3. 浏览器的同源策略限制了js的跨域能力,但没有限制link img iframe script 的跨域行为
 实现方式:
 1. 利用js创建一个script标签,把json的url赋给script的scr属性,
 2. 把这个script插入到页面里,让浏览器去跨域获取资源
 3. JS先声明好回调函数,插入页面后会代为执行该函数,并且传入json对象为其参数。
 注意:
 1. jsonp只针对get请求
 2. script标签加载回来的资源会被当成js在全局执行

3:CORS 跨域资源共享(xhr2)
 CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)
 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
 整个CORS通信过程,都是浏览器自动完成,不需要用户参与
 对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样
 实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信

4:nginx代理跨域
 通过nginx服务器转发跨域请求,达到跨域的目的

♥︎♥︎♥︎ 什么是函数式编程?什么的声明式编程?

函数式编程:

函数式编程和声明式编程是有所关联的,因为他们思想是一致的:即只关注做什么而不是怎么做。但函数式编程不仅仅局限于声明式编程。

函数式编程最重要的特点是“函数第一位”,即函数可以出现在任何地方,比如你可以把函数作为参数传递给另一个函数,不仅如此你还可以将函数作为返回值。

声明式编程:

声明式编程是以数据结构的形式来表达程序执行的逻辑。它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。

SQL 语句就是最明显的一种声明式编程的例子,例如:

SELECT * FROM collection WHERE num > 5

除了 SQL,网页编程中用到的 HTML 和 CSS 也都属于声明式编程。

特点:

1:是它不需要创建变量用来存储数据。

2:不包含循环控制的代码如 for, while。

♥︎♥︎♥︎ super() 是否必须执行?不执行怎么让它不报错?

非必须,

在 JavaScript 中,super 指的是父类的构造函数

如果想在构造函数中使用this,你必须首先调用super。 先让父类做完自己的事不执行无法使用this.

不报错的方法:

1:不使用this

2:手动修正this

♥︎♥︎♥︎ eventloop 渲染在哪一步?

任务队列
所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列( Event Queue )的机制来进行协调。

同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入 Event Queue。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。

在事件循环中,每进行一次循环操作称为tick,通过阅读规范可知,每一次 tick 的任务处理模型是比较复杂的,其关键的步骤可以总结如下:
在此次 tick 中选择最先进入队列的任务( oldest task ),如果有则执行(一次)检查是否存在 Microtasks ,如果存在则不停地执行,直至清空Microtask Queue更新 render
主线程重复执行上述步骤

那么,什么是 microtasks ?规范中规定,task分为两大类, 分别是 Macro Task (宏任务)和 Micro Task

(微任务), 并且每个宏任务结束后, 都要清空所有的微任务,这里的 Macro Task也是我们常说的 task 。

类型 例子
(macro)task script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
microtask Promise、MutaionObserver、process.nextTick(Node.js 环境)
整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 script start
遇到 setTimeout,其回调函数被分发到宏任务 Event Queue 中
遇到 Promise,其 then函数被分到到微任务 Event Queue 中,记为 then1,之后又遇到了 then 函数,
将其分到微任务 Event Queue 中,记为 then2
遇到 console.log,输出 script end
至此,Event Queue 中存在三个任务,如下表:
宏任务 微任务
setTimeout then1
then2
执行微任务,首先执行then1,输出 promise1, 然后执行 then2,输出 promise2,这样就清空了
所有微任务
此时,所有的mircotask执行完毕,本轮事件循环结束,UI 开始 render,当 UI render 完毕,开始
下一轮事件循环.
执行 setTimeout 任务,输出 setTimeout, 至此,输出的顺序是:script start, script end,
promise1, promise2, setTimeout

UI渲染

根据HTML Standard,一轮事件循环执行结束之后,下轮事件循环执行之前开始进行 UI render。即:macro-task任务执行完毕,接着执行完所有的micro-task任务后,此时本轮循环结束,开始执行UIrender。UI render完毕之后接着下一轮循环。

♥︎♥︎♥︎ 简述call、apply、bind,call 和 apply哪个性能更好?

1、call()

call() 方法调用一个函数, 其具有一个指定的 this值和分别地提供的参数(参数的列表)。 第一个参数:在
fun 函数运行时指定的 this 值;如果指定了 null 或者 undefined 则内部 this 指向 window,后面的参
数:指定的参数列表
var fn = function(arg1, arg2) { 
};
fn.call(this, arg1, arg2);
var numbers = [5, 458 , 120 , -215 ]; 
var maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //获取数组中的最大值458

2、apply()

apply()方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的
参数。apply() 与 call() 非常相似,不同之处在于提供参数的方式。apply() 使用参数数组而不是一组参数
列表。
var fn = function(arg1, arg2) { 
};
fn.apply(this, [arg1, arg2])
var numbers = [5, 458 , 120 , -215 ]; 
//umber 本身没有 max 方法,但是 Math 有,我们就可以借助 call 或者 apply 使用其方法。
var maxInNumbers = Math.max.apply(Math, numbers), //获取数组中的最大值458

3、bind()

bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相的函数体(在 ECMAScript 5 规范中内置的call属性)。
当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数

/* ——call 和 apply哪个性能更好?—— */

call的性能要比apply好一些,尤其是传递给函数的参数超过3个时所以后期开发的时候,可以使用call多一些

(传参数3个以内的话,call和apply性能差不多,超过3个以上call更好一些)

♥︎♥︎♥︎ Promise 避免回调地狱的语法糖–实现链式调用的核心点是什么?

解决回调地狱的终极方法 async/await ES7的语法,可以通过 async/await让代码看起来像同步的async异步 await等待
await 等待 就是当后面跟的是promise对象,就让他停止 ,先让里面的异步事情做完,在把结果返回给
前面的新变量,在继续向后执行
他只生效当前作用域内部,也就是async函数内部。

实现链式调用的核心点: 在 then 中新创建的 Promise,它的状态变为 fulfilled 的节点是在上一个 Promise的回调执行完毕的时候。也就是说当一个 Promise 的状态被 fulfilled 之后,会执行其回调函数,而回调函数返回的结果会被当作 value,返回给下一个 Promise(也就是then 中产生的 Promise),同时下一个 Promise的状态也会
被改变(执行 resolve 或 reject),然后再去执行其回调,以此类推下去…

♥︎♥︎♥︎ 进程线程区别是什么?

什么是进程?什么是线程?

进程是系统中正在运行的一个程序,程序一旦运行就是进程。

进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空

间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要

使用进程间通信,比如管道,文件,套接字等。

一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。线程与进程的一个主要区别是,统一

进程内的一个主要区别是,同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一

个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写

这些栈内存。

线程是进程的一个实体,是进程的一条执行路径。

线程是进程的一个特定执行路径。当一个线程修改了进程的资源,它的兄弟线程可以立即看到这种变

化。

进程和线程的区别体现在以下几个方面:

1.地址空间和其他资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程

在其他进程内不可见。

2.通信:进程间通信IPC(管道,信号量,共享内存,消息队列),线程间可以直接独写进程数据段(如

全局变量)来进程通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

3.调度和切换:线程上下文切换比进程上下文切换快得多。

4.在多线程OS中,进程不是一个可执行的实体。

♥︎♥︎♥︎ 禁止事件冒泡,禁止默认事件

/-----禁止事件冒泡:-----/
function stopBubble(e) {
//如果提供了事件对象,则这是一个非IE浏览器
if ( e && e.stopPropagation )
 //因此它支持W3C的stopPropagation()方法
 e.stopPropagation();
else
 //否则,我们需要使用IE的方式来取消事件冒泡
 window.event.cancelBubble = true;
}

/-----阻止浏览器的默认行为-----/
function stopDefault( e ) {
 //阻止默认浏览器动作(W3C)
 if ( e && e.preventDefault )
 e.preventDefault();
 //IE中阻止函数器默认动作的方式
 else
 window.event.returnValue = false;
 return false;
}

♥︎♥︎♥︎ 使用箭头函数应该注意什么?

  1. 不要在对象里面定义函数,对象里面的行数应该用传统的函数方法

  2. 不要在对原型对象上定义函数,在对象原型上定义函数也是遵循着一样的规则

  3. 不要用箭头定义构造函数

  4. 不要用箭头定义事件回调函数

♥︎♥︎♥︎ 你知道 ES6 中的 Generator 和 yiled 吗?在实际开发中使用过吗?

Generator 函数是 ES6 提供的一种异步编程解决方案

执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

形式上,Generator函数是一个普通函数,但是有两个特征:

1. function关键字与函数名之间有一个星号
2. 函数体内部使用yield表达式,定义不同的内部状态
//-----利用Generator函数,在对象上实现Iterator接口-----//
function* iterEntries(obj) {
 let keys = Object.keys(obj);
 for (let i=0; i < keys.length; i++) {
 let key = keys[i];
 yield [key, obj[key]];
 }
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
 console.log(key, value);
}

♥︎♥︎♥︎ Cookie、storage 的区别?什么时候使用?

区别:

1. cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。localStorage不会自动把数据发给服务器,仅在本地保存。
2. cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。
3. 存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
4. 数据有效期不同,
localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;
cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
5. WebStorage 支持事件通知机制,可以将数据更新的通知发送给监听者。
6. WebStorage 的 api 接口使用更方便。

使用场景:

localStorage可以用来统计页面访问次数。

cookie一般存储用户名密码相关信息,一般使用escape转义编码后存储。

♥︎♥︎♥︎ map、fillter、reduce 各自有什么作用?

1:map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后放入到新的数组中。
另外 map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组
2:filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组,我们可以
利用这个函数删除一些不需要的元素,和 map 一样,filter 的回调函数也接受三个参数,用处也相同。
3:reduce 可以将数组中的元素通过回调函数最终转换为一个值。
它接受两个参数,分别是回调函数和初始值,接下来我们来分解上述代码中 reduce 的过程
首先初始值为 0,该值会在执行第一次回调函数时作为第一个参数传入
回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组,后三者想必大家都可以明白作
用,这里着重分析第一个参数

♥︎♥︎♥︎ promise 常见方法和 all 和 race的应用场景

Promise.race():

race的用法:谁跑的快,以谁为准执行回调。

race的使用场景:比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作

Promise.all():

all的用法:谁跑的慢,以谁为准执行回调。

在前端的开发实践中,我们有时会遇到需要发送多个请求并根据请求顺序返回数据的需求

♥︎♥︎♥︎ 介绍一下 ES6 中 Set, Map的区别?

Map
在JS中的默认对象的表示方式为{},即一组键值对,但是键必须是字符串。
为了使用Number或者其他数据类型作为键,ES6规范引入了新的数据类型Map。
Map是一组键值对的结构,具有极快的查找速度。初始化Map需要一个二维数组,或者直接初始化一个
空Map。
Map 对象是键值对集合,和 JSON 对象类似,但是 key 不仅可以是字符串还可以是其他各种类型的值包
括对象都可以成为Map的键


Set
Set也是一组key的集合,与Map类似。但是区别是Set不存储value,并且它的key不能重复。
创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
重复元素会在Set中自动被过滤
Set 对象类似于数组,且成员的值都是唯一的

♥︎♥︎♥︎ 为什么操作 dom 慢?

DOM对象本身也是一个js对象,所以严格来说,并不是操作这个对象慢,而是说操作了这个对象后,需

要经过跨流程通信和渲染线程触发的重新渲染,导致DOM操作慢

JS引擎和和渲染引擎的模块化设计,使得它们可以独立优化,运行速度更快,但是这种设计带来的后果

就是DOM操作会越来越慢

♥︎♥︎♥︎ js中的常用事件绑定方法

  1. 在DOM元素中直接绑定

  2. 在JavaScript代码中绑定

  3. 绑定事件监听函数

♥︎♥︎♥︎ ts 和 js 的区别

1.ts是静态类语言,可以做到声明即文档,js是动态类语言相对更灵活。
2.如用ts写一个button组件可以清晰的知道,ButtonProps如是否必传,可选,style是什么类型,
disabled是什么类型,较js,ts更易于维护和拓展,可以做到代码即注释,避免一个月不见3,代码自己
都忘记自己写了什么的尴尬,
4.ts对比js基础类型上,增加了 void/never/any/元组/枚举/以及一些高级类型
5.js没有重载概念,ts有可以重载
6.vscode/ide对ts有很友好的提示
7.ts更利于重构

♥︎♥︎♥︎ 简述原生 js 发 ajax 的步骤

1.创建XMLHTTPRequest对象

2.使用open方法设置和服务器的交互信息

3.设置发送的数据,开始和服务器端交互

4.注册事件

5.更新界面

♥︎♥︎♥︎ instanceof的原理是什么?

// instanceof 可以正确的判断对象的类型,是通过判断对象的原型链中是不是能找到类型的prototype。
function fn(left, right) {
 let prototype = right.prototype;
 left = left.proto;
 while (true) {
 if (left === undefined || left === null) {
 return false;
 }
 if (left === prototype) {
 return true;
 }
 left = left.proto;
 }
}

♥︎♥︎♥︎♥︎ 介绍一下 typeof 区分类型的原理

typeof原理: 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信
息。
000: 对象
010: 浮点数
100:字符串
110: 布尔
1: 整数
/----------------------------------------------/
typeof null 为"object", 原因是因为 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)
三位都为0的话会被判断为Object类型,null的二进制表示全为0,自然前三位也是0,所以执行typeof时
会返回"object"###

♥︎♥︎♥︎♥︎ 说说你对 JavaScript 的作用域的理解。什么是作用域链?

在 JavaScript 中有两种作用域类型:

  1. 局部作用域:只能在函数内部访问它们

  2. 全局作用域:网页的所有脚本和函数都能够访问它

JavaScript 拥有函数作用域:每个函数创建一个新的作用域。

作用域决定了这些变量的可访问性(可见性)。

函数内部定义的变量从函数外部是不可访问的(不可见的)。

作用域链:

当查找变量的时候,会先从当前上下文的变量对象中查找,

如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变

量对象,也就是全局对象。

这样由多个执行上下文的变量对象构成的链表就叫做作用域链

♥︎♥︎♥︎♥︎ 说说你对执行上下文的理解

执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文(eval一般不会使用) 
1. 全局执行上下文:
全局执行上下文只有一个,也就是我们熟知的window对象,我们能在全局作用域中通过this直接访问到它
2. 函数执行上下文
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;
需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
3. 执行上下文栈(下文简称执行栈)也叫调用栈,
执行栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是
先进后出)的特性。

JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,都会创
建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,所以可以理解为,JS代码执行完毕前
在执行栈底部永远有个全局执行上下文。###

♥︎♥︎♥︎♥︎ 你对事件循环有了解吗?说说看!

Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:

执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行

检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列

更新render(每一次事件循环,浏览器都可能会去更新渲染)

重复以上步骤

宏任务 > 所有微任务 > 宏任务

  1. 将所有任务看成两个队列:执行队列与事件队列。

  2. 执行队列是同步的,事件队列是异步的,宏任务放入事件列表,微任务放入执行队列之后,事件队

列之前。

  1. 当执行完同步代码之后,就会执行位于执行列表之后的微任务,然后再执行事件列表中的宏任务

♥︎♥︎♥︎♥︎ 微任务和宏任务有什么区别?

宏任务(macrotask) 微任务(microtask)
谁发起的宿主 (Node、浏览器) JS引擎
具体事件 1. script (可以理解为外层同步代码) 2. setTimeout/setInterval 3. UI rendering/UI事件 4.postMessage,MessageChannel 5. setImmediate,I/O(Node.js) 1. Promise 2.MutaionObserver 3. Object.observe(已废弃;Proxy 对象替代) 4. process.nextTick(Node.js)
谁先运行 后运行 先运行
会触发新一轮Tick吗 不会

♥︎♥︎♥︎♥︎ 如何实现函数的柯里化?比如 add(1)(2)(3)

/-----解决方法1:-----/
function add () {
 var args = Array.prototype.slice.call(arguments);
 var fn = function () {
 var sub_arg = Array.prototype.slice.call(arguments);
   // 把全部的参数聚集到参数的入口为一个参数: args.concat(sub_arg)
 return add.apply(null, args.concat(sub_arg));
 }
 fn.valueOf = function () {
 return args.reduce(function(a, b) {
 return a + b;
 })
 }
 return fn;
}
console.log(add(1,2)) // 3
console.log(add(1)(2)) // 3
console.log(add(1)(2)(3)) // 6
console.log(add(1,2,3)(4)) // 10
/-----解决方法2:-----/
function add () {
 var args = Array.prototype.slice.call(arguments);
  var fn = function () { 
    // 把参数都放在一个相当于全局变量的 args 里面 args.push(...arguments) 
    return fn; }
  fn.valueOf = function () { 
    return args.reduce(function(a, b) { 
      return a + b; }) 
  }return fn;
}
console.log(add(1,2)) // 3
console.log(add(1)(2)) // 3
console.log(add(1)(2)(3)) // 6
console.log(add(1,2,3)(4)) // 10

♥︎♥︎♥︎♥︎ 什么是反柯里化

在JavaScript中,当我们调用对象的某个方法时,其实不用去关心该对象原本是否被设计为拥有这个方法,这是动态类型语言的特点。可以通过反柯里化(uncurrying)函数实现,让一个对象去借用一个原本不属于他的方法。

♥︎♥︎♥︎♥︎ 了解 ES6 的 Proxy 吗?

Proxy,代理,是ES6新增的功能,可以理解为代理器(即由它代理某些操作)。
Proxy 对象用于定义或修改某些操作的自定义行为,可以在外界对目标对象进行访问前,对外界的访问
进行改写。
new Proxy()表示生成一个 Proxy 实例
 -target:目标对象
 -handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数。
注意:要实现拦截操作,必须是对 Proxy 实例进行操作,而不是针对目标对象 target 进行操作

♥︎♥︎♥︎♥︎ 深拷贝是什么?项目哪里是用到了深拷贝?

1,在拷贝构造函数中假如只完成了数据成员本身的赋值则称为“浅拷贝”;编译器提供的默认拷贝构造函

数就已经可以完成这个任务。

而假如要复制的数据除了属性值本身以外,还要复制附加在数据属性值上的额外内容,那就要自己来写

拷贝构造函数了,来完成所谓的“深拷贝”。

举个例子:

若在构造函数中new了一个新的空间存放数据,并且用指针记录了首地址;若是浅拷贝,则在拷贝构造

函数中指针值将复制给另一个数据成员,这样就会有两个指针指向同一个空间;这样的话在析构函数里

将会对指针所指向的空间进行释放,由于两个指针指向的是同一个空间,在释放第一个指针指向的空间

时不会出现什么问题,而释放第二个指针指向的空间时就会因为空间已经被解析过而导致解析的空间不

存在的情况,就会造成程序无法终止。

而解决上面这种情况的办法就是使用“深拷贝”,深拷贝是在拷贝构造函数里再new一个新的空间。将数

据复制在新空间里,并将拷贝的指针记录这个新空间的首地址,这样在析构函数里就不会有问题了。

2,在某些引用类型值不更新的情况下用深拷贝

♥︎♥︎♥︎♥︎ ES6 中,数组监测怎么实现的(代理)

// 通过ES6的关键字extends实现继承完成Array原型方法的重写
class NewArray extends Array {
 constructor(...args) {
 // 调用父类Array的constructor()
 super(...args)
 }
 push (...args) {
 console.log('监听到数组的变化啦!');
   // 调用父类原型push方法 
   return super.push(...args)
 }
 // ...
}
let list3 = [1, 2];
let arr = new NewArray(...list3);
console.log(arr)
// (2) [1, 2]
arr.push(3);
// 监听到数组的变化啦!
console.log(arr)
// (3) [1, 2, 3]

♥︎♥︎♥︎♥︎ 模板引擎原理

//模板引擎是通过字符串拼接得到的
let template = 'hello <% name %>!'
let template = 'hello ' + name + '!'
字符串是通过new Function执行的
let name = 'world'
let template = let str = 'hello ' + name + '!' return str
let fn = new Function('name', template)
console.log(fn(name)) // hello world!

//将模板转换为字符串并通过函数执行返回
let template = 'hello <% name %>!'
let name = 'world'
function compile (template) {
 let html = template.replace(/<%([\s\S]+?)%>/g, (match, code) => {
 return ' + ${code} + '
 })
 html = let str = '${html}'; return str
 return new Function('name', html)
}
let str = compile(template)
console.log(str(name)) // hello world!

//函数只能接收一个name变量作为参数,功能太单一了,一般会通过对象来传参,with来减少变量访
问。
with功能
let params = {
 name: '张三',
 age: 18
}
let str = ''
with (params) {
 str = 用户${name}的年龄是${age}岁 }
console.log(str) // 用户张三的年龄是18岁

//实现简单的模板引擎
let template = 'hello <% name %>!'
let name = 'world'
function compile (template) {
 let html = template.replace(/<%([\s\S]+?)%>/g, (match, code) => {
 return ' + ${code.trim()} + '
 })
 html = '${html}'
 html = let str = ''; with (params) { str = ${html}; } return str
 return new Function('params', html)
}
let str = compile(template)
console.log(str({ name })) // hello world!

♥︎♥︎♥︎♥︎ ES6 的新特性

  1. const与let

  2. 模板字符串

  3. 解构赋值

  4. 对象简写法

  5. for…of循环

  6. 展开运算符

  7. 剩余参数(可变参数)

  8. ES6箭头函数

  9. 参数默认值

  10. 类和继承

  11. 模块化规范

♥︎♥︎♥︎♥︎ 图片懒加载怎么实现?

原理:随着滚轮滚动,底部的图片会被不断地加载,从而显示在页面上,按需加载,当页面需要显示图片的时候才进行加载,否则不加载

  1. 页面加载完成时记录每个img标签的src值的字符串,

  2. 用鼠标滚轮判断图片是否出现在屏幕,如果是,则把记录的src值赋值给src属性

  3. 然后让image的src来发起请求,获取对应的图片放置到DOM树的这个位置上,从而实现图片的页面渲染!

于是就可以知道,当进入页面的时候,其实我们已经把所有的图片的这个地址信息拿到了,图片懒加载的作用就是让这个图片的src按需发起请求,获取图片。

♥︎♥︎♥︎♥︎ 异步的解决方案有哪些?

1.回调函数callback

2.事件发布订阅

3.Promise

4.Generator

5.async/await

♥︎♥︎♥︎♥︎ 常见内存泄漏

1、静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。

2、各种连接,如数据库连接、网络连接和IO连接等。在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。

3、变量不合理的作用域。一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。

4、内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。

5、改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露

6、缓存泄漏

内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘,对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值

7、监听器和回调

内存泄漏第三个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚。需要确保回调立即被当作垃圾回收的最佳方法是只保存他的若引用,例如将他们保存成为WeakHashMap中的键。

♥︎♥︎♥︎♥︎ 插入几万个 dom ,如何实现页面不卡顿?

让创建插入节点的工作分批进行:
setTimeout(() => {
 // 插入十万条数据
 const total = 100000;
 // 一次插入 20 条,如果觉得性能不好就减少
 const once = 20;
 // 渲染数据总共需要几次
 const loopCount = total / once;
 let countOfRender = 0
 let ul = document.querySelector("ul");
 function add() {
 // 优化性能,插入不会造成回流
 const fragment = document.createDocumentFragment();
 for (let i = 0; i < once; i++) {
 const li = document.createElement("li");
 li.innerText = Math.floor(Math.random() * total);
 fragment.appendChild(li);
 }
 ul.appendChild(fragment);
 countOfRender += 1;
 loop();
 }
 function loop() {
 if (countOfRender < loopCount) {
 window.requestAnimationFrame(add);
 }
 }
 loop();
}, 0);

♥︎♥︎♥︎♥︎ 你知道什么是原型吗?我们为什么要用原型呢?或者说原型为我们提供了什么?

什么是原型:

Javascript规定,每一个函数都有一个prototype对象属性,指向另一个对象(原型链上面的)。

prototype(对象属性)的所有属性和方法,都会被构造函数的实例继承。这意味着,我们可以把那些不变(公用)的属性和方法,直接定义在prototype对象属性上。

prototype就是调用构造函数所创建的那个实例对象的原型(proto)。

prototype可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。

为什么要用原型:使用原型对象解决浪费内存


♥︎♥︎♥︎♥︎♥︎ 浏览器和 Node 事件循环的区别?

Node中的事件循环:

Node 中的 Event Loop 和浏览器中的是完全不相同的东西。Node.js 采用 V8 作为 js 的解析引擎,而

I/O 处理方面使用了自己设计的 libuv,libuv 是一个基于事件驱动的跨平台抽象层,封装了不同操作系

统一些底层特性,对外提供统一的 API,事件循环机制也是它里面的实现(下文会详细介绍)。

Node.js 的运行机制如下:

V8 引擎解析 JavaScript 脚本。

解析后的代码,调用 Node API。

libuv 库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个 Event Loop(事件循

环),以异步的方式将任务的执行结果返回给 V8 引擎。

V8 引擎再将结果返回给用户。

♥︎♥︎♥︎♥︎♥︎ 函数节流、防抖。scroll resize 使用函数节流实现不要频繁触发事件的需求。

防抖:
//scroll方法中的do somthing至少间隔500毫秒执行一次
 window.addEventListener('scroll',function(){
 var timer;//使用闭包,缓存变量
 return function(){
 if(timer) clearTimeout(timer);
 timer = setTimeout(function(){
 console.log('do somthing')
 },500)
 }
 }());//此处()作用 - 立即调用return后面函数,形成闭包
节流:
//scroll方法中当间隔时间大于2s,do somthing执行一次
 window.addEventListener('scroll',function(){
 var timer ;//使用闭包,缓存变量
 var startTime = new Date();
 return function(){
 var curTime = new Date();
 if(curTime - startTime >= 2000){
 timer = setTimeout(function(){
 console.log('do somthing')
 },500);
 startTime = curTime;
 }
   } }());//此处()作用 - 立即调用return后面函数,形成闭包

♥︎♥︎♥︎♥︎♥︎ JS中的常见设计模式以及应用场景?

1、单例模式

单例模式就是一个实例在整个网页的生命周期里只创建一次,后续再调用实例创建函数的时候,返回的

仍是之前创建的实例。在实际开发中应用十分广泛,例如页面中的登录框,显示消息的提示窗

2、策略模式

策略模式是指将策略(算法)封装起来,策略的目的是将算法和使用分离开。

3、代理模式

代理模式很好理解,我们不能直接使用目标函数,而是通过调用代理函数来实现对目标函数的使用。

4、发布订阅模式

发布订阅模式在实际应用中非常常见,例如,我们在微信App上关注了某个公众号,当该公众号有新文

章发布时,就会通知我们。

发布订阅模式定义了一种一对多的依赖关系,当“一”发生变化,通知多个依赖。

5、命令模式

所谓命令模式就是将下要执行的业务逻辑封装到一个函数或类中,不需要具体谁来执行该命令的

♥︎♥︎♥︎♥︎♥︎ 用多种方法实现JavaScript继承

1.借用构造函数(经典继承)

function Cat(name,age){
    Animal.call(this,name)
    this.age=age
}
let cat=new Cat("妙妙",1)
cat.sayname()
console.log(cat instanceof Animal)  //false
console.log(cat instanceof Cat)    //true

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类。
优点:
1.构造函数,优点是可以多重继承。
2.创建子类实例时,可以向父类传递参数。
缺点:
1.实例并不是父类的实例,只是子类的实例。
2.只能继承构造函数内的,原型上的东西不能继承。
3.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。

2.原型链继承

function Dog(name){
    this.name=name
}
Dog.prototype=new Animal()
let dog =new Dog('旺财')
dog.sayname();dog.eat('bone')
console.log(dog instanceof Animal)  //true
console.log(dog instanceof Dog)    //true

核心:将父类的实例作为子类的原型。
优点:
1.非常纯粹的继承关系,实例是子类的实例,也是父类的实例。
2.简单,父类上新增的方法属性子类都能访问的到。
3.简单,易于实现。
缺点:
1.要想为子类新增属性和方法,必须在 new Animal这样语句后,不能放到构造器内。
2.无法多重继承。
3.创建子类实例的时候,无法向父类构造函数传参。

3.实例继承

function Bird(name){
    let instance= new Animal()
    instance.name= name || 'bird'
    return instance
}
let bird =new Bird("飞飞")
bird.sayname();bird.eat("虫子")
console.log(bird instanceof Animal)  //true
console.log(bird instanceof Bird)    //false

核心:为父类实例添加新特性,作为子类实例返回。
优点:不限制调用方式,不管是new子类()还是子类(),返回的对象具有相同的效果。
缺点:就是实例是父类的实例,不是子类的实例,不支持多继承。

4.拷贝继承

function Cow(name){
    let animal=new Animal()
    for (let key in animal){
        console.log("key",key)
        Cow.prototype[key]=animal[key]
    }
    Cow.prototype.name=name || 'cow'
}
let cow =new Cow("哞~")
cow.sayname();cow.eat("草")
console.log(cow instanceof Animal)  //false
console.log(cow instanceof Cow)    //true 

优点:支持多继承
缺点:
1.效率较低,内存占用高(因为要拷贝父亲的属性)
2.无法获取父亲不可枚举的方法(不可枚举方法,不能用for in 访问到)

5.组合继承(原型链和构造函数)

function Sheep(name){
    Animal.call(this)
    this.name=name || "羊"
}
Sheep.prototype=new Animal()
let sheep=new Sheep("喜洋洋")
sheep.sayname();sheep.eat("草")
console.log(sheep instanceof Animal)  //true
console.log(sheep instanceof Sheep)    //true 

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例做为子类原型,实现函数复用。
优点:
1.弥补了构造继承的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法。
2.即是子类的实例,也是父类的实例。
3.不存在引用属性共享问题。
4.可传参。
5.函数可复用。
缺点:调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

6.寄生式组合继承

function Horse(name){
    Animal.call(this)
    this.name = name || '马'
}
(function(){
    var Super = function(){} //创建一个没有实例的方法类
    Super.prototype =Animal.prototype
    Horse.prototype=new Super() //将实例作为子类的原型
})()
let horse = new Horse('白龙马')
horse.sayname()
console.log(horse instanceof Animal)  //true
console.log(horse instanceof Horse)    //true 

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点。
优点:堪称完美
缺点:实现较为复杂

♥︎♥︎♥︎♥︎♥︎ SSR和CSR优势劣势

  • SSR更有利于首屏渲染,CSR更有利于页面交互

  • SSR:

    优势:

    • 搜索引擎可以抓取网站以获取更好的SEO
    • 初始页面加载速度更快
    • 非常适合静态网站

    劣势:

    • 频繁的服务器请求
    • 整体缓慢的页面渲染
    • 整页重新加载
    • 不擅长于网站交互
  • CSR:

    优势:

    • 擅于网站交互
    • 初始加载网站渲染速度快
    • 非常适合Web应用程序
    • 强大的JS选择

    劣势:

    • 不利于SEO
    • 初始加载可能需要更多时间

♥︎♥︎♥︎♥︎♥︎ for…in 和 for of

  • for…in 循环:只能获得对象的key,不能获得value,主要是为了遍历对象而生,不适用于遍历数组。for…in 循环不仅遍历数字键名,还会遍历手动添加的其它键,包括原型链上的键。for…of 则不会这样。

​ for…of 循环:允许遍历获得键值,可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象

  • 对于普通对象,没有部署原生的 iterator 接口,直接使用 for…of 会报错

​ 可以使用 for…in 循环遍历键名

  • forEach 循环无法中途跳出,break 命令或 return 命令都不能奏效

​ for…of 循环可以与break、continue 和 return 配合使用,跳出循环

  • 无论是 for…in 还是 for…of 都不能遍历出 Symbol 类型的值,遍历 Symbol 类型的值需要用 Object.getOwnPropertySymbols() 方法

♥︎♥︎♥︎♥︎♥︎ 获取对象的所有key值

  • object.keys :

​ 排序方式:index 、字符串的key创建事件、symbol

  • for … in :它能够额外遍历继承来的可枚举的属性

  • object.getOwnPropertyName => 获取key值

  • Object.getOwnPropertySymbols => 获取 symbol key,获取在对象上定义的符号数组

  • reflect.ownkeys:返回一个由目标对象自身的属性键组成的数组。

♥︎♥︎♥︎♥︎♥︎ null和undefined的区别

(1)Number转换的值不同,Number(null)输出为0, Number(undefined)输出为NaN

(2)null表示一个值被定义了,但是这个值是空值

  • 作为函数的参数,表示函数的参数不是对象

  • 作为对象原型链的终点 (Object.getPrototypeOf(Object.prototype))

  • 定义一个值为null是合理的,但定义为undefined不合理(var name = null)

  

(3)undefined表示缺少值,即此处应该有值,但是还没有定义

  • 变量被声明了还没有赋值,就为undefined

  • 调用函数时应该提供的参数还没有提供,该参数就等于undefined

  • 对象没有赋值的属性,该属性的值就等于undefined

  • 函数没有返回值,默认返回undefined

♥︎♥︎♥︎♥︎♥︎ 判断对象为空

Object.keys(obj).length = = = 0  // true 则为空对象(有缺陷)
Object.keys(obj).length = = = 0 && obj.constructor = = = Object

♥︎♥︎♥︎♥︎♥︎ 对象的可冻结性

本质都是Object.defineProperty去配置对象(去看看第三个参数那几个配置的参数)

初级冰冻:对key值进行只读,不可更改

中级冰冻:对key值进行不可读,不可枚举

高级冰冻:对key值不可配置

♥︎♥︎♥︎♥︎♥︎ 对象冻结

Object.Seal

当你听到 “seal” 这个词时,你会想到什么? “seal” 的第一个意思是印章或者蜜蜡之类封信件的东西。在 JavaScript 中 Object.seal 的作用与 “密封” 相同。

Object.seal 使传入对象的所有属性都不可配置。来举个例子。

const obj = { a: 100 };
Object.getOwnPropertyDescriptors(obj);

/* {
 *   a: {
 *        configurable: true,
 *        enumerable: true,
 *        value: 100,
 *        writable: true
 *      }
 * }
 *
 */

Object.seal(obj);
Object.getOwnPropertyDescriptors(obj);

/* {
 *   a: {
 *        configurable: false,
 *        enumerable: true,
 *        value: 100,
 *        writable: true
 *      }
 * }
 *
 */

obj.a = 200;
console.log(obj.a);
// 输出:200

delete obj.a;
console.log(obj.a);
// 输出:200

obj.b = 500;
console.log(obj.b);
// 输出:undefined

对象 obj 只有一个值为 100 的属性。obj 的基本属性就像上面一样,configurable, enumerablewritable 都是 true。然后我用 Object.seal 把它封起来,想要看看哪些基本属性被修改了,哪些没有。结果是,只有 configurable 被改成了 false

obj.a = 200;

即使被密封对象的基本属性 configurable 现在为 false,但属性值也被更改为 200。如前所述,将 configurable设置为 false 将使属性不可写,但如果 writable 已经写明为 true 时,configurable设置为 false 将不会生效。当你创建一个对象并添加一个新属性时,默认情况下它是 writable: true

delete obj.a;

封装对象使每个属性变为不可配置,从而使属性不可删除。

obj.b = 500;

Object.seal 后删除属性失败了是为什么呢?因为当调用 Object.sealObject.freeze 时,传递给这些方法的对象变成不可扩展的对象,这意味着不能删除其中的任何属性或向其中添加任何属性。

Object.freeze

Object.freeze 对传递的对象的限制比 Object.seal 更多。让我们再举一个例子。

const obj = { a: 100 };
Object.getOwnPropertyDescriptors(obj);

/* {
 *   a: {
 *        configurable: true,
 *        enumerable: true,
 *        value: 100,
 *        writable: true
 *      }
 * }
 *
 */

Object.freeze(obj);
Object.getOwnPropertyDescriptors(obj);

/* {
 *   a: {
 *        configurable: false,
 *        enumerable: true,
 *        value: 100,
 *        writable: false
 *      }
 * }
 *
 */

obj.a = 200;
console.log(obj.a);
// 输出:100

delete obj.a;
console.log(obj.a);
// 输出:100

obj.b = 500;
console.log(obj.b);
// 输出:undefined

所以, Object.freezeObject.seal 的区别是在使用后,Object.freezewritable 会被设置为 falseObject.seal 仍然为 true

obj.a = 200;

因此,修改现有属性总是失败。

delete obj.a;

就像 Object.seal 一样, Object.freeze 也使得传递的对象不可配置,这使得其中的每个属性都不可删除。

obj.b = 500;

冻结对象也会使对象不可扩展。

共同点

  1. 作用的对象变得不可扩展,这意味着不能再添加新属性。
  2. 作用的对象中的每个元素都变得不可配置,这意味着不能删除属性。
  3. 如果在 ‘use strict’ 模式下使用,这两个方法都可能抛出错误,例如在严格模式下修改 obj.a = 500

不同点

Object.seal 能让你修改属性的值,但 Object.freeze 不能.

缺点

Object.freezeObject.seal 在 “实用性” 方面都有 “缺陷”,他们只冻结/封印对象的第一深度。

这里有一个简单的比较。

const obj = {
  foo: {
    bar: 10
  }
};

现在 obj 内部嵌套了一个对象 foo。 它内部有一个 bar 属性.

Object.getOwnPropertyDescriptors(obj);
/* {
 *   foo: {
 *        configurable: true,
 *        enumerable: true,
 *        value: {bar: 10},
 *        writable: true
 *      }
 * }
 */

Object.getOwnPropertyDescriptors(obj.foo);
/* {
 *   bar: {
 *        configurable: true,
 *        enumerable: true,
 *        value: 10,
 *        writable: true
 *      }
 * }
 */

使用 Object.freezeObject.seal 之后,

Object.seal(obj);
Object.freeze(obj);

// 这两种方法都会导致相同结果

让我们看看基础属性是如何变化的。

Object.getOwnPropertyDescriptors(obj);
/* {
 *   foo: {
 *        configurable: false,
 *        enumerable: true,
 *        value: {bar: 10},
 *        writable: false
 *      }
 * }
 */

Object.getOwnPropertyDescriptors(obj.foo);
/* {
 *   bar: {
 *        configurable: true,
 *        enumerable: true,
 *        value: 10,
 *        writable: true
 *      }
 * }
 */

foo 的基础属性已经改变但嵌套的 obj.foo 基础属性并没有变。这意味着嵌套的第二层仍然是可修改的。

obj.foo = { bar: 50 };
// 没有生效

Since obj.foo doesn’t let you change its value because it’s frozen, 因为 obj 被冻结了,所以 obj.foo 不允许改变它的值,

obj.foo.bar = 50;
// 生效了

obj.foo.bar 仍然允许你改变它的值,因为它没有被冻结。

那么如何将对象冻结/密封到最深层的嵌套对象呢?在 MDN 上可以查看到解决方案

function deepFreeze(object) {

  // 检索在对象上定义的属性名
  var propNames = Object.getOwnPropertyNames(object);

  // 在冻结自己之前先冻结属性

  for (let name of propNames) {
    let value = object[name];

    if(value && typeof value === "object") { 
      deepFreeze(value);
    }
  }

  return Object.freeze(object);
}

测试结果如下。

const obj = { foo: { bar: 10 } };
deepFreeze(obj);

obj.foo = { bar: 50 };
// 不会生效

obj.foo.bar = 50;
// 不会生效

总结

Object.freezeObject.seal 肯定是有用的方法。但是你应该考虑使用 deepFreeze 来冻结嵌套对象。

前端面试题大全之HTML5+CSS3

前端面试题大全(HTML5+CSS3)

前端面试题类目分类

  • HTML5 + CSS3
  • JavaScript
  • Vue + Vue3
  • React
  • Webpack
  • 服务端

考点频率 :♥︎ 、 ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎ ♥︎

HTML5 + CSS3

♥︎ 父元素和子元素宽高不固定,如何实现水平垂直居中

方法一:flex布局

-父元素设置:display:flex; justify-content:center; align-items:center;

-另一种:父元素设置弹性盒display:flex; 子元素可以设置margin:auto;实现水平居中

方法二:定位属性(position)配合位移属性(transform)

-子元素设置:position:absolute; top:50%;left:50%;transform:translate(-50%,-50%)

//方法一:
<div class="parent">
    <div class="child"></div>
</div>
body,html{
    width: 100%;
    height: 100%;
}
.parent{
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100%;
    background-color: antiquewhite;
}
.parent .child{
    width: 200px;
    height: 300px;
    background-color: rgb(78,151,211);
}
//方法二
<div class="parent">
    <div class="child">
        <p></p>
    </div>
</div>
//css部分
.parent{
         width: 100%;
         height: 100%;
         background-color: antiquewhite;
        }
.child{
         position: absolute;
         top:50%;
         left:50%;
         transform: translate(-50%,-50%);
        }
.child p{
         width: 300px;
         height: 400px;
         background-color: rgb(78, 151, 211);
        }/

♥︎简述title与h1的区别,b与strong的区别,i与em的区别

《title与h1》

1.从网站的角度来说,title更侧重于网站消息。title可以直接告诉搜索引擎和用户这个网站是关于什么主题和内容的。

2.从文章的角度看,h1则是侧重于概括文章主题。

3.一个网站可以有多个title,但最好一个单页用一个title,以便突出网站页面主体信息,从seo看,title权重比h1高,适用性比h1广。

4.标记了h1的文字页面给予的权重会比页面内其他权重高很多,一个好的网站是h1和title并存,既突出h1文章主题,又突出网站主题和关键字。达到双重优化网站的效果。

《b和strong》

1.b只是对文本的简单加粗,strong是一个语义化标签,对相关文本具有强调作用。

2.b标签只是侧重于字体加粗,strong标签加强字体的语气都是通过粗体来实现的,相比之下,搜索引擎更喜欢侧重于strong标签。

3.strong标签更注重于内容上的应用,在html中,对关键词的标明。还有一些网站上,也有使用strong标签对小标题进行强调,但是在页面中,如果出现过多的strong标签,可能会对于排名不利。

《i与em》

1.i是实体标签,用来使字符倾斜,em是逻辑标签,作用是强调文本内容。

2.i标签只是斜体的样式,没有实际含义,常用来表达无强调或者重意味的斜体,比如生物学名、术语、外来语。

3.em表示标签内字符重要,用以强调,其默认格式是斜体,但是可以通过css添加样式。

‼️建议:为了符合css的规范,i标签应尽量少用,应改用em。

♥︎什么是标准文档流

标准文档流指的是元素排版布局过程中,元素会默认自动从左往右,从上往下的流式排列方式。当前面内容发生了变化,后面的内容位置也会随着发生变化。

HTML就是一种标准文档流文件。

♥︎z-index是什么?在position的值什么时候可以触发

  • z-index堆叠上下文只有在postion:relative/absolute/fixed脱离文档流控制时才生效,static时无效。
  • 当父元素和子元素都处于堆叠上下文时,子元素继承父元素的优先级,故父元素大的就大,如果父元素没有处于堆叠上下文时,即z-index:auto;或者position:static;时,子元素不会继承父元素的优先级。
  • z-index为0时依然处于堆叠上下文中,比负值高,比正值低。
  • z-index为负值时不仅会处于z-index为0和正值元素的后面,还会处于非堆叠元素的后面。

♥︎简述一下src和href的区别,title和alt的区别

href:href表示超文本引用,用来建立当前元素和文档的链接,常用在link和a等元素上。

‼️注:当浏览器解析到这一句时会识别该文档为css文件,会下载并不会停止对当前文档的处理,所以建议使用link的方式而不是使用@import加载css。

src:src表示引用资源,替换当前元素,是页面内容不可缺少的一部分,常用在img,script,iframe上。src指向外部资源的位置,指向的内部会嵌入到文档中当前标签所在的位置;请求src资源时会将其指向的资源下载并应用到当前文档中,例如js脚本、img图片等。

‼️注:src链接内的地址不会有跨域问题。当浏览器解析到这一句时会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕。这也是js脚本放在底部而不是头部的问题。

title:

1.title属性是为元素提供额外的注释信息,当鼠标放在元素上时会有title文字显示,以达到补充说明或提示。

2.title属性更倾向于用户体验的考虑。

3.title既可以是元素的属性也可以是标签。

alt:

1.alt属性是在你图片无法显示时的替代文本,它会直接输出在原本加载图片的地方。

2.alt属性是有利于SEO,是搜索引擎搜录时判断图片与文字是否相关的重要依据。

3.alt只能是元素的属性,只能用在img、area和input标签中(img,area中的alt必须指定)。

‼️注:当a标签内嵌套img标签时,起作用的是img的title属性。

♥︎CSS清除浮动的方法

浮动问题,例如:

浮动代码实现:

<div class="fater">
    <div class="box1">one</div>
    <div class="box2">two</div>
</div>
<div class="box3">three</div>
.fater{
    width: 300px;
    border:1px solid pink;
}
.box1{
    width: 100px;
    height: 100px;
    background-color: blue;
    float: left;
}
.box2{
    width: 140px;
    height: 120px;
    background-color: blueviolet;
    float: left;
}
.box3{
    width: 200px;
    height: 150px;
    background-color: brown;
}

如上图所示,因为盒子1和2的浮动,脱离了文档流,那么在他们下面的盒子就会顶上来,又因为父盒子没有设置高度,此时父盒子的高度为零,浮动的元素不能撑开父盒子的高度是因为子元素脱离文档流,父元素没有脱离文档流,所以现在的父盒子相当于没有元素,所以高度为零。

清除浮动的方法:

1.在标签尾部添加空的块级标签,设置样式属性为:clear:both;

缺点:如果页面浮动布局过多,就要添加很多空的块级元素,不利于页面的优化。

//html
<div class="fater">
    <div class="box1">one</div>
    <div class="box2">two</div>
    <div class="box4">空元素</div>
</div>
<div class="box3">three</div>
//css
.box4{
    clear:both;
    background-color: aqua;
}

2.给父盒子添加overflow:hidden;(触发BFC)

缺点:不能和position配合使用;内容增多的时候容易造成不会自动换行导致内容被隐藏,无法显示要溢出的元素。

//html
<div class="fater">
    <div class="box1">one</div>
    <div class="box2">two</div>
</div>
<div class="box3">three</div>
//css
.fater{
    width: 300px;
    border:1px solid pink;
    overflow: hidden;
}

3.使用伪元素清除浮动(推荐)

缺点:ie6-7不支持伪元素 :after,使用zoom:1 触发hasLayout。

//html
<div class="fater clear">
    <div class="box1">one</div>
    <div class="box2">two</div>
</div>
<div class="box3">three</div>
//css
.clear::after{
    content: "";
    display: block;
    clear: both;
}
.clearfix{
        *zoom:1 // *ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行
}

4.直接给父元素单独设置高度(height)(不推荐)

缺点:只适合高度固定的布局,不利于响应式布局。

♥︎hasLayout是什么?(了解即可)

hasLayout是IE特有的一个属性。很多的ie下的css bug都与其息息相关。在ie中,一个元素要么自己对自身的内容进行计算大小和组织,要么依赖于父元素来计算尺寸和组织内容。当一个元素的hasLayout属性值为true时,它负责对自己和可能的子孙元素进行尺寸计算和定位。虽然这意味着这个元素需要花更多的代价来维护自身和里面的内容,而不是依赖于祖先元素来完成这些工作。
下列元素默认 hasLayout=true

<table> <td> <body> <img> <hr> <input> <select> <textarea> <button> <iframe> <embed> <object> <applet> <marquee> 
很多情况下,我们把 hasLayout的状态改成true 就可以解决很大部分ie下显示的bug。 
hasLayout属性不能直接设定,你只能通过设定一些特定的css属性来触发并改变 hasLayout 状态。下面列出可以触发hasLayout的一些CSS属性值。 
------------------------------------- 
display 
启动haslayout的值:inline-block 
取消hasLayout的值:其他值 
-------------------------------------- 
width/height 
启动hasLayout的值:除了auto以外的值 
取消hasLayout的值:auto 
--------------------------------------- 
position 
启动hasLayout的值:absolute 
取消hasLayout的值:static 
---------------------------------------- 
float 
启动hasLayout的值:left或right 
取消hasLayout的值:none 
--------------------------------------- 
zoom 
启动hasLayout的值:有值 
取消hasLayout的值:narmal或者空值 
(zoom是微软IE专有属性,可以触发hasLayout但不会影响页面的显示效果。zoom: 1常用来除错,不过 ie 5 对这个属性不支持。)

♥︎什么是CSS3 transform? animation? 区别是什么?

一、transform描述的是元素静态样式

​ transform属性应用与元素的2D和3D转换。这个属性允许你将元素旋转,缩放,移动,倾斜等。必须是鼠标移上或者点击执行属性变化,鼠标离开属性回归。说到底就是属性不会变化。配合-webkit-transition: 0.3s;transition: 0.3s;才会有动画的效果,否则会很生硬。

旋转:rote(30deg)    水平面以元素中心旋转多少度;
     rotateX(angle)   定义沿着 X 轴的 3D 旋转;
     rotateY(angle)   定义沿着 Y 轴的 3D 旋转;
位移:translate(x,y)   定义 2D 转换;
     translate3d(x,y,z)  定义 3D 转换;
缩放:scale(x,y)   定义 2D 缩放转换;
     scale3d(x,y,z)   定义 3D 缩放转换;

二、实现动画效果的:transition animation

1、transition

transition 属性是一个简写属性,用于设置四个过渡属性 
transition: property    duration      timing-function   delay;
            过渡的属性 | 完成过度效果需要时间 | 速度曲线  |  延迟时间
.one1{transition: width 3s linear 2s;}    
.one1:hover{width:300px;}

transition定义了动画的属性、时间、速度曲线以及延迟时间  ;通常和hover等事件配合使用,由事件触发。

2、animation

animation 属性是一个简写属性,用于设置六个动画属性:
animation的使用必须结合@keyframes animation-name使用
    @keyframes  move{
                                        form{ left:0px;}  
                                        to{ left:200px;}
                                    }
在需要动画的元素上面添加动画  div{animation:move 5s infinite;}
animation: name duration timing-function delay iteration-count direction;
动画名称,动画执行时间,速度曲线,动画延迟时间,播放次数,是否反向播放
animation可以设定每一帧的样式和时间

区别:

  1. 触发条件不同。transition通常和hover等事件配合使用,由事件触发。animation则立即播放。
  2. 循环。 animation可以设定循环次数。
  3. 精确性。 animation可以设定每一帧的样式和时间。tranistion 只能设定头尾。 animation中可以设置每一帧需要单独变化的样式属性, transition中所有样式属性都要一起变化。
  4. 与javascript的交互。animation与js的交互不是很紧密。tranistion和js的结合更强大。js设定要变化的样式,transition负责动画效果,天作之合,比之前只能用js时爽太多。

‼️总结:

1.transition是css中检测指定属性变化进行自动补间动画的属性。

2.animation是先指定好动画过程中的关键帧属性,进行动画的属性。

♥︎如何理解HTML结构语义化?

一、什么是语义化?

字面意思就是说根据我们所说的话,就能了解其中的含义。语义化,故名思意,就是你写的HTML结构,是用相对应的有一定语义的英文字母(标签)表示的,标记的,因为HTML本身就是标记语言。不仅对自己来说,容易阅读,书写。别人看你的代码和结构也容易理解,甚至对一些不是做网页开发的人来说,也容易阅读。

二、什么是HTML语义化?

首先标签语义化是指HTML,不是CSS, 语义化标签只是HTML,CSS不存在语义化。HTML是标签,CSS是属性。

在最初html里标签的语义,我们看到table,就会知道这是列表,看到p,就知道这是段落,看到img知道是图片,看到input就知道这是一个表单,h1~h6是标题。 机器和人类相比笨多了,但是只要我们设定好程序,上面的标签的意思机器也能读懂。

三、怎样判断标签是否语义化

去掉样式,看网页结构是否组织良好有序,是否仍然有很好的可读性。

四、写HTML代码时应注意什么?

1.尽可能少的使用无语义的标签div和span;

2.在语义不明显时,既可以使用div或者p时,尽量用p, 因为p在默认情况下有上下间距,对兼容特殊终端有利;

3.不要使用纯样式标签,如:b、font、u等,改用css设置。

4.需要强调的文本,可以包含在strong或者em标签中(浏览器预设样式,能用CSS指定就不用他们),strong默认样式是加粗(不要用b),em是斜体(不用i);

5.使用表格时,标题要用caption,表头用thead,主体部分用tbody包围,尾部用tfoot包围。表头和一般单元格要区分开,表头用th,单元格用td;

6.每个input标签对应的说明文本都需要使用label标签,并且通过为input设置id属性,在lable标签中设置for=someld来让说明文本和相对应的input关联起来;

五、解决的问题(好处)

1.清晰的页面结构。去掉或样式丢失的时候,也能让页面呈现清晰的结构,增强页面的可读性。

2.支持更多的设备。屏幕阅读器会完全根据你的标记来“读”你的网页。更好的支持浏览器的阅读模式等。

3.有利于SEO(搜索引擎优化)。和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息,搜索引擎的爬虫也依赖于标记来确定上下文和各个关键字的权重。

4.便于团队开发和维护。在团队中大家都遵循同一个标准,可以减少很多差异化的东西,方便开发和维护,提高开发效率,甚至实现模块化开发。

5.方便其他设备解析(如屏幕阅读器、盲人阅读器、移动设备)以意义的方式来渲染网页;

♥︎块级元素?行内元素?空元素?

1.行内元素:

不独占一行、会和其他的行内元素排成一排;

不能设置宽高,默认的宽高是由其内容宽高撑起来的;

margin 上下不生效,左右生效;

2.块级元素

独占一行,不会和其他的元素排成一排;

可以设置宽高,默认的宽度是继承父元素的宽度、高度默认是其内容的高度;

margin 上下左右都可以设置,margin : auto;不生效;

3.行内块元素

不独占一行,可以和其他的行内元素或者行内块级元素排成一排;

设置宽高生效、默认宽度为父元素的宽度,默认高度为其内容的高度;

margin 上下左右生效,margin : auto;不生效;


常见行内元素:a、b、u、span、i、em、strong等文字标签

常见块级元素: div、table、tr、form、ul、li、ol、h1~h6、p

常见行内块元素: img、select、input

常见空元素: img、hr、br、input、meta


♥︎meta标签的name属性值

meta标签的name属性是用来定义一个HTML文档的描述、关键词,规定了元数据的名称,规定了content属性的信息/值的名称。

//属性值
1.application-name  //规定页面所代表的Web应用程序的名称
2.author  //规定页面文档的作者的名字
1.description  //规定页面的描述。搜索引擎会把这个描述显示在搜索结果中
实例:<meta name="description" content="页面描述">
2.gennerator  //规定用于生成文档的一个软件包(不用于手写页面)
实例:<meta name="gennerator" content="FrontPage 4.0">
3.keywords  //规定一个逗号分隔的关键词列表 - 相关的网页(告诉搜索引擎页面是与什么相关的)
实例:<meta name="keywords" content="HTML,meta tag,tag reference">

♥︎如何实现图片和文字在同一行显示?

1.给img标签添加“vertical-align:middle”属性

2.如果是背景图,则通过background的定位属性来设置位置

3.分别把图片和文字放入不同的div中,设置“vertical-align:middle”属性

♥︎简述video标签的几个属性和方法

1、video

video标签的使用方法,如下:

 <video src="视频文件路径" controls>请选择兼容的浏览器</video>
    <!-- 
        其中src时video的基本属性用于存放视频文件路径;
        而controls是video标签提供的一套默认的控制栏功能;
        而video标签中包含的文字是用于浏览器不支持时显示的;
     -->

通过在video中使用更多的视频格式,从而兼容更多的浏览器

<video controls>
    <source src = "视频文件路径" type = "video/格式">
    <source src = "视频文件路径" type = "video/格式">
    <source src = "视频文件路径" type = "video/格式">
    ...
</video>

2、video常用的标签播放属性

属性 允许取的值 取值的说明
autoplay autoplay 如果出现了该属性,则视频在缓冲就绪后马上播放
controls controls 如果出现了该属性,则向用户显示控件,比如播放按钮
height px(多少像素)、100% 设置视频播放器的高度
width px(多少像素)、100% 设置视频播放器的宽度
loop loop 如果出现该属性,则当媒体文件播放完后再次播放(循环播放)
preload preload 如果出现该属性,则视频在页面加载时进行加载,并预备播放。如果使用了autoplay,则忽略该属性)
src 视频文件的路径 视频文件的路径

3、video对象的常用方法

方法 方法描述
load() 该方法用于加载视频文件,为播放做准备。常用于播放前的预加载,也会用于重新加载媒体文件
play() 用于播放视频文件。如果视频没有加载,则加载并播放;如果视频时暂停的,则变为播放
pause() 暂停视频
canPlayType() 测试浏览器是否支持指定的视频类型

4、video对象的常用属性

属性 属性描述
currentSrc 返回当前视频文件的地址
currentTime 设置或者返回视频中的当前播放位置(以秒为单位)
duration 返回视频的长度(以秒为单位)
ended 返回视频的播放是否以及结束
error 返回视频错误状态的MediaError对象
paused 设置或者返回视频是否暂停
muted 设置或者返回视频是否关闭声音
volume 设置或者返回视频的音量大小
height / width 设置或者返回视频的高度值 / 宽度值

♥︎什么是border-image?什么是box-sizing?

一、border-image

border-image 属性可以通过一些简单的规则,将一副图像划分为 9 个单独的部分,浏览器会自动使用相应的部分来替换边框的默认样式。border-image 属性是五个 border-image-* 属性的简写,其语法格式如下:

//集成属性
border-image:border-image-source || border-image-slice [ / border-image-width | / border-image-width ? / border-image-outset ]? || border-image-repeat;
//单个属性
border-image-source:定义边框图像的路径;
border-image-slice:定义边框图像从什么位置开始分割;
border-image-width:定义边框图像的厚度(宽度);
border-image-outset:定义边框图像的外延尺寸(边框图像区域超出边框的量);
border-image-repeat:定义边框图像的平铺方式。

二、box-sizing

当布局使用的是自适应的布局方式时,盒子的宽度给的是百分比的形式,但是边框和内边距是用像素来表示的话就会改变盒子视觉上的大小,但是给加上box-sizing:border-box的话就会在不改变宽高的情况下,让边框和内边距满足我们所需要的条件之下,让content的大小自适应。

使用box-sizing:border-box;时的盒子模型:

使用了box-sizing:border-box;盒模型:

♥︎div+css的布局比较table布局有什么有点?

一、table布局

优点:

1、对于新手而言,容易上手,尤其对于一些布局中规中矩的网页,更让人首先想到excel,进而通过使用table去实现它。

2、表现上更加“严谨”,在不同浏览器中都能得到很好的兼容

3、通过复杂的表格套表格的形式,也可以实现比较复杂的布局需求。布置好表格,然后将内容放进去就可以了。

4、它可以不用顾及垂直居中的问题。

5、数据化的存放更合理。

缺点:

1、标签结构多,复杂,在表格布局中,主要是用到表格的相互嵌套使用,这样就会造成代码的复杂度更高!

2、表格布局,不利于搜索引擎抓取信息,直接影响到网站的排名;

二、div + css布局

优点:

1、符合W3C标准的,W3C标准提出网页由三部分组成:结构(Structure)、表现(Presentation)和行为(Behavior)。结构清晰明了,结构、样式和行为分离,带来足够好的可维护性。

2、布局更加灵活多样,能够通过样式选择来实现界面设计方面的更多要求。

3、布局改版方便,不需要过多地变动页面内容,通常只要更换相应的css样式就可以将网页变成另外一种风格展现出来。

4、布局可以让一些重要的链接和文字信息等优先让搜索引擎抓取,内容更便于搜索。

5、增加网页打开速度,增强用户体验。

缺点:

1、开发技术高,要考虑兼容版本浏览器。目前来看,DIV+CSS还没有实现所有浏览器的统一兼容。

2、CSS网站制作的设计元素通常放在1个外部文件中,或几个文件,

有可能相当复杂,甚至比较庞大,如果CSS文件调用出现异常,那么整个网站将变得惨不忍睹。

‼️总结:div+css的布局较table布局的明显优势;

1,其实也是div+css布局的第一个特点,table标签被严格地定义为存放数据的一个区域,而不是布局工具,它的布局形式不符合W3C标准,没有实现结构和表现的分离,它既有css的表现功能,也有html的结构功能。

2,table布局加载网页时,必须整体加载完,降低了网页的呈现速度,而div+css布局是边加载边显示的。

3,table布局在网页代码编写时,有时需要嵌套多重表格才能实现,但使用div+css布局,相对而言会减少许多嵌套时的代码,更容易检查和维护。

4,table布局不方便表现的更换,使用div+css布局,大多只要更改css样式表就能变化表现形式。

5、易于维护和改版。

♥︎display:none和visbility:hidden的区别是什么?

display:none 和visibility:hidden都可以实现让元素隐藏的效果,但原理是大不相同。

1.display:none是让这个元素失去块元素的效果,其本身这个元素也是直接消失,会影响到布局问题。

2.visibility:hidden:可以让元素消失,属于css样式,它只是简单的让元素看不见,但本身的位置还在,如果对div进行hidden,那么div除了看不见,其他所有的样式都在。


♥︎♥︎分别实现骰子中的一点 三点的布局。

1.一点布局

<div class="father">
    <div class="child"></div>
</div>
.father{
    width: 100px;
    height: 100px;
    background-color: black;
    border-radius: 10px;
    display: flex;
    justify-content: center;
    align-items: center;
}
.child{
    width: 20px;
    height: 20px;
    background-color: white;
    border-radius: 50%;
}

一点

2.三点布局

<div class="father">
    <div class="child"></div>
    <div class="child1"></div>
    <div class="child2"></div>
</div>
.father{
    width: 100px;
    height: 100px;
    background-color: black;
    padding: 5px;
    border-radius: 10px;
    display: flex;
    justify-content: space-between;
}
.child{
    width: 20px;
    height: 20px;
    background-color: white;
    border-radius: 50%;
}
.child1{
    width: 20px;
    height: 20px;
    background-color: white;
    border-radius: 50%;
    align-self: center;
}
.child2{
    width: 20px;
    height: 20px;
    background-color: white;
    border-radius: 50%;
    align-self: flex-end;
}

三点布局

♥︎♥︎简述选择器~和+的区别。

~选择器的作用:

​ 1.选择紧跟着当前符合条件元素后面的同级元素

​ 2.可以匹配多个

+选择器的作用:

​ 1.选择紧跟在当前符合条件元素后面的同级元素

​ 2.只能匹配一个

♥︎♥︎简述align-items和align-content的区别。

1.align-item属性是针对单独的每一个flex子项起作用,它的基本单位是每一个子项,在所有情况下都有效果(当然要看具体的属性值)。

2.align-content属性是将flex子项作为一个整体起作用,它的基本单位是子项构成的行,只在两种情况下有效果:①子项多行且flex容器高度固定 ②子项单行,flex容器高度固定且设置了flex-wrap:wrap;

‼️总结列表

条件 属性(是否有效果 是/否)
子项 flex容器 align-items align-content
单行 不指定高度
固定高度 否(但是有设置flex-wrap:wrap;时,有效果)
多行 不指定高度
固定高度

♥︎♥︎简述data-属性的用法(如何设置,如何获取),有何优势?

data-*定义:

1.是用于存储页面或应用程序的私有自定义数据。

2.赋予我们在所以html元素上嵌入自定义data属性的能力。

data-*用法:

1.属性名不应该包含任何大写字母,并且在前缀“data-”之后必须有至少一个字母。

2.属性值可以是任何字符。

3.一个元素可以拥有任意数量的data属性。

4.data属性无法存储对象,如需存储,可以通过对象序列化。

data-*设置方法:

1.如何设置

通过javascript内置的setAttribute(‘data属性名’,‘新内容’)即可设置。

(兼容性方法)通过该数据类型的(dataset)API设置data值,IE10以上才支持;

​		var button = document.queryselector('button')

​		button.dataset.data属性名 = ‘新内容’;//这里的data属性名是指data-后面的名字

2.如果获取

通过javascript内置的getAttribute(‘data属性名’)即可获取。
兼容性方法)通过该数据类型的(dataset)API设置data值,IE10以上才支持;

​		var button = document.queryselector('button')

​		data = button.dataset.data属性名;//这里的data属性名是指data-后面的名字

❗️data-*优势:

1.其存储的自定义数据能够被页面的javascript利用,可以创建更好的用户体验。

2.可以通过javascript来构造数据、填充数据。

3.代码体积小、较为灵活。

4.解决网站的外观和实用性之间产生的冲突。

♥︎♥︎CSS3 如何实现圆角?

一、border-radius 完整写法:

border-radius:为元素添加圆角边框。
border-radius:10px 20px 30px 40px / 40px 30px 20px 10px
“/”前的四个数值表示圆角的水平半径,后面四个值表示圆角的垂直半径。

比如:
border-radius: 2em 1em 4em / 0.5em 3em;
等同于:
border-top-left-radius: 2em 0.5em; //左上角
border-top-right-radius: 1em 3em; //右上角
border-bottom-right-radius: 4em 0.5em; //右下角
border-bottom-left-radius: 1em 3em; //左下角

常见简写方式:

  • border-radius:30px/20px;表示每个圆角的水平半径时30px,垂直半径为20px;
  • border-radius:30px; 等同于:border-radius:30px 30px 30px 30px/30px 30px 30px 30px;

♥︎♥︎HTML5有哪些缓存方式?

1、localstorege缓存,将数据储存在本地客户端,只有用户手动清除才能清除缓存

API:
    1.localstorege.setItem(key,value),键值对的形式缓存
  2.localstorege.getItem(key),根据键名来缓存值
  3.localstorege.length ,获取总缓存数量

2、sessionStorege 会话缓存,会话机制是指从打开浏览器开始访问页面的时候,到关闭这个页面的过程成为一个会话,sessionStorege储存的数据会随着页面关闭而销毁

API: 
    1. sessionStorage.setItem(key,val),localStorage是以键值对的形式创建的;
  2. sessionStorage.getItem(key),根据键名来获取缓存的值;
  3. sessionStorage.length;获取总共缓存值得数量, localStoarge返回的是个对象;

3、离线缓存机制(Application Cache)

一、配置manifest文件,manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)
二、manifest 文件可分为三个部分:
    1、CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存
    2、NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存
    3、FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面)
三、API: 0(UNCACHED) : 无缓存, 即没有与页面相关的应用缓存
    1 (IDLE) : 闲置,即应用缓存未得到更新
    2 (CHECKING) : 检查中,即正在下载描述文件并检查更新
    3 (DOWNLOADING) : 下载中,即应用缓存正在下载描述文件中指定的资源
    4 (UPDATEREADY) : 更新完成,所有资源都已下载完毕
    5 (IDLE) : 废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存

4、web SQL

1. 关系数据库,通过SQL语句访问
2. Web SQL 数据库API并不是HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用SQL操作客户端数据库的APIs
3. 支持情况:Web SQL 数据库可以在最新版的 Safari, Chrome 和 Opera 浏览器中工作。
4. API:
    openDatabase:这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。
    transaction:这个方法让我们能够控制一个事务,以及基于这种情况执行提交或者回滚。
    executeSql:这个方法用于执行实际的 SQL 查询。

5、 IndexDB

    索引数据库 (IndexedDB) API(作为 HTML5 的一部分)对创建具有丰富本地存储数据的数据密集型的离线 HTML5 Web 应用程序很有用。同时它还有助于本地缓存数据,使传统在线 Web 应用程序(比如移动 Web 应用程序)能够更快地运行和响应。

♥︎♥︎CSS3新增伪类有那些

常用的伪类:
     1. :link 选择所有未访问的链接
     2. :visited 选择所有访问过的链接
     3. :active 选择正在活动的链接(或理解为鼠标点击瞬间效果)
     4. :hover 鼠标放到链接后的状态
     5. :focus 选择元素输入后具有焦点
     6. :before 在元素之前插入内容
     7. :after 在元素之后插入内容

♥︎♥︎什么叫做优雅降级和渐进增强?

一、渐进增强 progressive enhancement:

  1. 针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。

  2. 渐进增强观点则认为应关注于内容本身。内容是我们建立网站的诱因。有的网站展示它,有的则收集它,有的寻求,有的操作,还有的网站甚至会包含以上的种种,但相同点是它们全都涉及到内容。这使得“渐进增强”成为一种更为合理的设计范例。这也是它立即被 Yahoo! 所采纳并用以构建其“分级式浏览器支持 (Graded Browser Support)”策略的原因所在。

二、优雅降级 graceful degradation:

  1. 一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

  2. 优雅降级观点认为应该针对那些最高级、最完善的浏览器来设计网站。而将那些被认为“过时”或有功能缺失的浏览器下的测试工作安排在开发周期的最后阶段,并把测试对象限定为主流浏览器(如 IE、Mozilla 等)的前一个版本。

‼️区别:

  1. 优雅降级是从复杂的现状开始,并试图减少用户体验的供给

  2. 渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需要

  3. 降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带

♥︎♥︎请问苹果原生浏览器中默认样式如何清除,例如button,input默认样式?

清除苹果默认样式:css样式中加入 
input,textarea,button { 
  -webkit-appearance: none; 
  border-radius:0px; 
  border:none;
}
input、button默认样式: 
input[type="button"], input[type="submit"], input[type="reset"] {
  -webkit-appearance: none;
}

♥︎♥︎PC端常用的布局方法。

一、两列布局

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style type="text/css">
            /* 宽度适配 */
            html,
            body {
                width: 100%;
                overflow-x: hidden;/* 外层盒子设置最小宽度的话看不到横向滚动条 */
            }

            /*1. pc端适配的需求:目前我们pc项目的设计稿尺寸是宽度1920,高度最小是1080。
            2.放大或者缩小屏幕,网页可以正常显示 */
            /* 一、两列布局 */
            /* 1.左定宽 右边自适应 或者 右边定宽左边自适应 */
            .content{
                width: 1200px; /* 主容器 */
                min-width: 960px;
                margin: 0 auto;
                background: #fff;
            }
            .left {
                float: left;
                width: 200px;/* 定宽 */
                background: #ccc;
                height: 800px;/* 测试设了一个高度和背景(为了更好看效果) */
            }

            .right {
                margin-left: 100px;
                background: #999;
                height: 800px;/* 测试设了一个高度和背景(为了更好看效果) */
            }
        </style>
    </head>
    <body>
        <div class="content">
            <div class="left">左边</div>
            <div class="right">右边</div>
        </div>
    </body>
</html>

二、三列布局

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style type="text/css">
            /* 宽度适配 */
            html,
            body {
                width: 100%;
                overflow-x: hidden;
                /* 外层盒子设置最小宽度的话看不到横向滚动条 */
            }
            /* 一、三列布局 */
            /* 1.左右定宽中间自适应 */
            .content {
                width: 1200px;
                /* 主容器 */
                min-width: 960px;
                margin: 0 auto;
                background: firebrick;/* 测试设了一个背景(为了更好看效果) */
                display: table;
            }

            .left {
                width: 100px;
                /* 定宽 */
                background: #ccc;
                height: 800px;
                /* 测试设了一个高度和背景(为了更好看效果) */
            }
            .right {
                width: 100px;
                /* 定宽 */
                background: fuchsia;
                height: 800px;
                /* 测试设了一个高度和背景(为了更好看效果) */
            }

            .left,
            .right,
            .center {
                display: table-cell;
            }
        </style>
    </head>
    <body>
        <div class="content">
            <div class="left">左边</div>
            <div class="center">中间</div>
            <div class="right">右边</div>
        </div>
    </body>
</html>

三、双飞翼布局

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>实现三栏水平布局之双飞翼布局</title>
        <style type="text/css">
            .container {
                width: 1200px;
                /* 主容器 */
                min-width: 960px;
                margin: 0 auto;
                background: #ccc;
                /* 测试设了一个背景(为了更好看效果) */
            }

            .left,
            .center,
            .right {
                float: left;
                min-height: 400px;
                /* 测试更好观看效果 统一高度*/
                text-align: center;
            }

            .left {
                margin-left: -100%;
                background: #0000FF;
                width: 200px;
                /* 定宽 */
            }

            .right {
                margin-left: -300px;
                background-color: #FF0000;
                width: 300px;
                /* 定宽 */
            }

            .center {
                background-color: #f2f1f1;
                width: 100%;
            }

            .content {
                margin: 0 300px 0 200px;
            }
        </style>
    </head>
    <body>
        <div class="container">
              <div class="center">
                  <div class="content">中间自适应</div>
               </div>
              <div class="left">左边</div>
              <div class="right">右边</div>
        </div>
    </body>
</html>

四、圣杯布局

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>实现三栏水平布局之圣杯布局</title>
        <style type="text/css">
            .container {
                width: 1200px;
                /* 主容器 */
                min-width: 960px;
                margin: 0 auto;
                background: #ccc;/* 测试设了一个背景(为了更好看效果) */
                padding: 0 300px 0 200px;
            }

            .left,
            .center,
            .right {
                position: relative;
                min-height: 200px;
                float: left;
            }

            .left {
                left: -200px;
                margin-left: -100%;
                background: green;/* 测试设了一个背景(为了更好看效果) */
                width: 200px;
            }

            .right {
                right: -300px;
                margin-left: -300px;
                background: red;/* 测试设了一个背景(为了更好看效果) */
                width: 300px;
            }

            .center {
                background: blue;/* 测试设了一个背景(为了更好看效果) */
                width: 100%;
            }
        </style>
    </head>
    <body>
        <div class="container">
              <div class="center">center</div>
              <div class="left">left</div>
              <div class="right">right</div>
        </div>
        <div class="tip_expand">双飞翼布局比圣杯布局多创建了一个div,但不用相对布局了</div>
    </body>
</html>

五、flex弹性盒布局

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>实现三栏水平布局-Flex布局</title>
        <style type="text/css">
            .container {
                display: flex;
                width: 1200px;
                /* 主容器 */
                min-width: 960px;
                margin: 0 auto;
                background: #ccc;
                /* 测试设了一个背景(为了更好看效果) */
                min-height: 800px;
                font-size: 0; /* 间隙处理 */
            }

            .main {
                flex-grow: 1;
                background-color: blue;
                font-size: 24px;
            }

            .left {
                order: -1;/* 对于order属性:定义项目的排列顺序,越小越靠前,默认为0。 */
                flex-basis: 200px;/* 通过项目属性flex-basis 设置left和right的固定宽度 */
                background-color: green;
                font-size: 24px;
            }

            .right {
                flex-basis: 300px;/* 通过项目属性flex-basis 设置left和right的固定宽度 */
                background-color: red;
                font-size: 24px;
            }
        </style>
    </head>
    <body>
        <div class="container">
              <div class="main">main</div>
              <div class="left">left</div>
              <div class="right">right</div>
        </div>
    </body>
</html>

♥︎♥︎行内元素和块级元素?img算什么?行内元素怎么转化为块元素?

行内元素:
    1. 无法设置宽高;
    2. 对margin仅设置左右有效,上下无效;
    3. padding上下左右有效;不会自动换行
块级元素:
    1. 可以设置宽高;
    2. margin和padding的上下左右均对其有效
    3. 超出当前行会自动换行
     4. 多个块状元素标签写在一起,默认排列方式为从上至下
img:属于行内块元素(inline-block),即有行内元素的属性也有块级元素的属性
元素之间的转化可以通过设置样式:display:block/inline/inline-block来改变自身的元素属性

♥︎♥︎nth-of-type和nth-child的区别是什么?

MDN上的概念:

某个元素:nth-of-type(n)这个CSS 伪类是针对具有一组兄弟节点的标签, 用 n 来筛选出在一组兄弟节点的位置。(一组标签内选择)

某个元素:nth-child(n)这个CSS 伪类首先找到所有当前元素的兄弟元素,, 用 n 来筛选出在当前元素的兄弟元素节点的位置。(兄弟元素里选择)

我们可以注意到:nth-of-type他是当前元素的兄弟元素的第n个,而nth-child是当前元素的兄弟节点的第n个当前元素。

♥︎♥︎:before和 ::before区别是什么?

区别:
 1. 叫法不同:一个是伪类,一个是伪元素
 2. 版本不同:作用都是一样,但单冒号伪类写法是旧版本css2写法, 双冒号伪元素是新版本css3写法
 3. 兼容性差异:单冒号伪类写法 兼容性比 双冒号要好。 :before > ::before

♥︎♥︎简述 viewport所有属性

width:控制 viewport 的大小,可以指定的一个值,如 600,或者特殊的值,如 device-width 为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)。
height:和 width 相对应,指定高度。
initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例(调整页面缩放比例)。
maximum-scale:允许用户缩放到的最大比例。
minimum-scale:允许用户缩放到的最小比例。
user-scalable:用户是否可以手动缩放

♥︎♥︎伪类选择器和伪元素?CSS3中引入的伪元素有什么?

1. 伪类选择器是css2版本中的旧写法,相对于css3中伪元素的的新写法兼容性会更好。
2. 伪元素只能在一个选择器中出现一次,且需要配合content属性一起使用
3. 伪元素不会出现在DOM中,所以不能通过js来进行操作,仅仅是在渲染层加入而已

css3引入的伪元素:
 1、 ::after //在xxx之后插入内容
 2、 ::before // 在xxx之前插入内容
 3、 ::first-letter //选择xxx元素的首字母
 4、 ::first-line //选择xxx元素的首行
 5、 ::selection //选择用户选择的元素部分

♥︎♥︎HTML5有哪些新特性,移除了哪些元素?如何处理HTML5

HTML5 现在已经不是 SGML 的子集,主要是关于图像,位置,存储,多任务等功能的增加

新增选择器 document.querySelector、document.querySelectorAll
拖拽释放(Drag and drop) API
媒体播放的 video 和 audio
本地存储 localStorage 和 sessionStorage
离线应用 manifest
桌面通知 Notifications
语意化标签 article、footer、header、nav、section
增强表单控件 calendar、date、time、email、url、search
地理位置 Geolocation
多任务 webworker
全双工通信协议 websocket
历史管理 history
跨域资源共享(CORS) Access-Control-Allow-Origin
页面可见性改变事件 visibilitychange
跨窗口通信 PostMessage
Form Data 对象
绘画 canvas
移除的元素:

纯表现的元素:basefont、big、center、font、 s、strike、tt、u
对可用性产生负面影响的元素:frame、frameset、noframes

♥︎♥︎新标签兼容问题?如何区分HTML和HTML5?

支持HTML5新标签:

IE8/IE7/IE6支持通过document.createElement方法产生的标签
可以利用这一特性让这些浏览器支持HTML5新标签
浏览器支持新标签后,还需要添加标签默认的样式
当然也可以直接使用成熟的框架、比如html5shim

如何区分HTML和HTML5:

HTML: 

    1)标识文本(eg: 定义标题文本、段落文本、列表文本、预定义文本);

     2)建立超链接,便于页面链接的跳转;

     3)创建列表,把信息有序地组织在一起,方便浏览;

     4)在网页中显示“图像、声音、视频、动画”等多媒体信息,使网页设计更具冲击力;

     5)可制作表格,以便显示大量数据;

     6)可制作表单,允许在网页内输入文本信息,执行其他用户操作,方便信息互动;

     7)没有体现结构语义化的标签;

HTML5: 

    1)用于绘画的canvas元素; 

    2)用于媒介回放的video和audio元素; 

    3)对本地离线存储有更好的支持; 

    4)新的特殊内容元素(eg: article、footer、header、nav、section等); 

    5)新的表单控件(eg: calendar、date、time、email、url、search等); 

    6)有语义优势,提供了一些新标签,(eg: <header> <article> <footer> 提供了语义化标 

签),可以更好地支持搜索引擎的读取,便于SEO蜘蛛的爬行。

♥︎♥︎常见浏览器兼容性问题?

[常见浏览器兼容问题]: “https://blog.csdn.net/weixin_43214644/article/details/125872520

♥︎♥︎media属性?screen? All? max-width? min-width?

media: 媒体查询

screen :计算机屏幕

All :默认,适合所有设备

max(min)-width :规定目标显示区域的宽度

css合并写法: @media screen and (min-width:xxxpx) {}

♥︎♥︎ 一般做手机页面切图的几种方式

针对手机端页面,通常情况下,需要对设计图片切两种图片。

①:dpr:2——切两倍图(即设计原图大小,因为设计图是按原来的手机尺寸放大两倍之后的) 一般保存为xxx@2x

②:dpr:3——切三倍图(即设计原图大小的1.5倍,因为设计图是按原来的手机尺寸放大两倍之后的) 一般保存为xxx@3x

例如:设计图是720px的宽度。

由于设计图是放大两倍的。所以一倍的大小是=720/2 = 360px;

放大三倍图就是= 3603 = 7201.5 = 1080px;

♥︎♥︎px/em/rem有什么区别?为什么通常给font-size设置的字体为62.5%

px(像素):页面默认的尺寸计算单位,绝对长度,它是相对于显示器屏幕分辨率而言的;

特点:
        1. IE无法调整那些使用px作为单位的字体大小;
        2. 国外的大部分网站能够调整的原因在于其使用了em或rem作为字体单位; 
        3. Firefox能够调整px和em,rem,但是96%以上的中国网民使用IE浏览器(或内核)。

em:相对长度,相对于应用在当前元素的字体尺寸;一般浏览器默认字体大小为16px,则 1em = 16px

特点:1. em的值并不是固定的; 2. em会继承父级元素的字体大小。

rem(root em):相对单位,相对于html根元素字体大小的单位,当html的font-size:16px时,

1rem = 16px

特点:
        1. 这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。 
        2. 除了IE8及更早版本外,所有浏览器均已支持rem。

为什么给font-size设置为62.5%: 方便换算!

1. 因为绝大多数浏览器的默认字体大小为 16px ,而将font-size设置为 62.5% 则等价于字体大小的font-size:10px;
2. 随之在其他的换算单位,如 rem 的字体换算时,则可以表示为 1rem = 10px, 整数值对于单位的
换算会比较方便;
3. 但是在Chrome(谷歌浏览器)中,支持最小字体的大小为 12px ,解决办法就是 将html根字体设置为 font-size: 625%; 意:1rem = 100px ,以此单位换算;

♥︎♥︎sass和scss有什么区别? sass一般怎么样编译的

Sass和SCSS其实是同一种东西,我们平时称之为Sass,两者之间不同之处有以下两点:

1、文件扩展名不同,Sass是以“.sass”后缀为扩展名,而SCSS是以“.scss”后缀为扩展名
2、语法书写方式不同,Sass是以严格的缩进式语法规则来书写,不带大括号({})和分号(;),而SCSS的语法书写和我们的CSS语法书写方法非常类似。

Sass语法

$font-stack:Helvetica,sans-serif //定义变量
$primary-color:#eee  //定义变量
 
html
  font:100% $font-stack
  color:$primary-color

SCSS语法

$font-stack:Helvetica,sans-serif //定义变量
$primary-color:#eee  //定义变量
 
html {
  font:100% $font-stack
  color:$primary-color
}

♥︎♥︎如果对css进行优化如何处理?

优化原则:减少css样式的渲染加载时间,通过削减css样式的代码体积等相关操作

实践型优化:

1、内联首屏关键CSS(Critical CSS):内联CSS能够使浏览器开始页面渲染的时间提前,性能优化中有一个重要的指标——首次有效绘制(First Meaningful Paint,简称FMP)即指页面的首要内容(primary content)出现在屏幕上的时间,这一指标影响用户看到页面前所需等待的时间,而内联首屏关键CSS(即Critical CSS,可以称之为首屏关键CSS)能减少这一时间。

‼️注:内联css并不是不加以限制的,它的初始拥堵窗口3存在限制(TCP相关概念,通常是 14.6kb, 压缩后的大小),如果内联CSS后的文件超出了这一限制,系统就需要在服务器和浏览器之间进行更多次的往返,这样并不能提前页面渲染时间。

2、异步加载CSS :CSS会阻塞渲染,在CSS文件请求、下载、解析完成之前,浏览器将不会渲染任何已处理的内容。 有时,这种阻塞是必须的,因为我们并不希望在所需的CSS加载之前,浏览器就开始渲染页面。 那么将首屏关键CSS内联后,剩余的CSS内容的阻塞渲染就不是必需的了,可以使用外部CSS,并且异步加载;

方式一、 使用JavaScript动态创建样式表link元素,并插入到DOM中。 
方式二、 将link元素的media属性设置为用户浏览器不匹配的媒体类型(或媒体查询),如 media="print",甚至可以是完全不存在的类型media="noexist"。对浏览器来说,如果样式表不适用于当前媒体类型,其优先级会被放低,会在不阻塞页面渲染的情况下再进行下载。

3、文件压缩

通过相关的构建工具对css样式进行打包压缩,去除多余的空格和换行。如 webpack、rollup、 
grunt/gulp.js 等 

4、去除无用CSS

1. 筛选去除相关重复的css样式 

2. 去除在页面中无法生效或不生效的css样式

建议型优化:

1、有选择地使用选择器;

2、减少使用昂贵的属性;

1. 在浏览器绘制屏幕时,所有需要浏览器进行操作或计算的属性相对而言都需要花费更大的代价。 
2. 当页面发生重绘时,它们会降低浏览器的渲染性能。所以在编写CSS时,我们应该尽量减少使用昂贵 属性 * 昂贵属性: 如box-shadow/border-radius/filter/透明度/伪类:nth-child()等

3、优化重排与重绘;

减少重排 
        1. 重排会导致浏览器重新计算整个文档,重新构建渲染树,这一过程会降低浏览器的渲染速度。有 很多操作会触发重排,我们应该避免频繁触发这些操作。

避免不必要的重绘 
        1. 当元素的外观(如color,background,visibility等属性)发生改变时,会触发重绘。 
        2. 在网站的使用过程中,重绘是无法避免的。不过,浏览器对此做了优化,它会将多次的重排、重 绘操作合并为一次执行。 
        3. 不过我们仍需要避免不必要的重绘,如页面滚动时触发的hover事件,可以在滚动的时候禁用 hover事件,这样页面在滚动时会更加流畅。

4、不要使用@import;

不建议使用@import主要有以下两点原因。 
  1.使用@import引入CSS会影响浏览器的并行下载。
  2.使用@import引用的CSS文件只有在引用它的 那个css文件被下载、解析之后,浏览器才会知道还有另外一个css需要下载,这时才去下载,然后下载后开始 解析、构建render tree等一系列操作。这就导致浏览器无法并行下载所需的样式文件。 
  3.多个@import会导致下载顺序紊乱。在IE中,@import会引发资源文件的下载顺序被打乱, 即排列在@import后面的js文件先于@import下载,并且打乱甚至破坏@import自身的并行下载.

♥︎♥︎如何对css文件进行压缩合并? gulp如何实现?

[Gulp压缩css]: “https://www.jianshu.com/p/00b3f479dc90

如何压缩合并:

通过相关的构建工具对css样式进行打包压缩,去除多余的空格和换行。如 webpack、rollup、
grunt/gulp.js 等
a标签的设置顺序:
     1. link , 链接平常的状态
     2. hover ,鼠标放置在链接上显示的样式
     3. active ,链接被按下的样式
     4. visited , 链接被访问过后的状态

♥︎♥︎常见的视频编码格式有几种?视频格式有几种?

常见的视频编码格式,H264 , VP8, AVS, RMVB,WMV,QuickTime(mov)

视频格式有MPEG、AVI、nAVI、ASF、MOV、3GP、WMV、DivX、XviD、RM、RMVB、FLV/F4V。

♥︎♥︎canvas在标签上设置宽高和在style中设置宽高有什么区别?

在canvas标签上设置宽高, canvas画布发生的变化不会影响到画布内容,即画布内容不会发生改变相反的,在style样式中设置宽高则会影响到画布内容的形状;

♥︎♥︎canvas如何绘制一个三角形|正方形

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    canvas {
        margin: 0 auto;
        border: 2px solid #aaa;
        display: block; /*画布居中*/
    }
    </style>
</head>
<body>
    <canvas id="cont" width="500px" height="500px">Hello Canvas</canvas>
    <script>
        
        //获取画布
        var canvas = document.querySelector("#cont");
        //获取画布上下文
        var ctx = canvas.getContext('2d');
        function drawLine(x1, y1, x2, y2, color, width) {
            ctx.beginPath();  //开启一条路径
            ctx.moveTo(x1, y1); //确定起始点
            ctx.lineTo(x2, y2); //确定结束点
            ctx.strokeStyle = color;  //着色之前设置颜色和线宽
            ctx.lineWidth = width;
            ctx.stroke(); //着色
            ctx.closePath(); //结束路径
        }
        drawLine(100, 100, 400, 100, 'green', 5);
        drawLine(400, 100, 400, 400, 'purple', 5);
        drawLine(400, 400, 100, 400, 'orange', 5);
        drawLine(100, 400, 100, 100, 'blue', 5);

    </script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    canvas {
        margin: 0 auto;
        border: 2px solid #aaa;
        display: block; /*画布居中*/
    }
    </style>
</head>
<body>
    <canvas id="cont" width="500px" height="500px">Hello Canvas</canvas>
    <script>
        var canvas = document.querySelector("#cont");
        //获取画布上下文
        var ctx = canvas.getContext('2d');
        ctx.beginPath();
        ctx.moveTo(100, 100);
        //此处直线连用,画完三条线再关闭路径
        ctx.lineTo(400, 100);
        ctx.lineTo(400, 400);
        ctx.lineTo(100, 100);
        ctx.strokeStyle = 'purple';
        ctx.lineWidth = 5;
        ctx.stroke();
        ctx.closePath();
    </script>
</body>
</html>

♥︎♥︎解释下CSS sprites,以及你要如何在页面或者网站中使用它。

css sprites直译过来就是CSS精灵。通常被解释为“CSS图像拼合”或“CSS贴图定位”。其实就是通过将多个图片融合到一张图里面,然后通过CSS background背景定位技术技巧布局网页背景。这样做的好处也是显而易见的,因为图片多的话,会增加http的请求,无疑促使了网站性能的减低,特别是图片特别多的网站,如果能用css sprites降低图片数量,带来的将是速度的提升。

css sprites是什么通俗解释:CSS Sprites其实就是把网页中一些背景图片整合拼合成一张图片中,再利用DIV CSS的“background-image”,“background- repeat”,“background-position”的组合进行背景定位,background-position可以用数字能精确的定位出背景图片在布局盒子对象位置。

//如何使用
ps切好图,利用定位代码实现

♥︎♥︎a点击出现框,解决方法。

解决a标签点击会出现虚框现象。
    当a标签获得焦点的时候,a标签的周围就会出现虚框,它不同于border,不占任何宽度,a失去焦点的时候就会消失,就是outline。在遨游,Firefox ,IE的几个版本中就会看到、而Safari、Opera、Google 本身不支持这个效果,就看不到。

解决方法: 
    1、可以给a标签设置 outline: none;但在IE6、IE7 遨游中都不能实现。只有在IE8、Firefox中才会消除虚框。
    2、在a标签中加入js控制,当a标签活的焦点是就强制取消焦点。<a href="#"  onfocus="this.blur();"></a>,这里设置聚焦时触发blur(),强制取消焦点。
    3、在a标签里面嵌入其他标签,如span ,这样点击时,嵌套的标签活的焦点,a标签自然就不会出现虚框。
    4、不用a标签做链接,采用其他标签,用js控制实现点击跳转。

♥︎♥︎CSS3中多列布局的用处是什么?

多列布局是专门针对于文本(图文)排版的一种布局形式,作用对象是容器中的内容数据。
常见应用于电子杂志、阅读APP类型的项目,兼容性较好,IE10+及现代浏览器都支持(加前缀)

[多列布局属性]: “https://www.jianshu.com/p/6776f811b6d3

♥︎♥︎WebSQL是什么?WebSQL是HTML5规范的一部分吗?

WebSQl是前端的一个独立模块,是web存储方式的一种,用于存储或管理数据库中数据的网页。我们调试的时候会经常看到,只是一般很少使用。并且,当前只有谷歌支持,ie和火狐均不支持。

我们对数据库的一般概念是后端才会跟数据库打交道,进行一些业务性的增删改查。而这里的数据库也不同于真正意义上的数据库。

WebSQl API不是HTML5规范的一部分,而是一个单独的规范。它提供了一组API来操作客户数据库。

♥︎♥︎介绍一下CSS的盒子模型?弹性盒子模型是什么?

盒子模型分为IE盒子和W3C盒子两种

W3C盒子(标准盒子模型)由margin,border,padding,content,设置的width height是针对content的
IE盒子(怪异盒子模型)由margin,border,padding,content构成,但是设置的width height是包括border+padding+content

弹性盒子模型:弹性盒模型决定一个盒子在其他盒子中的分布方式以及如何处理可用的空间;可以很轻松的创建自适应浏览器窗口的流动布局或自适应字体大小的弹性布局;

盒模型是CSS的基石之一,它指定元素如何显示以及如何相互交互。页面上的每个元素被看做一个矩形框,这个框由元素的内容、内边距、边框和外边距组成;
置为怪异盒模型,如何设置为怪异盒模型
如果在.html页面中缺少<!doctype>声明的话,就会触发怪异盒子模型
设置怪异盒子模型:通过box-sizing:border-box来设置怪异盒子模型

♥︎♥︎Doctype的作用?标准模式与兼容模式各有什么区别?

一、Doctype作用
<!DOCTYPE>声明位于HTML文档中的第一行,处于<html>标签之前。告知浏览器的解析器用什么文档标准解析这个文档。
DOCTYPE不存在或格式不正确会导致文档以兼容模式呈现。

二、兼容模式与标准模式的区别
标准模式的排版和js运作模式,都是以该浏览器支持的最高标准运行。
在兼容模式中,页面以宽松的向后兼容的方式显示,模拟老式浏览器的行为以防止站点无法工作。

♥︎♥︎前端页面有哪三层构成? 分别是什么? 作用是什么?

一、网页的**结构层(structural layer)**由HTML或XHTML之类的标记语言负责创建。
标记语言也就是指网页的标签,标签只对网页内容的语义和含义做出描述,不包含任何关于如何显示内容的信息。

二、网页的**表示层(resentation layer)**由CSS负责创建。
作用是对内容如何显示做一定的控制。

三、网页的**行为层(behavior layer)**由JavaScript语言和DOM创建。
作用是控制用户做出一个事件该如何显示。例如:用户悬浮在某个元素上,弹出一个显示元素标题内容的提示框

♥︎♥︎rem和em。

rem和em很容易混淆,其实两个都是css的单位,并且也都是相对单位,现有的em,css3才引入的rem。

一、em

em作为font-size的单位时,其代表父元素的字体大小,em作为其他属性单位时,代表自身字体大小——MDN

em可以让我们的页面更灵活,更健壮,比起到处写死的px值,em似乎更有张力,改动父元素的字体大小,子元素会等比例变化,这一变化似乎预示了无限可能

有些人提出用em来做弹性布局页面,但其复杂的计算让人诟病,甚至有人专门做了个px和em的计算器,不同节点像素值对应的em值。

缺点:牵一发而动全身,一旦某个节点的字体大小发生变化,那么其后代元素都得重新计算。

二、rem

rem作用于非根元素时,相对于根元素字体大小;rem作用于根元素字体大小时,相对于其出初始字体大小——MDN

rem取值分为两种情况,设置在根元素时和非根元素时,举个例子

/* 作用于根元素,相对于原始大小(16px),所以html的font-size为32px*/
html {font-size: 2rem}

/* 作用于非根元素,相对于根元素字体大小,所以为64px */
p {font-size: 2rem}

本质:rem布局的本质是等比缩放,一般是基于宽度.

♥︎♥︎pointer-events: none 是干什么的?

pointer-events: none;理解:你可以看的到某个元素,但是你无法摸的着,点击不到,点击会穿透触发到下层的元素
display:none; 是你摸不着,但是你也看不见

大家都知道 input[type=text|button|radio|checkbox]支持 disabled 属性,可以实现事件的完全禁用。
如果其他标签需要类似的禁用效果,可以试试 pointer-events: none

♥︎♥︎♥︎ html中元素的margin是否会叠加(合并)?如何解决?

/* 会叠加 */
问题详解1: flex布局对子元素的影响
 1.子元素的float、clear和vertical-align属性将会失效
 2.解决了margin传递、重叠(叠加)问题

问题详解2:flex布局的margin传递叠加问题主要有以下两种
 1.父子间的margin,会由子级传递到父级
 —— 解决方法: margin传递的产生的原因是父级的高度没有被自动撑开,所以在父级父级增加属
性:overflow: auto 即可解决
 2.兄弟间的margin值会重复叠加
 —— 解决方法: 浏览器为了保证列表的整齐,上下margin产生了叠加,不能直接解决。只能通过减
少一个margin的方式。如只定义margin-top:100px; margin-bottom:0px。的方式解决。

♥︎♥︎♥︎ 移动端适配怎么做?

方法一:@media 媒体查询,通过查询设备的宽度来执行不同的 css 代码,最终达到界面的配置。
方法二:Flex弹性布局
方法三:rem + viewport 缩放,屏幕宽度设定 rem 值,需要适配的元素都使用 rem 为单位,不需要适配的元素还是使用 px 为单位。

♥︎♥︎♥︎ 手机端上图片长时间点击会选中图片,如何处理?

img{ pointer-events:none },禁止事件,但会把整个标签的事件都禁用掉,不建议使用
img{ -webkit-user-select:none },用户选中状态

推荐:
img{
       -webkit-touch-callout: none; //触摸
         -webkit-user-select: none;
         -moz-user-select: none;
         -ms-user-select: none;
         user-select: none;
 }

♥︎♥︎♥︎ 说说对transition的了解。

1、transition 属性是一个简写属性,可用于设置四个过渡属性:

transition-property 过渡效果的 CSS 属性的名称(height、width、opacity等)。
transition-duration 完成过渡效果需要时间。
transition-timing-function 规定速度效果的速度曲线。
transition-delay 过渡效果何时开始(延迟时间)。
注:如果 transition-duration属性时长为 0,就不会产生过渡效果。

2、渐变函数的值:

渐变函数是transition-timing-function;

其中贝塞尔曲线的预设值

ease渐快,匀速,减慢cubic-bezier(0.25,0.1,0.25,1)
ease-in渐快,匀速cubic-bezier(0.42,0,1,1)
ease-out匀速,减慢cubic-bezier(0,0,0.58,1)
ease-in-out和ease类似,但比ease的加速度大(幅度大)cubic-bezier(0.42,0,0.58,1)
linear全程匀速cubic-bezier(0,0,1,1)

3、简写方式:

transition:css属性名 过度时间 渐变函数值 延迟时间

♥︎♥︎♥︎ 为什么要初始化CSS样式?

1、浏览器差异

因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异。

2、提高编码质量

初始化CSS可以节约网页代码,节约网页下载时间;还会使得我们开发网页内容时更加方便简洁,不用考虑很多。

如果不初始化,整个页面做完会很糟糕,重复的css样式很多。

我们在开发比较复杂的网页时候就不会知道自己是否已经设置了此处的CSS属性,是否和前面的CSS属性相同,是否统一整个网页的风格和样式。

‼️弊端:初始化样式会对seo有一定的影响,但鱼和熊掌不可兼得,但力求影响最小的情况下初始化。

⁉️总结:CSS初始化是指重设浏览器的样式。不同的浏览器默认的样式可能不尽相同,所以开发时的第一件事可能就是如何把它们统一。如果没对CSS初始化往往会出现浏览器之间的页面差异。每次新开发网站或新网页时候通过初始化CSS样式的属性,为我们将用到的CSS或html标签更加方便准确,使得我们开发网页内容时更加方便简洁,同时减少CSS代码量,节约网页下载时间。

♥︎♥︎♥︎ CSS3中的选择器都有什么?

[CSS3所有选择器讲解汇总]: “https://blog.csdn.net/weixin_44860226/article/details/126031813

♥︎♥︎♥︎ 本地存储有生命周期吗?

没有

cookie: expire 和 max-age 都能控制数据的存储时间。expire 是一个绝对的过期时间,max-age是文档被访问之后的存活时间(是相对时间)。默认是 session。
sessionStorage 当会话被关闭后(浏览器、标签页被关闭),就会被清除。与 localStorage 用法一样。
localStorage: 除非被主动清除,不然永久储存在浏览器中。
IndexedDB: 没有过期时间,除非主动清除。

♥︎♥︎♥︎ CSS选择符有哪些?优先级算法如何计算?

问题一:对多个选择器使用的优先级是怎么进行计算的?

对于不同类别的选择器,以以下原则进行排序:

1、在属性后面使用!important会覆盖页面内任何位置定义的元素样式。

2、作为style属性写在元素内的样式

3、id选择器

4、类选择器

5、标签选择器

6、通配符选择器

7、浏览器自定义或子元素集成父类的样式

将上面的稍微总结一下就是:

!important>行内样式>ID选择器>类选择器>标签>通配符>继承>浏览器默认属性

同一级别中后写的会覆盖先写的样式。

问题二:当不同类别的多个选择器混合使用个怎么计算优先级?

有一个简单的算法,设

a.内联样式表的权值为1000

b.ID选择器的权值为100

c.class类选择器的权值为10

d.HTML标签选择器的权值为1

[

我们可以把选择器中规则对应多加法,比较权值,如果权值相同那就后面的覆盖前面的。如图,div.test1.test3的权值是1+10+10=21,而.test1.test2.test3的权值是10+10+10=30,所以div会应用.test1.test2.test3变成绿色。

♥︎♥︎♥︎ iframe的优缺点?

iframe的优点:

1、iframe能够原封不动的把嵌入的网页展现出来;

2、如果有多个网页引用iframe,那么只需要修改iframe的内容,就可以实现调用每一个页面的更改,方便快捷;

3、网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe嵌套,可以增加代码的可重用;

4、如果遇到加载缓慢的第三方内容,如图标或广告,这些问题可以由iframe来解决;

iframe的缺点:

1、iframe会阻塞主页面的Onload事件;

2、iframe和主页面共享链接池,而浏览器对相同城的链接有限制,所以会影响页面的并行加载;

3、使用iframe之前需要考虑这两个缺点,如果需要使用iframe,最好是通过JavaScript;

4、动态给iframe添加src属性值,这样可以可以绕开以上两个问题

5、不利于seo

6、代码复杂,无法一下被搜索引擎索引到

7、iframe框架页面会增加服务器的http请求,对于大型网站不可取。

8、很多的移动设备无法完全显示框架,设备兼容性差。

♥︎♥︎♥︎ DPR?

设备像素比DPR(devicePixelRatio)是默认缩放为100%的情况下,设备像素和CSS像素的比值

举例:

iPhone6的设备宽度是375px/设备独立像素(或css像素),但是它一行有750个像素颗粒,dpr就是2。

了解几个概念

1.设备像素
设备像素(device pixel)又称物理像素(physical pixel),设备能控制显示的最小单位,我们可以把这些像素看作成显示器上一个个的点。

2.CSS 中的像素
CSS 中的像素是一个相对值,不是绝对值,因此1px 的 CSS 像素并不一定等于 1px 的物理像素。
需要注意的是,CSS 中的像素单位是抽象的,只是一种规范,最终的显示是取决于物理设备的。物理设备根据某种规则,决定该采用几个物理像素去显示 1px 的 CSS 像素,这个规则就是设备像素比。
CSS像素是一个抽象概念,设备无关像素,简称-“DIPS”,device-independent像素,主要使用在浏览器上,用来精确的度量(确定)Web页面上的内容。

3.PPI
像素密度(屏幕密度),即每英寸所拥有的像素数目(比如:上面iPhone 7的PPI是326),PPI数值越高,代表显示屏能够以越高的密度显示图像,画面的细节就会越丰富。

4.DPR:
设备像素比DPR(devicePixelRatio)是默认缩放为100%的情况下,设备像素和CSS像素的比值
dpr,也被成为device pixel ratio,即物理像素与逻辑像素的比,那也就不难理解:iphone6下dpr=2,iphone6+下dpr=3(考虑的是栅格化时的像素,并非真实的物理像素);
DPR = 设备像素 / CSS像素(某一方向上)
公式表示就是:window.devicePixelRatio = 物理像素(device pixel) / (设备无关像素/CSS像素/dips)

♥︎♥︎♥︎ 简述一下Sass,Less,请说明区别?

一. Sass 和 Less 的定义

sass 与 less都是一种动态样式语言,对css赋予了一些动态语言特性

二. 它们的区别大致有以下几种

//编译环境不一样
sass的安装需要Ruby环境的,是在服务端上处理的;
而less是需要引入less.js来处理Less代码输出css到浏览器,也可以在开发环节使用Less,然后编译成css文件,直接放到项目中

//变量符不一样
Less是@,而Scss是$
  
//输出设置
Less没有输出设置
Sass提供四种输出选项:
nested: 嵌套缩进的css代码
expanded:展示的多行css代码
compact:简洁格式的css代码
conpressed: 压缩后的css代码
Sass支持条件语句,可以使用if{}else{},for{}循环等等,而Less不支持

//引入外部CSS文件
scss引用的外部文件命名必须以开头,文件名如果以下划线开头的话,Sass会认为该文件是一个引用文件,不会将其编译为css文件

//Sass和Less的工具库不同
Sass有工具库 Compass
Less有UI组件库Bootstrap

♥︎♥︎♥︎♥︎重排(reflow)与重绘(repaint)

[重排(reflow)与重绘(repaint)]: “https://blog.csdn.net/lhz_333/article/details/125001060

♥︎♥︎♥︎♥︎BFC是什么东西

一、什么是BFC

1、BFC即 Block Formatting Contexts (块级格式化上下文), 是 W3C CSS2.1 规范中的一个概念。
2、BFC是指浏览器中创建了一个独立的渲染区域,并且拥有一套渲染规则,他决定了其子元素如何定位,以及与其他元素的相互关系和作用。

二、BFC的特点

1、具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。通俗一点来讲,可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。

三、BFC布局规则

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

四、哪些元素会产生BFC

1、根元素
2、float属性不为none
3、position为absolute或fixed
4、 display为inline-block, table-cell, table-caption, flex, inline-flex
5、 overflow不为visible

五、在布局中BFC的应用场景

(1)清除盒子垂直方向上外边距合并——盒子垂直方向的距离由margin决定。属于同一个BFC的两个相邻盒子垂直方向的margin会发生重叠。
解决方法:
根据属于同一个BFC的两个相邻盒子垂直方向的margin会发生重叠的性质,可以给其中一个盒子再包裹一个盒子父元素,并触发其BFC功能(例如添加overflow:hidden;)这样垂直方向的两个盒子就不在同一个BFC中了,因此也不会发生垂直外边距合并的问题了。
(2)在子元素设置成浮动元素的时候,会产生父元素高度塌陷的问题。
解决方法:
给父元素设置overflow:hidden;的时候会产生BFC
由于在计算BFC高度时,自然也会检测浮动的子盒子高度。所以当子盒子有高度但是浮动的时候,通过激发父盒子的BFC功能,会产生清除浮动的效果。

♥︎♥︎♥︎♥︎flex布局有哪些属性

1)Flex布局父容器属性

flex-direction / flex-wrap / flex-flow / justify-content / align-items / align-content

1》水平(主轴上)对齐方式:

justify-content:flex-start | flex-end | center | space-between | space-around;
flex-start(默认值):左对齐
flex-end:右对齐
center: 居中
space-between:两端对齐,子元素间隔相等。
space-around:子元素两侧的间隔相等。

2》十字交叉轴上对齐方式

align-items:flex-start | flex-end | center | baseline | stretch;
flex-start:上对齐。
flex-end:下对齐。
center:交叉轴对齐。
baseline: 第一行文字的基线对齐。
stretch(默认值):如果子元素未设置高度或设为auto,将占满整个容器。

3》项目排列方向

flex-direction:row | row-reverse | column | column-reverse;
row(默认值):从左1/2/3/...。
row-reverse:从左../3/2/1。
column:从上1/2/3/...。
column-reverse:从上../3/2/1。

4》换行方式

flex-wrap:nowrap(不换行) | wrap(向下换) | wrap-reverse(向上换);

5》flex-flow

flex-direction和flex-wrap的简写
flex-flow:row nowrap

6》多根轴线的对齐方式

align-content:flex-start | flex-end | center | space-between | space-around | stretch;
flex-start:上对齐。
flex-end:下对齐。
center:居中对齐。
space-between:两端对齐,间隔平均。
space-around:间隔相等。
stretch(默认值):占满。

2)Flex布局子元素属性

order/flex-grow/flex-shrink/flex-basis/flex/align-self

1》order属性(num)

order定义自身排列顺序。数值越小,越靠前,默认为0。-1/0/1/2/3/...

2》flex-grow属性(num)

flex-grow 定义自身放大比例,默认为0不放大。例如:1/2/1=25%:50%:25%

3》flex-shrink属性(num)

flex-shrink定义了空间不足时自身缩小比例,默认为1自动缩小,0不缩小。

4》flex-basis属性

flex-basis定义最小空间,默认值为auto,即自身的本来大小。

5》flex属性

flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。

6》align-self属性

align-self定义自身对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

♥︎♥︎♥︎♥︎单行或者多行文本溢出展示省略号的实现方法。

在页面布局时,经常会遇到文本内容超出盒子的情况,如果要实现单行文本的溢出显示省略号,大家应该都知道用text-overflow:ellipsis属性,当然还需要加宽度width属来兼容部分浏览,接下来,我们一起看看。

//单行溢出 ... 显示
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;

但是这个属性只支持单行文本的溢出显示省略号,如果我们要实现多行文本溢出显示省略号呢。接下来重点说一说多行文本溢出显示省略号,如下。

//多行溢出 ... 显示
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:3;
overflow:hidden;

‼️注:
1、-webkit-line-clamp用来限制在一个块元素显示的文本的行数。 为了实现该效果,它需要组合其他的WebKit属性。常见结合属性:
2、display: -webkit-box; 必须结合的属性 ,将对象作为弹性伸缩盒子模型显示 。
3、-webkit-box-orient 必须结合的属性 ,设置或检索伸缩盒对象的子元素的排列方式 。

♥︎♥︎♥︎♥︎position:sticky用过没,有啥效果?

[position:sticky 粘性定位的几种巧妙应用]: “https://www.bbsmax.com/A/qVdeoVOrJP/

♥︎♥︎♥︎♥︎说说你对GPU的理解,举例说明哪些元素能触发GPU硬件加速?

GPU: 图形处理器,用于处理图形有关的任务,用于渲染页面
在css中使用 transform: translateZ(0),可以开启GPU硬件加速

♥︎♥︎♥︎♥︎纯CSS方式实现CSS动画的暂停与播放

动画控制要完成的效果是:

  1. 页面 render 后,无任何操作,动画不会开始。只有当鼠标对元素进行 click ,触发元素的 :active 伪类效果的时候,动画才开始进行;
  2. 动画进行到任意时刻,鼠标停止点击,则动画停止;
  3. 重新对元素进行点击,动画继续从上一帧结束的状态开始
  4. 如果动画播放完,再点击不会重复播放,动画状态保留在动画的最后一帧
//html
<div></div>

//css
div {
    margin: 50px auto;
    width: 100px;
    height: 100px;
    background: #000;
    animation: move 1s linear;
    animation-fill-mode: forwards;
}
@keyframes move {
    100% {
        transform: translate(200px, 0) rotate(180deg);
    }
}
div {
    margin: 50px auto;
    width: 100px;
    height: 100px;
    background: #000;
    animation: move 1s linear;
    animation-fill-mode: forwards;
+   animation-play-state: paused; //添加了动画播放状态默认暂停
}

只有通过点击的时候,动画才会运行:

body:active div {
    animation-play-state: running;
}

♥︎♥︎♥︎♥︎使用CSS3动画代替JS动画的好处

导致JavaScript效率低的两大原因:操作DOM和使用页面动画。

通常我们会通过频繁的操作 DOM的css来实现视觉上的动画效果,导致js效率低的两个因素都包括在内了在频繁的操作DOM和css时,浏览器会不停的执行重排和重绘,在PC版本的浏览器中,因为浏览器可用的内存比较大,用户肉眼几乎看不见页面动画产生的repaint和reflow,所以工程师几乎无需过多的考虑动画带来的性能问题,但在移动设备上可大有不同,移动设备分配给内置浏览器的内存可没有PC版本的浏览器内存可观,目前对CSS3支持最好的莫过于webkit浏览器了,在webkit内核的浏览器,一是safari其次是chrome.

用CSS3动画替代js模拟动画的好处:

  • 不占用JS主线程;
  • 可以利用硬件加速;
  • 浏览器可对动画做优化(元素不可见时不动画减少对FPS影响)

CSS3动画提供了2D和3D以及常规动画属性接口,它可以工作在页面的任何一个元素的任意一个属性,CSS3的动画是利用C语言编写的,它是系统层面的动画。

采用js动画还是css3动画,需要开发者根据不同的需求做出不同的抉择,但应该遵循一个基本的原则是:如果你需要做2D动画,请勿必使用CSS3的transition或animation

CSS3动画与JavaScript模拟动画有以下区别:

  1. CSS 3D动画在js中无法实现
    CSS3的3D动画是CSS3中非常强大的功能,因为它的工作原理是在一个三维的空间里,因此js是无法模拟出像CSS3那样的3D动画
  2. CSS 2D矩阵动画效率高于js利用margin和left,top模拟的矩阵动画
    CSS3的2D动画是指是2D矩阵Transform变化,js当然是不能做变形动画的。就拿坐标动画来说,使用CSS3的transform做translateXY动画比js中的position left,position right快了近700mm!而且视觉上也比js动画流畅很多。
  3. CSS3其它常规动画属性的效率均低于js模拟的动画
    常规动画属性在这里是指:height,width,opacity,border-width,color

♥︎♥︎♥︎♥︎♥︎flex:1是什么

[css弹性盒flex-grow、flex-shrink、flex-basis详解]: “https://www.qetool.com/scripts/view/24368.html

♥︎♥︎♥︎♥︎♥︎移动端通用的1px边框的实现原理?

[7种方法实现移动端屏幕1px边框效果]: “https://blog.csdn.net/z591102/article/details/106404049/

如何搭建一个自己的博客平台(github+hexo)

如何搭建一个自己的博客平台(github+hexo)

一、下载node.js

直接到node.js官网下载就可以了

二、下载cnpm

//得进入到管理员权限中,要不然权限不够
sudo su
//输入自己的密码就可以了
//然后查看自己node是否安装成功
node -v
npm -v
//利用npm下载cnpm
npm install -g cnpm 
//查看cnpm是否安装成功
cnpm -v

三、下载hexo博客框架

//利用cnpm来下载hexo框架
cnpm install -g hexo-cli
//验证hexo框架是否下载
hexo -v

四、搭建hexo博客

1.创建一个文件夹blog

mkdir blog

2.进入blog文件夹并且初始化一hexo博客

//1.进入blog
cd blog
//2.初始化hexo博客
sudo hexo init

3.初始化完成之后,那么就可以启动hexo博客进行预览了

//启动hexo博客
hexo s
//浏览器打开 http://localhost:4000/ 端口号就可以了

4.新建一篇博客

//1.新建一篇博客
hexo n "我的第一篇hexo博客"
//2.检测博客是否被创建
//查看当前路径
pwd 
//进入博客目录文件夹
cd source/_posts
//查看文件
ls -l

5.编辑空白博客

//如果大家熟悉md语法,就可以在终端直接编写博客
vim +博客的名字
//例如 vim 我的第一篇hexo博客
//如果大家不熟悉md语法,也可以使用其他可编辑md文件的快捷编辑器。例如:Typora等

6.生成hexo博客

cd ../..
//清理一下缓存
hexo clean
//然后生成hexo博客
hexo g

7.查看hexo博客内容是否更新

//启动博客
hexo s 
//浏览器输入端口号
//博客更新成功
//到目前为止,hexo博客本地部署就完成了,后期我们可以通过本地来测试和修改我们即将发布远端的博客

五、将本地的博客部署到远端github上面

1.创建自己的github仓库

仓库地址必须是xxxx.github.io,而且xxxx必须是和你的用户名一样的,否则访问不了。以后我们只需要访问这个地址就可以访问我们的博客了

2.安装一个hexo的git插件

cnpm install --save hexo-deployer-git

3.!!!!重点,配置_config.yml

//查看当前目录下的文件
ls
//配置_config.yml文件
vim _config.yml

//在配置文件的最底部deploy配置
//repo:替换成你自己的git仓库地址
deploy:
type: git
repo:https://github.com/mengfeng/mengfeng.github.io.git
branch:master

4.配置完成之后就可以部署了

hexo d

5.这时候可能会出现下面的问题(没问题可以省略)

出现这个问题的原因应该是你使用了github密码去部署了,应该使用密钥去部署,具体请看下面的博主介绍

创建自己的密钥部署

出现上图的结果,那么说明你的hexo博客已经部署到了github上面去了,访问 xxxx.github.io 就可以了。

六、替换博客主题

因为默认的主题可能没有长在博主的审美上,所以我们换一个好看一点的,自己喜欢的博客主题也是可以滴。

1.你可以在HEXO官网选择你喜欢的博客主题

2.替换博客主题

所有的博客主题都可以在blog/themes里面替换

//下载博客源码到themes文件中,用我用的博客主题举例吧
git clone https://github.com/Shen-Yu/hexo-theme-ayer.git themes/ayer
//修改_config.yml配置 ayer为博客主题的名字
theme:ayer
//想要更新博客的话只需要
cd themes/ayer
git pull
//最后重新部署就可以了
hexo clean && hexo g && hexo d

后期修改博客配置请看git仓库的配置介绍

  • Copyrights © 2022-2023 alan_mf
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信