对JSON的了解

JSON 是一种基于文本的轻量级的数据交换格局。它能够被任何的编程语言读取和作为数据格式来传递。

在我的项目开发中,应用 JSON 作为前后端数据交换的形式。在前端通过将一个合乎 JSON 格局的数据结构序列化为
JSON 字符串,而后将它传递到后端,后端通过 JSON 格局的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。

因为 JSON 的语法是基于 js 的,因而很容易将 JSON 和 js 中的对象弄混,然而应该留神的是 JSON 和 js 中的对象不是一回事,JSON 中对象格局更加严格,比如说在 JSON 中属性值不能为函数,不能呈现 NaN 这样的属性值等,因而大多数的 js 对象是不合乎 JSON 对象的格局的。

在 js 中提供了两个函数来实现 js 数据结构和 JSON 格局的转换解决,

  • JSON.stringify 函数,通过传入一个合乎 JSON 格局的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不合乎 JSON 格局,那么在序列化的时候会对这些值进行对应的非凡解决,使其符合规范。在前端向后端发送数据时,能够调用这个函数将数据对象转化为 JSON 格局的字符串。
  • JSON.parse() 函数,这个函数用来将 JSON 格局的字符串转换为一个 js 数据结构,如果传入的字符串不是规范的 JSON 格局的字符串的话,将会抛出谬误。当从后端接管到 JSON 格局的字符串时,能够通过这个办法来将其解析为一个 js 数据结构,以此来进行数据的拜访。

JavaScript 中如何进行隐式类型转换?

首先要介绍ToPrimitive办法,这是 JavaScript 中每个值隐含的自带的办法,用来将值 (无论是根本类型值还是对象)转换为根本类型值。如果值为根本类型,则间接返回值自身;如果值为对象,其看起来大略是这样:

/*** @obj 须要转换的对象* @type 冀望的后果类型*/ToPrimitive(obj,type)

type的值为number或者string

(1)当typenumber时规定如下:

  • 调用objvalueOf办法,如果为原始值,则返回,否则下一步;
  • 调用objtoString办法,后续同上;
  • 抛出TypeError 异样。

(2)当typestring时规定如下:

  • 调用objtoString办法,如果为原始值,则返回,否则下一步;
  • 调用objvalueOf办法,后续同上;
  • 抛出TypeError 异样。

能够看出两者的次要区别在于调用toStringvalueOf的先后顺序。默认状况下:

  • 如果对象为 Date 对象,则type默认为string
  • 其余状况下,type默认为number

总结下面的规定,对于 Date 以外的对象,转换为根本类型的大略规定能够概括为一个函数:

var objToNumber = value => Number(value.valueOf().toString())objToNumber([]) === 0objToNumber({}) === NaN

而 JavaScript 中的隐式类型转换次要产生在+、-、*、/以及==、>、<这些运算符之间。而这些运算符只能操作根本类型值,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive转换成根本类型,再进行操作。

以下是根本类型的值在不同操作符的状况下隐式转换的规定 (对于对象,其会被ToPrimitive转换成根本类型,所以最终还是要利用根本类型转换规定):

  1. +操作符 +操作符的两边有至多一个string类型变量时,两边的变量都会被隐式转换为字符串;其余状况下两边的变量都会被转换为数字。
1 + '23' // '123' 1 + false // 1  1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number '1' + false // '1false' false + true // 1
  1. -*\操作符

NaN也是一个数字

1 * '23' // 23 1 * false // 0 1 / 'aa' // NaN
  1. 对于==操作符

操作符两边的值都尽量转成number

3 == true // false, 3 转为number为3,true转为number为1'0' == false //true, '0'转为number为0,false转为number为0'0' == 0 // '0'转为number为0
  1. 对于<>比拟符

如果两边都是字符串,则比拟字母表程序:

'ca' < 'bd' // false'a' < 'b' // true

其余状况下,转换为数字再比拟:

'12' < 13 // truefalse > -1 // true

以上说的是根本类型的隐式转换,而对象会被ToPrimitive转换为根本类型再进行转换:

var a = {}a > 2 // false

其比照过程如下:

