对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)当type
为number
时规定如下:
- 调用
obj
的valueOf
办法,如果为原始值,则返回,否则下一步; - 调用
obj
的toString
办法,后续同上; - 抛出
TypeError
异样。
(2)当type
为string
时规定如下:
- 调用
obj
的toString
办法,如果为原始值,则返回,否则下一步; - 调用
obj
的valueOf
办法,后续同上; - 抛出
TypeError
异样。
能够看出两者的次要区别在于调用toString
和valueOf
的先后顺序。默认状况下:
- 如果对象为 Date 对象,则
type
默认为string
; - 其余状况下,
type
默认为number
。
总结下面的规定,对于 Date 以外的对象,转换为根本类型的大略规定能够概括为一个函数:
var objToNumber = value => Number(value.valueOf().toString())objToNumber([]) === 0objToNumber({}) === NaN
而 JavaScript 中的隐式类型转换次要产生在+、-、*、/
以及==、>、<
这些运算符之间。而这些运算符只能操作根本类型值,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive
转换成根本类型,再进行操作。
以下是根本类型的值在不同操作符的状况下隐式转换的规定 (对于对象,其会被ToPrimitive
转换成根本类型,所以最终还是要利用根本类型转换规定):
+
操作符+
操作符的两边有至多一个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
-
、*
、\
操作符
NaN
也是一个数字
1 * '23' // 23 1 * false // 0 1 / 'aa' // NaN
- 对于
==
操作符
操作符两边的值都尽量转成number
:
3 == true // false, 3 转为number为3,true转为number为1'0' == false //true, '0'转为number为0,false转为number为0'0' == 0 // '0'转为number为0
- 对于
<
和>
比拟符
如果两边都是字符串,则比拟字母表程序:
'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申明的变量是不容许扭转指针的指向。
区别 | var | let | const |
---|---|---|---|
是否有块级作用域 | × | ✔️ | ✔️ |
是否存在变量晋升 | ✔️ | × | × |
是否增加全局属性 | ✔️ | × | × |
是否反复申明变量 | ✔️ | × | × |
是否存在暂时性死区 | × | ✔️ | ✔️ |
是否必须设置初始值 | × | × | ✔️ |
是否扭转指针指向 | ✔️ | ✔️ | × |
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
代码执行过程如下:
- 首先遇到定时器,将其退出到宏工作队列;
- 遇到Promise,首先执行外面的同步代码,打印出2,遇到resolve,将其退出到微工作队列,执行前面同步代码,打印出3;
- 继续执行script中的代码,打印出7和8,至此第一轮代码执行实现;
- 执行微工作队列中的代码,首先打印出4,如遇到Promise,执行其中的同步代码,打印出5,遇到定时器,将其退出到宏工作队列中,此时宏工作队列中有两个定时器;
- 执行宏工作队列中的代码,这里咱们须要留神是的第一个定时器的工夫为100ms,第二个定时器的工夫为10ms,所以先执行第二个定时器,打印出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
代码的执行过程如下:
- 首先进入
async1
,打印出async1 start
; - 之后遇到
async2
,进入async2
,遇到定时器timer2
,退出宏工作队列,之后打印async2
; - 因为
async2
阻塞了前面代码的执行,所以执行前面的定时器timer3
,将其退出宏工作队列,之后打印start
; - 而后执行async2前面的代码,打印出
async1 end
,遇到定时器timer1,将其退出宏工作队列; - 最初,宏工作队列有三个工作,先后顺序为
timer2
,timer3
,timer1
,没有微工作,所以间接所有的宏工作依照先进先出的准则执行。
为什么 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.1
和 0.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构造函数承受一个函数作为参数,该函数的两个参数别离是resolve
和reject
。
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作胜利 */){ resolve(value); } else { reject(error); }});
个别状况下都会应用new Promise()
来创立promise对象,然而也能够应用promise.resolve
和promise.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。上面就来看一下这些办法。
- 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
最初的状态,在执行完then
或catch
指定的回调函数当前,都会执行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操作符的实现步骤如下:
- 创立一个对象
- 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
- 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象增加属性和办法)
- 返回新的对象
所以,下面的第二、三步,箭头函数都是没有方法执行的。
Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?
1)Vue为什么要用vm.$set() 解决对象新增属性不能响应的问题
- Vue应用了Object.defineProperty实现双向数据绑定
- 在初始化实例时对属性执行 getter/setter 转化
- 属性必须在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 的实现原理是:
- 如果指标是数组,间接应用数组的 splice 办法触发相应式;
- 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
- 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决
defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法
说一下类组件和函数组件的区别?
1. 语法上的区别:函数式组件是一个纯函数,它是须要承受props参数并且返回一个React元素就能够了。类组件是须要继承React.Component的,而且class组件须要创立render并且返回React元素,语法上来讲更简单。2. 调用形式函数式组件能够间接调用,返回一个新的React元素;类组件在调用时是须要创立一个实例的,而后通过调用实例里的render办法来返回一个React元素。3. 状态治理函数式组件没有状态治理,类组件有状态治理。4. 应用场景类组件没有具体的要求。函数式组件个别是用在大型项目中来宰割大组件(函数式组件不必创立实例,所有更高效),个别状况下能用函数式组件就不必类组件,晋升效率。