对 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([]) === 0
objToNumber({}) === 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 // true
false > -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);
输入后果为:
2
3
7
8
4
5
6
1
代码执行过程如下:
- 首先遇到定时器,将其退出到宏工作队列;
- 遇到 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 start
async2
start
async1 end
timer2
timer3
timer1
代码的执行过程如下:
- 首先进入
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
。
javascript
let 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. 应用场景
类组件没有具体的要求。函数式组件个别是用在大型项目中来宰割大组件(函数式组件不必创立实例,所有更高效),个别状况下能用函数式组件就不必类组件,晋升效率。