a.valueOf() // {}, 下面提到过,ToPrimitive默认type为number,所以先valueOf,后果还是个对象,下一步a.toString() // "[object Object]",当初是一个字符串了Number(a.toString()) // NaN,依据下面 < 和 > 操作符的规定,要转换成数字NaN > 2 //false,得出比拟后果

又比方:

var a = {name:'Jack'}var b = {age: 18}a + b // "[object Object][object Object]"

运算过程如下:

a.valueOf() // {},下面提到过,ToPrimitive默认type为number,所以先valueOf,后果还是个对象,下一步a.toString() // "[object Object]"b.valueOf() // 同理b.toString() // "[object Object]"a + b // "[object Object][object Object]"

宏工作和微工作别离有哪些

  • 微工作包含: promise 的回调、node 中的 process.nextTick 、对 Dom 变动监听的 MutationObserver。
  • 宏工作包含: script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。

let、const、var的区别

(1)块级作用域: 块作用域由 { }包含,let和const具备块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:

  • 内层变量可能笼罩外层变量
  • 用来计数的循环变量泄露为全局变量

(2)变量晋升: var存在变量晋升,let和const不存在变量晋升,即在变量只能在申明之后应用,否在会报错。

(3)给全局增加属性: 浏览器的全局对象是window,Node的全局对象是global。var申明的变量为全局变量,并且会将该变量增加为全局对象的属性,然而let和const不会。

(4)反复申明: var申明变量时,能够反复申明变量,后申明的同名变量会笼罩之前申明的遍历。const和let不容许反复申明变量。

(5)暂时性死区: 在应用let、const命令申明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。应用var申明的变量不存在暂时性死区。

(6)初始值设置: 在变量申明时,var 和 let 能够不必设置初始值。而const申明变量必须设置初始值。

(7)指针指向: let和const都是ES6新增的用于创立变量的语法。 let创立的变量是能够更改指针指向(能够从新赋值)。但const申明的变量是不容许扭转指针的指向。

区别varletconst
是否有块级作用域×✔️✔️
是否存在变量晋升✔️××
是否增加全局属性✔️××
是否反复申明变量✔️××
是否存在暂时性死区×✔️✔️
是否必须设置初始值××✔️
是否扭转指针指向✔️✔️×

Promise.allSettled

形容:等到所有promise都返回后果,就返回一个promise实例。

实现

Promise.allSettled = function(promises) {    return new Promise((resolve, reject) => {        if(Array.isArray(promises)) {            if(promises.length === 0) return resolve(promises);            let result = [];            let count = 0;            promises.forEach((item, index) => {                Promise.resolve(item).then(                    value => {                        count++;                        result[index] = {                            status: 'fulfilled',                            value: value                        };                        if(count === promises.length) resolve(result);                    },                     reason => {                        count++;                        result[index] = {                            status: 'rejected'.                            reason: reason                        };                        if(count === promises.length) resolve(result);                    }                );            });        }        else return reject(new TypeError("Argument is not iterable"));    });}

如何判断一个对象是否属于某个类?

  • 第一种形式,应用 instanceof 运算符来判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
  • 第二种形式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,然而这种形式不是很平安,因为 constructor 属性能够被改写。
  • 第三种形式,如果须要判断的是某个内置的援用类型的话,能够应用 Object.prototype.toString() 办法来打印对象的[[Class]] 属性来进行判断。

手写题:数组去重

Array.from(new Set([1, 1, 2, 2]))

代码输入后果

setTimeout(function () {  console.log(1);}, 100);new Promise(function (resolve) {  console.log(2);  resolve();  console.log(3);}).then(function () {  console.log(4);  new Promise((resove, reject) => {    console.log(5);    setTimeout(() =>  {      console.log(6);    }, 10);  })});console.log(7);console.log(8);

输入后果为:

23784561

代码执行过程如下:

  1. 首先遇到定时器,将其退出到宏工作队列;
  2. 遇到Promise,首先执行外面的同步代码,打印出2,遇到resolve,将其退出到微工作队列,执行前面同步代码,打印出3;
  3. 继续执行script中的代码,打印出7和8,至此第一轮代码执行实现;
  4. 执行微工作队列中的代码,首先打印出4,如遇到Promise,执行其中的同步代码,打印出5,遇到定时器,将其退出到宏工作队列中,此时宏工作队列中有两个定时器;
  5. 执行宏工作队列中的代码,这里咱们须要留神是的第一个定时器的工夫为100ms,第二个定时器的工夫为10ms,所以先执行第二个定时器,打印出6;
  6. 此时微工作队列为空,继续执行宏工作队列,打印出1。

