daytoywhy / cxx-blog Goto Github PK
View Code? Open in Web Editor NEW个人博客,用于经验分享和记录一些工作上遇到的问题
个人博客,用于经验分享和记录一些工作上遇到的问题
这种方式是通过让子类的原型(prototype)是父类的实例(new Father),这样子类就可以使用父类所有的属性和方法
//父类型
function Father(name,age){
this.name = name
this.age = age
this.nums = [1,2,3]
this.setName = function () {}
}
Father.prototype.setAge = function () {}
//子类型
function Son(score) {
this.score = score
this.setScore = function () {}
}
//继承的方式
Son.prototype = new Father()
let s1 = new Son(100)
let s2 = new Son(60)
console.log(s1,s2)
通过让子类的原型指向父类的实例,这样子类的实例就可以通过__proto__访问到Son.prototype也就是父类的实例,这样就可以访问父类的方法和属性,然后再通过__proto__指向父类的prototype也就是父类的原型,这样就可以访问父类原型上的方法。这种方式让父类的公有、私有的属性和方法都变成了子类的公有属性和方法
这样就会产生一个问题,我们知道javascript的数据类型有基本类型和引用类型,操作基本数据类型的就是操作值本身,而操作引用数据类型实际上是操作引用的地址,通过原型链方式继承所有的子类实例都会共享父类的属性和方法,当子类实例操作父类引用数据时,就会影响其他子类实例
s1.nums.push(4)
console.log(s1.nums , s2.nums) //都是输出[1,2,3,4]
console.log(s1.__proto__ === s2.__proto__) // true
console.log(s1.__proto__.__proto__ === s2.__proto__.__proto__) // true
另外需要注意,在子类原型上想要新增属性或者方法或者想重写父类的属性和方法,一定要放在原型替换之后
Son.prototype.SetValue = function () {} //错误写法,原型替换父类实例会覆盖这一块内容
Son.prototype = new Father()
Son.prototype.SetValue = function () {} //正确写法
特点:
缺点:
这种方式是在子类的构造函数中通过call()调用父类型的构造函数来实现
function Father(name,age) {
this.age = age
this.name = name
this.setName = function () {}
}
Father.prototype.setAge = function () {}
function Son(name,age,socre){
Father.call(name,age) //这里相当于调用this.Father(name,age)
/*this.name = name
this.age = age*/
this.score = score
}
let s1 = new Son('xiaoming' , '25' , '100')
这种方式只能实现部分继承,如果父类原型上还有方法和属性,子类实例是无法拿到的
console.log(s1.setAge())//Uncaught TypeError: s1.setAge is not a function
特点:
缺点:
这种方式是结合方式一和方式二:通过调用父类函数,继承父类的属性和方法并保留可以向父类传参的优点,再让子类的原型指向父类的实例,实现函数的复用
//父类型
function Father(name,age){
this.name = name
this.age = age
this.nums = [1,2,3]
this.setName = function () {}
}
Father.prototype.setAge = function () {}
//子类型
function Son(name,age,score) {
Father.call(this, name, age) //利用方式二
this.score = score
this.setScore = function () {}
}
//继承的方式
Son.prototype = new Father() //利用方式一
Son.prototype.constructor = Son //组合式继承也需要修复构造函数指向问题
Son.prototype.setValue = function () {}
let s1 = new Son('xiaoming' , '25' , '100')
let s1 = new Son('xiaofang' , '24' , '150')
console.log(s1,s2)
特点:
缺点:
let Father = {
a: 1
}
let Son = Object.create(Father)
console.log(Son)
特点:
缺点:
这种方式只是在原型继承的基础上做了增强
let Father = {
name:'xiong'
}
function extand (0){
const Son = Object.create(o)
Son.prototype.setName = function() {}
return Son
}
let s1 = extand(Father)
console.log(s1)
特点:
缺点:
这种方式是通过借助原型可以基于已有的对象来创建对象,var B = Object.create(A)以A对象为原型,生成了B对象。B继承了A的所有属性和方法。
//父类型
function Father(name,age){
this.name = name
this.age = age
this.nums = [1,2,3]
this.setName = function () {}
}
Father.prototype.setAge = function () {}
//子类型
function Son(name,age,score) {
Father.call(this, name, age)
this.score = score
this.setScore = function () {}
}
//继承的方式
Son.prototype = Object.create(Father.prototype) // 关键代码
Son.prototype.constructor = Son //组合式继承也需要修复构造函数指向问题
Son.prototype.setValue = function () {}
let s1 = new Son('xiaoming' , '25' , '100')
let s1 = new Son('xiaofang' , '24' , '150')
console.log(s1,s2)
特点:
ES6中引入class关键字,并通过extends来实现继承
class Father {
constructor(name,age){
this.name = name
this.age = age
}
setAge(){}
}
class Son extends Father{
constructor(name,age,score){
super(name,age) //这里要使用super调用父类的构造函数
this.score = socre
}
setAge(){}
}
特点:
缺点:
说到Promise,我们很容易联想到它可以解决回调地狱的问题,但它是通过什么方式来解决回调地狱问题的呢,请带着这样的疑惑,让我们一探究竟
在mdn中是这样定义Promise的:
一个
Promise
对象代表一个在这个 promise 被创建出来时不一定已知值的代理。它让你能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。
它必定处于以下三种状态之一:
因为 Promise.prototype.then()
、Promise.prototype.catch()
方法返回的都是Promise,所以它们可以被链式调用
我们可以用 Promise.prototype.then()
、Promise.prototype.catch()
和 Promise.prototype.finally()
这些方法将进一步的操作与一个变为已敲定状态的 promise 关联起来。
例如在.then()
方法中需要两个函数,一个是参数作为处理已兑现状态的回调函数,而第二个参数则作为处理已拒绝状态的回调函数,每个.then()方法返回的都是一个新的Promise对象,所以可以链式调用,如下
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 300);
});
myPromise
.then(handleResolvedA, handleRejectedA)
.then(handleResolvedB, handleRejectedB)
.then(handleResolvedC, handleRejectedC);
当 .then() 中缺少能够返回 promise 对象的函数时,链式调用就直接继续进行下一环操作。因此,链式调用可以在最后一个 .catch() 之前把所有的处理已拒绝状态的回调函数都省略掉。
过早地处理变为已拒绝状态的 promise 会对之后 promise 的链式调用造成影响。不过有时候我们因为需要马上处理一个错误也只能这样做。例如,外面必须抛出某种类型的错误以在链式调用中传递错误状态。另一方面,在没有迫切需要的情况下,可以在最后一个 .catch() 语句时再进行错误处理,这种做法更加简单。.catch() 其实只是没有给处理已兑现状态的回调函数预留参数位置的 .then() 而已。
myPromise
.then(handleResolvedA)
.then(handleResolvedB)
.then(handleResolvedC)
.catch(handleRejectedAny);
最后关于.finally()方法,一个Promise对象最终都会走进这个方法,工作中我发现有些实习同学很喜欢将原本放在成功回调里的一些方法放在.finnaly中去执行,这样是不对,无论Promise是成功还是失败的状态都会进入到.finally()方法中。一般在.finally()方法里我们可以执行像loading状态的取消这一类操作
const PENDING = 'pending'
const FULFILED = 'fulfiled'
const REJECTED = 'rejected'
function myPromise(fn){
var self = this
this.state = PENDING
this.value = null
this.resolveCallback = []
this.rejectCallback = []
try {
function resolve(value){
if(value instanceof myPromise){
return value.then(resolve,reject)
}
setTimeout(()=>{
if(self.state === PENDING){
self.state = FULFILED
self.value = value
self.resolveCallback.forEach(fn =>{
fn(value)
})
}
},0)
}
} catch (error) {
console.log(error,'error');
}
function reject(value){
setTimeout(()=>{
if(self.state === PENDING){
self.state = REJECTED
self.value = value
self.rejectCallback.forEach(fn => {
fn(value)
})
}
},0)
}
try {
fn(resolve,reject)
} catch (error) {
reject(error)
}
}
myPromise.prototype.then = function(onResolved,onRejected){
onResolved =
typeof onResolved === 'function'
? onResolved
: function(value){
return value
}
onRejected =
typeof onRejected === 'function'
? onRejected
: function(error){
throw error
}
if(this.state === PENDING){
this.resolveCallback.push(onResolved)
this.rejectCallback.push(onRejected)
}
if(this.state === FULFILED){
onResolved(this.value)
}
if(this.state === REJECTED){
onRejected(this.value)
}
}
Promise.all()
方法接收一个数组类型的参数,数组里面一般是包含Promise的值,如果是非Promise的值,在执行过程中会忽略该参数,但会放在最终返回的数组上(Promise完成的话)
只有数组中所有Promise状态都是成功时,Promise.all()方法才会返回成功的状态,一旦数组中有一个失败的状态出现,则会立即返回失败的状态
以下是成功状态的返回
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // Array [3, 42, "foo"]
})
以下是失败状态的返回
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(reject, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).catch((err) => {
console.log(err); // foo
})
Promise.all = function(array){
return new Promise((resolve,reject)=>{
if(!array || array.length == 0){ resolve([]) }
else{
let count = 0,reuslt = [];
for(let i = 0; i< array.length; i++){
Promise.resolve(array[i]).then(
data => result[i] = data
if(++count === array.length){
resolve(result)
},
err => {
reject(err)
return
}
)
}
}
})
}
Promise.race(iterable)
方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // two
// Both resolve, but promise2 is faster
});
Promise.race = function(array){
return new Promise((resolve,reject)=>{
for(let i of array){
Promise.resolve(i).then(
data => resolve(data)
).catch(
err => reject(err)
)
}
})
}
警告:
Promise.any()
方法依然是试验性的,尚未被所有的浏览器完全支持
该方法的入参也是一个包含Promise的数组,但Promise.any()
与Promise.all
方法相反,当数组中有一个Promise的状态是成功它会立即返回成功的状态,只有当数组中所有的Promise都是失败状态时,它才会返回失败。
const pErr = new Promise((resolve, reject) => {
reject("总是失败");
});
const pSlow = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "最终完成");
});
const pFast = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "很快完成");
});
Promise.any([pErr, pSlow, pFast]).then((value) => {
console.log(value);
// pFast fulfils first
})
Set对象是值的集合,你可以按照插入的顺序迭代它的元素。Set
中的元素只会出现一次,即 Set
中的元素是唯一的。
由于Set
中的元素只会出现一次,所以Set
中的值总是唯一的,此外NaN
和 undefined
都可以被存储在 Set
中,NaN
之间被视为相同的值(NaN 被认为是相同的,尽管 NaN !== NaN
)
工作中我们经常使用Set来对数据进行去重,举个例子
let set = new Set([1,1,2,2,3,3,4])
console.log(set) // [1,2,3,4]
需要注意的是,向Set加入值时,是不会发生类型的转换的,所以5和'5'是两个不同的值,这和===
的比较有些类似,不同的是NaN === NaN
为false,而在Set中他认为NaN都是一个值
let set = new Set([5,'5'])
console.log(set) // [5,'5']
let set2 = new Set([NaN,NaN])
console.log(set2) // [NaN]
接下来让我们看看Set实例的属性和方法都有哪些
let set = new Set([1,2,3,4,5])
console.log(set.size) // 5 类似于数组的.length方法
let set = new Set([])
set.add(1)
console.log(set) // [1]
set.has(1) // true
set.has(2) // false
set.delete(1) // []
Set默认可遍历,可以使用map、filter等方法,所以我们借助Set可以很容易实现数据的交集、并集、差集
let s1 = new Set([1,2,3])
let s2 = new Set([2,3,4])
let intersect = s1.filter(item => s2.has(item)) // 2,3
let union = new Set([...s1,...s2]) // 1,2,3,4
let difference = s1.filter(item => !s2.has(item)) // 1
WeakSet 对象允许你将弱保持对象存储在一个集合中
它和Set对象的主要区别有:
WeakSet
只能是对象的集合,而不能像Set
那样,是任何类型的任何值WeakSet
持弱引用:集合中对象的引用为弱引用。如果没有其他的对WeakSet
中对象的引用,那么这些对象会被当成垃圾回收掉,所以,WeakSet 对象里有多少个成员元素,取决于垃圾回收机制有没有运行,运行前后成员个数可能不一致,遍历结束之后,有的成员可能取不到了(被垃圾回收了),WeakSet
对象是无法被遍历的(ES6 规定 WeakSet
不可遍历),也没有办法拿到它包含的所有元素WeakSet实例的属性和方法
let set = new WeakSet()
set.add(1) // 报错 caught TypeError: Invalid value used in weak set
// 只能储存对象
set.add(window)
set.has(window) // true
set.delete(window)
set.has(window) // false
集合 与 字典 的区别:
let map = new Map()
let obj = { a: 1}
map.set(obj,'content') // map添加值的方法是set而不是add
map.get(obj) // content
map.get(o) // undefined 读取一个未知的的键,返回的是undefined
map.delete(obj)
map.has(obj) // false
需要注意的是,只有对同一对象的引用,Map才将它视为同一个键
let map = new Map()
map.set([1],'content')
map.get([1]) // undefined
map.has([1]) // false
上述例子中,虽然都是获取[1]
,但是该数组对象的引用地址不同,所以Map没有将其视为是同一个键
WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
要注意的是WeakMap
弱引用的只是键名,而不是键值。键值依然是正常引用。
WeakMap
中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap
的 key 是不可枚举的
bind()
方法会创建一个新的函数,在bind()被调用时,新函数的this会指定为bind()传入的的第一个参数,剩余的参数将作为新函数的参数,供新函数调用时使用
function.bind(thisArg,[arg1,arg2,...])
bind方法会返回一个原函数的拷贝,并拥有指定的this值和初始参数,也就是说使用bind方法不会立即执行原函数
num = 99
const obj = {
num:1,
func:function(){
return this.num
}
}
let s1 = obj.func
console.log(s1()); // 99
console.log(obj.func()); // 1
let fn = obj.func
let newFn = fn.bind(obj)
console.log(newFn()); // 1
从上面的示例可以看出使用bind方法后,原函数fn的this指向了obj对象,所以返回的是1
apply()
方法调用一个具有给定this值的函数,以及一个数组(或者一个类数组对象)的形式提供参数
apply(thisArg)
apply(thisArg, argsArray)
thisArg
: 在func
函数运行时使用的this值,需要注意的是this值可能不是该方法看到的实际值,在非严格模式下,指定null或者undefined时会自动转化为指向全局对象argsArray
: 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func
函数。如果该参数的值为null
、undefined
,则表示不需要传入任何参数它会返回调用指定this值和参数的函数的结果,也就是说调用apply方法会立即执行原函数
const array = ['a', 'b'];
const elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]
call()
方法使用一个指定的this值和一个或者多个参数来调用函数
备注:该方法的作用与apply方法类似,不同是传参形式的区别,
call()
方法接受的是一个参数列表,而apply()
接受的是一个包含多个参数的数组
function.call(thisArg, arg1, arg2, ...)
使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。也就是说使用call方法也会立即执行原函数
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
}
var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);
一般我们可以使用call方法来实现对象的继承,例如通过构造函数实现继承
new在mdn中的定义:
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
光看定义的话可能有点难以理解,我们可以先来看看new关键字都帮我做了哪些事
function Father(name,age){
this.name = name
this.age = age
}
Father.prototype.sayHello = function (){
console.log('hello world')
}
let s1 = new Father('jony',25)
console.log(s1.name) // jony
console.log(s1.age) // 25
console.log(s1.sayHello) // hello world
从上述例子中,我们可以看出经过new操作后,s1实例可以
其实在使用new关键字后,他会帮我们完成以下操作
我们可以通过自定义函数模拟一下new关键字的操作
function newObject(){
let obj = new Object()
Constructor = [].shift.call(arguments) // 去除arguments中的第一个参数
obj.__proto__ = Constructor.prototype
Constructor.apply(obj,arguments) //执行构造函数,给obj对象添加上属性和方法
return obj
}
上述自定义函数newObject是基于函数没有返回对象实现的,假设函数有返回对象
function Father(name,age){
this.name = name
this.age = age
return {
width:99
}
}
let s1 = new Father('jony',25)
console.log(s1.name) // undefined
console.log(s1.age) // undefined
console.log(s1.width) // 99
可以看出如果构造函数返回了一个对象,实例s1就只能访问返回的对象中的属性和方法
还要注意的是,如果构造函数返回的不是一个对象,而是一个值类型时
function Father(name,age){
this.name = name
this.age = age
return 'hello world'
}
let s1 = new Father('jony',25)
console.log(s1.name) // jony
console.log(s1.age) // 25
console.log(s1.width) // undefined
如果构造函数的返回值是一个值类型,就相当于没有返回值来处理
基于以上两种情况,我们再对newObject函数进行下改造
function newObject(){
let obj = new Object()
Constructor = [].shift.call(arguments)
obj.__proto__ = Constructor.prototype
const result = Constructor.apply(obj,arguments)
return typeof result == 'object' ? result : obj //判断构造函数返回值的类型,如果是对象,则return这个返回值,如果是值类型就返回创建的对象obj
}
function control(limit, requestList) {
return new Promise(async (resolve) => {
let length = requestList.length // 请求数组的长度
let pool = [] // 请求池
let result = [] // 所有的结果
let count = 0 // 已完成的数量
for (let i = 0; i < length; i++) {
let task = requestList[i]
const handler = (res) => {
pool.splice(pool.indexOf(task), 1)
result[i] = res
count++
if (count === length) {
resolve(result)
}
}
task.then(
res => handler(res),
err => handler(err)
)
pool.push(task)
if (pool.length >= limit) {
await Promise.race(pool)
}
}
})
}
let stack = [
new Promise((resolve)=>{
resolve(1)
}),
new Promise((resolve)=>{
resolve(2)
}),
new Promise((resolve)=>{
resolve(3)
}),
new Promise((resolve)=>{
resolve(4)
}),
new Promise((resolve)=>{
resolve(5)
}),
new Promise((resolve)=>{
resolve(6)
}),
new Promise((resolve)=>{
resolve(7)
})
]
control2(2,stack).then(res => console.log(res,'数据'))
//打印结果应该是
块格式化上下文(Block Formatting Context,简称BFC)是 Web 页面的可视 CSS 渲染的一部分,是块级盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。通俗的讲,BFC就是一个容器,用于管理块级元素
absolute
或者fixed
)table-cell
|table-caption
|inline-block
|inline-flex
|flex
visible
或者clip
的块元素,可以是hidden
|auto
|scroll
只有同属于一个BFC时,两个元素才有可能发生垂直margin的重叠,这个包括相邻元素或者嵌套元素,只要他们之间没有阻挡(比如边框、非空内容、padding等)就会发生margin重叠。
<style>
p{
color: #fff;
background: #888;
width: 200px;
line-height: 100px;
text-align:center;
margin: 100px;
}
</style>
<body>
<p>ABC</p>
<p>abc</p>
</body>
上面两个元素的距离应该有200px,但实际上只有100px,这里发生了margin的重叠,那么如何解决呢,其实很简单,只需给其中的一个p元素包裹一个div,他让形成一个新的BFC区域
<style>
p{
color: #fff;
background: #888;
width: 200px;
line-height: 100px;
text-align:center;
margin: 100px;
}
div{
overflow:hidden
}
</style>
<body>
<p>ABC</p>
<div>
<p>abc</p>
</div>
</body>
<style>
.box{
width:100px;
height:100px;
background:#ccc;
}
.wrap {
background:yellow;
}
.wrap h1{
background:pink;
margin:40px;
}
</style>
<body>
<div class="box">box</div>
<div class="wrap">
<h1>h1</h1>
</div>
</body>
上述例子中,wrap元素与h1元素应该有上下40px的margin,实际上没有,反而box与wrap元素有了40px的距离,这种情况的解决方法和之前的一样,也是让wrap形成一个新的BFC区域就能完美解决啦
<style>
.box{
width:100px;
height:100px;
background:#ccc;
}
.wrap {
background:yellow;
overflow:hidden
}
.wrap h1{
background:pink;
margin:40px;
}
</style>
<body>
<div class="box">box</div>
<div class="wrap">
<h1>h1</h1>
</div>
</body>
利用这个特性,我们可以创造自适应两栏布局。
<style>
.box1{
height: 100px;
width: 100px;
float: left;
background: lightblue;
}
.box2{
overflow:hidden;
height: 200px;
background: #eee;
}
</style>
<body>
<div class="box1">我是一个左浮动的元素</div>
<div class="box2">喂喂喂!大家不要生气嘛,生气会犯嗔戒的。悟空你也太调皮了,
我跟你说过叫你不要乱扔东西,你怎么又……你看,我还没说完你就把棍子给扔掉了!
月光宝盒是宝物,你把它扔掉会污染环境,要是砸到小朋友怎么办,就算砸不到小朋友,
砸到花花草草也是不对的。</div>
</body>
<style>
.box1{
width:100px;
height:100px;
float:left;
border: 1px solid #000;
}
.box2{
width:100px;
height:100px;
float:left;
border: 1px solid #000;
}
.box{
background:yellow
}
</style>
<body>
<div class="box">
<div class="box1"></div>
<div class="box2"></div>
</div>
</body>
这里父元素包裹了两个浮动元素,没有设置高度,于是发生了高度塌陷,未能将子元素包裹住,解决这个问题也很简单,让父元素形成BFC区域,BFC区域会计算浮动元素的高度;
给父元素box加上overflow:hidden,形成BFC区域
0.1 + 0.2 == 0.3 ?,没有学编程的人看到这道算术题,都会认为这个等式肯定成立呀,难不成又是一个脑经急转弯?嘿嘿,先别急,在javascript这门语言中,0.1 + 0.2 == 0.3 这个等式式不成立的,这是由于Number数据类型的浮点数精度导致的
在 IEEE754 中,规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度、与延伸双精确度。Number类型是一种基于IEEE754标准的双精度64位二进制格式值,也就是采用双精确度,用64位来储存一个浮点数。
十进制小数转换成二进制小数采用乘2取整,顺序排列法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止,拿0.1转成二进制为例:
0.1 * 2 = 0.2 //取0
0.2 * 2 = 0.4 //取0
0.4 * 2 = 0.8 //取0
0.8 * 2 = 1.6 //取1
0.6 * 2 = 1.2 //取1
0.2 * 2 = 0.4 //取0
......
可以看出0.1转成二进制的运算过程中,乘积中小数部分永远不会为0,会一直循环下去,直到到达ECMAScript规定的64位来进行存储
Number(0.1).toString(2) // 0.0001100110011001100110011001100110011001100110011001101
Number(0.2).toString(2) // 0.001100110011001100110011001100110011001100110011001101
所以我们看出0.1在转换成二进制并存储的过程中,精度就已经丢失了,当我使用浮点数进行运算时,也是使用已经丢失精度的数据进行运算
关于浮点数的运算,一般由以下五个步骤完成:对阶、尾数运算、规格化、舍入处理、溢出判断。我们来简单看一下 0.1 和 0.2 的计算。
首先是对阶,所谓对阶,就是把阶码调整为相同,比如 0.1 是 1.1001100110011…… * 2^-4,阶码是 -4,而 0.2 就是 1.10011001100110...* 2^-3,阶码是 -3,两个阶码不同,所以先调整为相同的阶码再进行计算,调整原则是小阶对大阶,也就是 0.1 的 -4 调整为 -3,对应变成 0.11001100110011…… * 2^-3
接下来是尾数计算:
0.1100110011001100110011001100110011001100110011001101
+ 1.1001100110011001100110011001100110011001100110011010
————————————————————————————————————————————————————————
10.0110011001100110011001100110011001100110011001100111
我们得到结果为10.0110011001100110011001100110011001100110011001100111 * 2^-3
将这个结果处理一下,即结果规格化,变成1.0011001100110011001100110011001100110011001100110011(1) * 2^-2
括号里的 1 意思是说计算后这个 1 超出了范围,所以要被舍弃了。
再然后是舍入,四舍五入对应到二进制中,就是 0 舍 1 入,因为我们要把括号里的 1 丢了,所以这里会进一,结果变成
1.0011001100110011001100110011001100110011001100110100 * 2^-2
本来还有一个溢出判断,因为这里不涉及,就不讲了。
所以最终的结果存成 64 位就是
0 01111111101 0011001100110011001100110011001100110011001100110100
将它转换为10进制数就得到 0.30000000000000004440892098500626
因为两次存储时的精度丢失加上一次运算时的精度丢失,最终导致了 0.1 + 0.2 !== 0.3
num.toString(2); // 十进制转二进制
num.toString(8); // 十进制转八进制
num.toString(10); // 十进制转十进制
num.toString(16); // 十进制转十六进制
parseInt(num, 2); // 二进制转十进制;num 被看做是二进制的数
parseInt(num, 8); // 八进制转十进制;num 被看做是八进制的数
parseInt(num, 16); // 十六进制转十进制;num 被看做是十六进制的数
parseInt(num, 2).toString(8); // 二进制转八进制;也可以看做是二进制先转成十进制,再转成八进制
parseInt(num, 2).toString(16); // 二进制转十六进制;也可以看做是二进制先转成十进制,再转成十六进制
// 以指定的精度返回该数值对象的字符串表示
(0.1 + 0.2).toPrecision(21)
=> "0.300000000000000044409"
(0.3).toPrecision(21)
=> "0.299999999999999988898"
由于Vuex在浏览器刷新是会丢失数据,所以我在项目里使用了Vuex持久化存储的功能,我这边实现的方案是监听页面刷新的事件,将Vuex中的数据存到sessionStorage中,在页面初始化的时候再将sessionStorage中的数据设进Vuex中,具体代码如下
create(){
//在页面加载时读取sessionStorage里的状态信息
if (sessionStorage.getItem("store")) {
this.$store.replaceState(
Object.assign(
{},
this.$store.state,
JSON.parse(sessionStorage.getItem("store"))
)
);
sessionStorage.removeItem("store");
}
//在页面刷新时将vuex里的信息保存到sessionStorage里
window.addEventListener("beforeunload", () => {
sessionStorage.setItem("store", JSON.stringify(this.$store.state));
});
}
这样一个简单的Vuex持久化储存的功能就实现了,浏览器刷新后我们也能读取到Vuex中储存的数据,但是突然有一天,这个功能失效了,页面也没有报错,经过我的一番摸索,最终将问题锁定在了Vuex中储存的$route对象上
$Route对象上有以下这几个属性
"/foo/bar"
/foo?user=1
,则有 $route.query.user == 1
,如果没有查询参数,则是个空对象#
) ,如果没有 hash 值,则为空字符串routes
配置数组中的对象副本 (还有在 children
数组)问题就出在了$route这个路由对象上,我们在页面刷新时要将Vuex中的数据转换成字符串,再进行储存
//在页面刷新时将vuex里的信息保存到sessionStorage里
window.addEventListener("beforeunload", () => {
sessionStorage.setItem("store", JSON.stringify(this.$store.state));
});
而store中如果有$route
就会报错,这是因为$route
包含了一些非序列话的属性,例如函数、循环引用等;
当使用JSON.stringify($route)
时,可能会报错TypeError: Converting circular structure to JSON
,这是因为$route
对象包含循环引用,即对象内部存在相互引用,导致JSON.stringify()
无法序列化这个对象,从而抛出这个错误。
为了解决这个问题,你可以将$route
对象转换成一个新的纯JavaScript
对象,然后再进行序列化。可以使用Object.assign()
方法来实现这一点,如下所示:
JSON.stringify(Object.assign({}, $route))
这个方法会创建一个新的JavaScript对象,将$route
的属性复制到这个新对象中,并返回这个新对象。由于这个新对象只包含纯JavaScript数据,因此可以安全地序列化它。
通过这次分享加深下对知识点的理解,也希望对大家有些帮助,希望能够多多点赞支持下~
发布订阅是最常见的设计模式之一,像Vue使用的$bus事件总线就是采用了发布订阅的模式来实现组件之间的消息传递
我们可以把发布-订阅模式看成一个微信公众号,公众号属于发布者,用户属于订阅者,用户将订阅公众号的事件注册到调度中心,公众号作为发布者,当有新文章发布时,公众号发布该事件到调度中心,调度中心会及时发消息告知用户。
这个“微信公众号”主要有四个方法
class Bus {
constructor(){
this.event = {}
}
emit(type,...args){
if(!this.event[type]){
return
}
else{
this.event[type].forEach(cb => cb(...args))
}
}
on(type,cb){
if(!this.event[type]){
this.event[type] = [cb]
}
else{
this.event[type].push(cb)
}
}
off(type,cb){
if(!this.event[type]){
return
}
else{
this.event[type] = this.event[type].filter(item => item !== cb)
}
}
once(type,cb){
const fn = () =>{
cb()
this.off(type,cb)
}
this.on(type,fn)
}
}
let bus = new Bus()
let func = (str)=>{
console.log(str);
}
bus.on('emit',func)
bus.emit('emit',123)
bus.once('emit',func)
bus.off('emit', func)
在CSS布局中,三栏式布局是我们最常用的布局方式,其中圣杯布局和双飞翼布局是三栏式布局当中的佼佼者,我们一起来认识一下
<!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>圣杯布局</title>
<style>
body{
padding: 0;
margin: 0;
}
header,footer{
height: 100px;
width: 100%;
background-color: lightskyblue;
}
.container{
height: 300px;
padding-left: 200px;
padding-right:300px
}
.container div{
float: left;
/* 圣杯布局 */
position: relative;
}
.main{
width: 100%;
background-color: pink;
height: 300px;
}
.left{
width: 200px;
height: 300px;
background-color: lightgreen;
margin-left: -100%;
/* 圣杯布局 */
left: -200px;
}
.right{
width: 300px;
height: 300px;
background-color: #666;
margin-left: -300px;
/* 圣杯布局 */
right: -300px;
}
</style>
</head>
<body>
<header>头部</header>
<div class="container">
<div class="main">中间</div>
<div class="left">左边</div>
<div class="right">右边</div>
</div>
<footer>页脚</footer>
</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>双飞翼布局</title>
<style>
body{
padding: 0;
margin: 0;
}
header,footer{
height: 100px;
width: 100%;
background-color: lightskyblue;
}
.container{
height: 300px;
}
.container div{
float: left;
}
.middle{
width: 100%;
background-color: pink;
height: 300px;
}
/* 双飞翼布局 */
.main{
margin-left: 200px;
margin-right: 300px;
}
.left{
width: 200px;
height: 300px;
background-color: lightgreen;
margin-left: -100%;
}
.right{
width: 300px;
height: 300px;
background-color: #666;
margin-left: -300px;
}
</style>
</head>
<body>
<header>头部</header>
<div class="container">
<div class="middle">
<div class="main">中间</div>
</div>
<div class="left">左边</div>
<div class="right">右边</div>
</div>
<footer>页脚</footer>
</body>
</html>
一般页面最主要的内容都是放在main中进行渲染,为保证用户能第一时间看到页面最主要的内容,所以诞生出圣杯布局和双飞翼布局,这也是为什么main要放在最上面的原因;
圣杯布局和双飞翼布局的不同点:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.