做完这道题目,咱们就须要分外留神,每个定时器的工夫,并不是所有定时器的工夫都为0哦。

函数防抖

触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会从新计时。

简略版:函数外部反对应用 this 和 event 对象;

function debounce(func, wait) {    var timeout;    return function () {        var context = this;        var args = arguments;        clearTimeout(timeout)        timeout = setTimeout(function(){            func.apply(context, args)        }, wait);    }}

应用:

var node = document.getElementById('layout')function getUserAction(e) {    console.log(this, e)  // 别离打印:node 这个节点 和 MouseEvent    node.innerHTML = count++;};node.onmousemove = debounce(getUserAction, 1000)

最终版:除了反对 this 和 event 外,还反对以下性能:

  • 反对立刻执行;
  • 函数可能有返回值;
  • 反对勾销性能;
function debounce(func, wait, immediate) {    var timeout, result;    var debounced = function () {        var context = this;        var args = arguments;        if (timeout) clearTimeout(timeout);        if (immediate) {            // 如果曾经执行过,不再执行            var callNow = !timeout;            timeout = setTimeout(function(){                timeout = null;            }, wait)            if (callNow) result = func.apply(context, args)        } else {            timeout = setTimeout(function(){                func.apply(context, args)            }, wait);        }        return result;    };    debounced.cancel = function() {        clearTimeout(timeout);        timeout = null;    };    return debounced;}

应用:

var setUseAction = debounce(getUserAction, 10000, true);// 应用防抖node.onmousemove = setUseAction// 勾销防抖setUseAction.cancel()

Cookie有哪些字段,作用别离是什么

Cookie由以下字段组成:

  • Name:cookie的名称
  • Value:cookie的值,对于认证cookie,value值包含web服务器所提供的拜访令牌;
  • Size: cookie的大小
  • Path:能够拜访此cookie的页面门路。 比方domain是abc.com,path是/test,那么只有/test门路下的页面能够读取此cookie。
  • Secure: 指定是否应用HTTPS平安协定发送Cookie。应用HTTPS平安协定,能够爱护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该办法也可用于Web站点的身份甄别,即在HTTPS的连贯建设阶段,浏览器会查看Web网站的SSL证书的有效性。然而基于兼容性的起因(比方有些网站应用自签订的证书)在检测到SSL证书有效时,浏览器并不会立刻终止用户的连贯申请,而是显示平安危险信息,用户仍能够抉择持续拜访该站点。
  • Domain:能够拜访该cookie的域名,Cookie 机制并未遵循严格的同源策略,容许一个子域能够设置或获取其父域的 Cookie。当须要实现单点登录计划时,Cookie 的上述个性十分有用,然而也减少了 Cookie受攻打的危险,比方攻击者能够借此动员会话定置攻打。因此,浏览器禁止在 Domain 属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻打产生的范畴。
  • HTTP: 该字段蕴含HTTPOnly 属性 ,该属性用来设置cookie是否通过脚本来拜访,默认为空,即能够通过脚本拜访。在客户端是不能通过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能通过服务端来设置。该属性用于避免客户端脚本通过document.cookie属性拜访Cookie,有助于爱护Cookie不被跨站脚本攻打窃取或篡改。然而,HTTPOnly的利用仍存在局限性,一些浏览器能够阻止客户端脚本对Cookie的读操作,但容许写操作;此外大多数浏览器仍容许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头。
  • Expires/Max-size : 此cookie的超时工夫。若设置其值为一个工夫,那么当达到此工夫后,此cookie生效。不设置的话默认值是Session,意思是cookie会和session一起生效。当浏览器敞开(不是浏览器标签页,而是整个浏览器) 后,此cookie生效。

总结: 服务器端能够应用 Set-Cookie 的响应头部来配置 cookie 信息。一条cookie 包含了5个属性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 生效的工夫,domain 是域名、path是门路,domain 和 path 一起限度了 cookie 可能被哪些 url 拜访。secure 规定了 cookie 只能在确保安全的状况下传输,HttpOnly 规定了这个 cookie 只能被服务器拜访,不能应用 js 脚本拜访。

代码输入后果

async function async1() {  console.log("async1 start");  await async2();  console.log("async1 end");  setTimeout(() => {    console.log('timer1')  }, 0)}async function async2() {  setTimeout(() => {    console.log('timer2')  }, 0)  console.log("async2");}async1();setTimeout(() => {  console.log('timer3')}, 0)console.log("start")

输入后果如下:

async1 startasync2startasync1 endtimer2timer3timer1

代码的执行过程如下:

  1. 首先进入async1,打印出async1 start
  2. 之后遇到async2,进入async2,遇到定时器timer2,退出宏工作队列,之后打印async2
  3. 因为async2阻塞了前面代码的执行,所以执行前面的定时器timer3,将其退出宏工作队列,之后打印start
  4. 而后执行async2前面的代码,打印出async1 end,遇到定时器timer1,将其退出宏工作队列;
  5. 最初,宏工作队列有三个工作,先后顺序为timer2timer3timer1,没有微工作,所以间接所有的宏工作依照先进先出的准则执行。

为什么 0.1 + 0.2 != 0.3,请详述理由

因为 JS 采纳 IEEE 754 双精度版本(64位),并且只有采纳 IEEE 754 的语言都有该问题。

咱们都晓得计算机示意十进制是采纳二进制示意的,所以 0.1 在二进制示意为

// (0011) 示意循环0.1 = 2^-4 * 1.10011(0011)

那么如何失去这个二进制的呢,咱们能够来演算下

小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且失去的第一位为最高位。所以咱们得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也根本如上所示,只须要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)

回来持续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.10.2 都是有限循环的二进制了,所以在小数位开端处须要判断是否进位(就和十进制的四舍五入一样)。

所以 2^-4 * 1.10011...001 进位后就变成了 2^-4 * 1.10011(0011 * 12次)010 。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11次)0100 , 这个值算成十进制就是 0.30000000000000004

上面说一下原生解决办法,如下代码所示

parseFloat((0.1 + 0.2).toFixed(10))

Promise的根本用法

(1)创立Promise对象

Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已胜利)和rejected(已失败)。

Promise构造函数承受一个函数作为参数,该函数的两个参数别离是resolvereject

const promise = new Promise(function(resolve, reject) {  // ... some code  if (/* 异步操作胜利 */){    resolve(value);  } else {    reject(error);  }});

个别状况下都会应用new Promise()来创立promise对象,然而也能够应用promise.resolvepromise.reject这两个办法:

  • Promise.resolve

Promise.resolve(value)的返回值也是一个promise对象,能够对返回值进行.then调用,代码如下:

Promise.resolve(11).then(function(value){  console.log(value); // 打印出11});

resolve(11)代码中,会让promise对象进入确定(resolve状态),并将参数11传递给前面的then所指定的onFulfilled 函数;

创立promise对象能够应用new Promise的模式创建对象,也能够应用Promise.resolve(value)的模式创立promise对象;

  • Promise.reject

Promise.reject 也是new Promise的快捷模式,也创立一个promise对象。代码如下:

Promise.reject(new Error(“我错了,请原谅俺!!”));

就是上面的代码new Promise的简略模式:

new Promise(function(resolve,reject){   reject(new Error("我错了!"));});

上面是应用resolve办法和reject办法:

function testPromise(ready) {  return new Promise(function(resolve,reject){    if(ready) {      resolve("hello world");    }else {      reject("No thanks");    }  });};// 办法调用testPromise(true).then(function(msg){  console.log(msg);},function(error){  console.log(error);});

下面的代码的含意是给testPromise办法传递一个参数,返回一个promise对象,如果为true的话,那么调用promise对象中的resolve()办法,并且把其中的参数传递给前面的then第一个函数内,因而打印出 “hello world”, 如果为false的话,会调用promise对象中的reject()办法,则会进入then的第二个函数内,会打印No thanks

(2)Promise办法

Promise有五个罕用的办法:then()、catch()、all()、race()、finally。上面就来看一下这些办法。

  1. then()

当Promise执行的内容合乎胜利条件时,调用resolve函数,失败就调用reject函数。Promise创立完了,那该如何调用呢?

promise.then(function(value) {  // success}, function(error) {  // failure});

then办法能够承受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中第二个参数能够省略。 then办法返回的是一个新的Promise实例(不是原来那个Promise实例)。因而能够采纳链式写法,即then办法前面再调用另一个then办法。

当要写有程序的异步事件时,须要串行时,能够这样写:

let promise = new Promise((resolve,reject)=>{    ajax('first').success(function(res){        resolve(res);    })})promise.then(res=>{    return new Promise((resovle,reject)=>{        ajax('second').success(function(res){            resolve(res)        })    })}).then(res=>{    return new Promise((resovle,reject)=>{        ajax('second').success(function(res){            resolve(res)        })    })}).then(res=>{})

那当要写的事件没有程序或者关系时,还如何写呢?能够应用all 办法来解决。

2. catch()

Promise对象除了有then办法,还有一个catch办法,该办法相当于then办法的第二个参数,指向reject的回调函数。不过catch办法还有一个作用,就是在执行resolve回调函数时,如果呈现谬误,抛出异样,不会进行运行,而是进入catch办法中。

p.then((data) => {     console.log('resolved',data);},(err) => {     console.log('rejected',err);     }); p.then((data) => {    console.log('resolved',data);}).catch((err) => {    console.log('rejected',err);});

3. all()

all办法能够实现并行任务, 它接管一个数组,数组的每一项都是一个promise对象。当数组中所有的promise的状态都达到resolved的时候,all办法的状态就会变成resolved,如果有一个状态变成了rejected,那么all办法的状态就会变成rejected

javascriptlet promise1 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(1);    },2000)});let promise2 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(2);    },1000)});let promise3 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(3);    },3000)});Promise.all([promise1,promise2,promise3]).then(res=>{    console.log(res);    //后果为:[1,2,3] })

调用all办法时的后果胜利的时候是回调函数的参数也是一个数组,这个数组按程序保留着每一个promise对象resolve执行时的值。

(4)race()

race办法和all一样,承受的参数是一个每项都是promise的数组,然而与all不同的是,当最先执行完的事件执行完之后,就间接返回该promise对象的值。如果第一个promise对象状态变成resolved,那本身的状态变成了resolved;反之第一个promise变成rejected,那本身状态就会变成rejected

let promise1 = new Promise((resolve,reject)=>{    setTimeout(()=>{       reject(1);    },2000)});let promise2 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(2);    },1000)});let promise3 = new Promise((resolve,reject)=>{    setTimeout(()=>{       resolve(3);    },3000)});Promise.race([promise1,promise2,promise3]).then(res=>{    console.log(res);    //后果:2},rej=>{    console.log(rej)};)

那么race办法有什么理论作用呢?当要做一件事,超过多长时间就不做了,能够用这个办法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

5. finally()

finally办法用于指定不论 Promise 对象最初状态如何,都会执行的操作。该办法是 ES2018 引入规范的。

promise.then(result => {···}).catch(error => {···}).finally(() => {···});

下面代码中,不论promise最初的状态,在执行完thencatch指定的回调函数当前,都会执行finally办法指定的回调函数。

上面是一个例子,服务器应用 Promise 解决申请,而后应用finally办法关掉服务器。

server.listen(port)  .then(function () {    // ...  })  .finally(server.stop);

finally办法的回调函数不承受任何参数,这意味着没有方法晓得,后面的 Promise 状态到底是fulfilled还是rejected。这表明,finally办法外面的操作,应该是与状态无关的,不依赖于 Promise 的执行后果。finally实质上是then办法的特例:

promise.finally(() => {  // 语句});// 等同于promise.then(  result => {    // 语句    return result;  },  error => {    // 语句    throw error;  });

下面代码中,如果不应用finally办法,同样的语句须要为胜利和失败两种状况各写一次。有了finally办法,则只须要写一次。

两栏布局的实现

个别两栏布局指的是右边一栏宽度固定,左边一栏宽度自适应,两栏布局的具体实现:

  • 利用浮动,将右边元素宽度设置为200px,并且设置向左浮动。将左边元素的margin-left设置为200px,宽度设置为auto(默认为auto,撑满整个父元素)。
.outer {  height: 100px;}.left {  float: left;  width: 200px;  background: tomato;}.right {  margin-left: 200px;  width: auto;  background: gold;}
  • 利用浮动,左侧元素设置固定大小,并左浮动,右侧元素设置overflow: hidden; 这样左边就触发了BFC,BFC的区域不会与浮动元素产生重叠,所以两侧就不会产生重叠。
.left{     width: 100px;     height: 200px;     background: red;     float: left; } .right{     height: 300px;     background: blue;     overflow: hidden; }
  • 利用flex布局,将右边元素设置为固定宽度200px,将左边的元素设置为flex:1。
.outer {  display: flex;  height: 100px;}.left {  width: 200px;  background: tomato;}.right {  flex: 1;  background: gold;}
  • 利用相对定位,将父级元素设置为绝对定位。右边元素设置为absolute定位,并且宽度设置为200px。将左边元素的margin-left的值设置为200px。
.outer {  position: relative;  height: 100px;}.left {  position: absolute;  width: 200px;  height: 100px;  background: tomato;}.right {  margin-left: 200px;  background: gold;}
  • 利用相对定位,将父级元素设置为绝对定位。右边元素宽度设置为200px,左边元素设置为相对定位,右边定位为200px,其余方向定位为0。
.outer {  position: relative;  height: 100px;}.left {  width: 200px;  background: tomato;}.right {  position: absolute;  top: 0;  right: 0;  bottom: 0;  left: 200px;  background: gold;}

深/浅拷贝

首先判断数据类型是否为对象,如果是对象(数组|对象),则递归(深/浅拷贝),否则间接拷贝。

function isObject(obj) {    return typeof obj === "object" && obj !== null;}

这个函数只能判断 obj 是否是对象,无奈判断其具体是数组还是对象。

如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有本人的this指向,更不能够应用arguments参数,所以不能New一个箭头函数。

new操作符的实现步骤如下:

  1. 创立一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
  3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象增加属性和办法)
  4. 返回新的对象

所以,下面的第二、三步,箭头函数都是没有方法执行的。

Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?

1)Vue为什么要用vm.$set() 解决对象新增属性不能响应的问题

  1. Vue应用了Object.defineProperty实现双向数据绑定
  2. 在初始化实例时对属性执行 getter/setter 转化
  3. 属性必须在data对象上存在能力让Vue将它转换为响应式的(这也就造成了Vue无奈检测到对象属性的增加或删除)

所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)

2)接下来咱们看看框架自身是如何实现的呢?

Vue 源码地位:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {  // target 为数组    if (Array.isArray(target) && isValidArrayIndex(key)) {    // 批改数组的长度, 防止索引>数组长度导致splcie()执行有误    target.length = Math.max(target.length, key)    // 利用数组的splice变异办法触发响应式      target.splice(key, 1, val)    return val  }  // key 曾经存在,间接批改属性值    if (key in target && !(key in Object.prototype)) {    target[key] = val    return val  }  const ob = (target: any).__ob__  // target 自身就不是响应式数据, 间接赋值  if (!ob) {    target[key] = val    return val  }  // 对属性进行响应式解决  defineReactive(ob.value, key, val)  ob.dep.notify()  return val}

咱们浏览以上源码可知,vm.$set 的实现原理是:

  1. 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  2. 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
  3. 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决
defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法

说一下类组件和函数组件的区别?

1. 语法上的区别:函数式组件是一个纯函数,它是须要承受props参数并且返回一个React元素就能够了。类组件是须要继承React.Component的,而且class组件须要创立render并且返回React元素,语法上来讲更简单。2. 调用形式函数式组件能够间接调用,返回一个新的React元素;类组件在调用时是须要创立一个实例的,而后通过调用实例里的render办法来返回一个React元素。3. 状态治理函数式组件没有状态治理,类组件有状态治理。4. 应用场景类组件没有具体的要求。函数式组件个别是用在大型项目中来宰割大组件(函数式组件不必创立实例,所有更高效),个别状况下能用函数式组件就不必类组件,晋升效率。