跳到主要内容

10 篇博文 含有标签「JavaScript」

查看所有标签

· 阅读需 3 分钟
DK

命令式和声明式

从范式上来看,视图层框架通常分为命令式和声明式,它们各有优缺点。作为框架设计者,应该对两种范式都有足够的认知,这样才能做出正确的选择,甚至想办法汲取两者的优点并将其捏合。

命令式和声明式各有优缺点,在框架设计方面,则体现在性能与可维护性之间的权衡。这里我们先抛出一个结论:声明式代码的性能不优于命令式代码的性能。 命令式。

早年间流行的 jQuery 就是典型的命令式框架。命令式框架的一大特点就是关注过程。例如,我们把下面这段话翻译成对应的代码:

· 阅读需 2 分钟
DK

模拟实现

Array.prototype.selfReduce = function (callback, initValue) {
// 获取源数组
const originArray = this;

// 判断源数组是否为空,如果为空,抛出异常
if (!originArray.length) {
throw new Error('selfReduce of empty array with no initial value');
}

// 声明累计器
let accumulator

// 是否有初始值情况
// 设置累计器初始值(如果有初始值,第一次调用`callback`时,`callback`的第一个参数的值为初始值,否则为源数组的第一项)
if (initValue === undefined) {
accumulator = originArray[0];
} else {
accumulator = initValue;
}

// 遍历数组,执行`callback`函数
for (let i = 0; i < originArray.length; i++) {
// 如果没有初始值且是最后一次循环,不再执行callback
if (initValue === undefined && (i + 1) === originArray.length) break;

// 循环执行 `callback`
// 这里判断一下`currentValue`
// 因为有初始值时,`currentValue`是`originArray[i]`
// 没有初始值时`currentValue`是`originArray[i + 1]`
accumulator = callback(accumulator, initValue === undefined ? originArray[i + 1] : originArray[i], i, originArray);
}

// 把累计器返回出去
return accumulator
}

测试

// 找出最大值
const r = [2, 4, 8, 1].selfReduce((a, b) => Math.max(a, b))
console.log(r) // 输出: 8

// 数组元素求和
const arr = [1, 2, 3, 4];
const initVal = 0;
const total = arr.reduce((acc, cur, index, array) => {
console.log(acc, cur);
// 0 1
// 1 2
// 3 3
// 6 4
return acc + cur;
}, initVal);
console.log(total); // 输出:10

· 阅读需 2 分钟
DK

一般情况下,我们或许会使用instanceof,是没有问题的。

async function back(){
return 1;
}
const res = back();
console.log(res instanceof Promise); //true

但是问题来了,Promise是一个规范而不只是一个类。

遵循Promise规范的库包含了ES6默认Promise、bluebird Promise、Q Promise等,那么我们使用bluebird Promise生成的Promise去instanceofES6的默认Promise会不会有问题呢? 显然,要出错。

所以这引出了React官方使用的方式是通过判断条件:

typeof destroy.then === 'function'

来判断一个对象是否是异步返回对象。

这样子的好处是,对于所有实现了Promise规范的异步库,这样的判断方式都是有效的。虽然这有产生误报的风险,但这是所有Promise库都必须遵循的规范。

同样的Promise判断方式并不只是React在使用,可以试试在F12运行这行代码,这将不会有任何输出。

await {then:()=>1};

原因无他,await的语法糖里判断Promise对象也是通过promise.then==='function',这源于Promise A+最基本的定义:

  • "promise"是具有then方法的对象或函数

· 阅读需 3 分钟
DK

为什么要有这些模式,目的:职责划分、分层(将Model层、View层进行分类)借鉴后端思想,对于前端而已,就是如何将数据同步到页面上

  • 传统的 MVC 指的是,用户操作会请求服务端路由,路由会调用对应的控制器来处理,控制器会获取数据。将结果返回给前端,页面重新渲染
  • MVVM:传统的前端会将数据手动渲染到页面上, MVVM 模式不需要用户收到操作 dom 元素,将数据绑定到 viewModel 层上,会自动将数据渲染到页面中,视图变化会通知 viewModel层 更新数据。ViewModel 就是我们 MVVM 模式中的桥梁

MVVM模式 映射关系的简化,隐藏了controller

MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。

  • Model: 代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。我们可以把Model称为数据层,因为它仅仅关注数据本身,不关心任何行为
  • View: 用户操作界面。当ViewModel对Model进行更新的时候,会通过数据绑定更新到View
  • ViewModel: 业务逻辑层,View需要什么数据,ViewModel要提供这个数据;View有某些操作,ViewModel就要响应这些操作,所以可以说它是Model for View.

总结: MVVM模式简化了界面与业务的依赖,解决了数据频繁更新。MVVM 在使用当中,利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化。

· 阅读需 2 分钟
DK

1.Beacon API用于将少量数据通过 post 请求发送到服务器。

2.Beacon是非阻塞请求,不需要响应

完美的解决性能缺陷问题:

1.浏览器将Beacon请求排队让它再空闲的时候执行并立即返回

2.它再unload状态下也可以异步发送,不阻塞页面刷新/跳转等操作 使用 navigator.sendBeacon()

const blob = new Blob(['room_id=123'], {
type: 'application/x-www-form-urlencoded',
})

const result = navigator.sendBeacon('http://127.0.0.1:8686', blob)

result是一个布尔值,代表这次发送请求的结果 浏览器接受并把请求排队了 返回true 如果过程中出现了问题 返回false

navigator.sendBeacon接受两个参数: url :请求的url ,请求是post请求 data:要发送的数据

Beacon其他相关 Beacon更多的情况是用于做前端埋点,监控用户活动 客户端优化:可以将 Beacon 请求合并到其他请求上,一同处理, 尤其在移动环境下。

· 阅读需 7 分钟
DK

函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性就是计算机科学文献中称的 “闭包”。这个术语非常古老,是指函数变量可以被隐藏于作用链之内,因此看起来是和函数将变量“包裹”起来。

在 JavaScript 中,只有函数内部的局部变量不能直接访问到,而函数中的子函数能读取局部变量,因此可以把闭包简单理解成 "定义在一个函数内部的函数"

《JS 高级程序设计》里面的定义是:一个可以访问另一个函数里局部变量的函数既闭包。

阮一峰的网络日志:在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁

闭包是一种形式,它是以函数的方式表现出来。

用处:

  • 能读取其他函数内部变量的函数,并且是 「间接」访问,不能「直接」访问。
  • 让这些变量的值始终保持在内存中。

特点:

  • 可以记录诞生他的环境(全局环境是必有的),JS 也采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个 作用域是在函数定义时决定的,而不是调用时决定的。
  • 闭包是把需要的变量保存到内置属性[[Scopeds]]上,这是一个数组,默认必有一个全局环境变量。其实就是帮我们把变量重新声明在当前的作用域中。(网上看的一些知识,自己也不太懂,逼格太高)

形成闭包的三个条件:

  1. 函数嵌套(有一个函数 A , 在函数 A 内部返回一个函数 B
  2. 访问所在的作用域的父级作用域 (在函数 B 中访问函数 A 的私有作用域变量
  3. 在所在的作用域外被调用 (在函数 A 外部,有变量引用函数 B
function A() {
var num = 100;
return function B() {
// 返回值函数B
var num1 = num; // 在函数B中访问函数A的局部变量
console.log(num1);
return num1;
};
}
var fn1 = A(); // 此处调用函数A,执行的结果就是函数B
var fn2 = fn1(); // 此处调用函数fn1(),执行结果就是引用函数B

使用闭包的注意点:

闭包会使得函数中的变量都被保存在内存中每个父函数调用完,就会形成新的闭包。父函数中的变量始终会在内存中,相当于缓存内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,尤其是IE 在我们使用完闭包之后,依然回收不了闭包里面引用的变量。

但是我认为: 使用了闭包那里面的变量明明就是我们需要的变量。不能说是内存泄漏,内存泄露是指: 你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。

IE 内存泄漏的解决方法:在退出函数之前,将不使用的局部变量全部删除。大部分浏览器,通过赋值为 null,释放内存。在 IE 中 在 return 返回值下面 delete 变量。

闭包可以在父函数外部,改变父函数内部变量的值。 所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,因为都在内存中,所以不要随便改变父函数内部变量的值,否则一处变其它地方也会随之变化

个人理解:

其实我觉得,根本不需要知道闭包这个概念,一样可以使用闭包!

因为函数也是一个 var,只是它比较特殊,函数里面的 var 不能被外接直接访问,然后在函数里面定义了一个函数来访问。其实跟调用一个对象里面定义的函数然后访问对象里面 key,区别就是对象里面的一个 key 可以被外面显示调用(造成不能隐蔽的问题),闭包解决这个问题

闭包是 JS 函数作用域的副产品。

由于 JS 的函数内部可以使用函数外部的变量,正好符合了闭包的定义,而不是 JS 故意要使用闭包。

总结

闭包让程序更加安全,封装数据、暂存变量。不让外部直接访问到我们的数据。

· 阅读需 7 分钟
DK

函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性就是计算机科学文献中称的 “闭包”。这个术语非常古老,是指函数变量可以被隐藏于作用链之内,因此看起来是和函数将变量“包裹”起来。

在 JavaScript 中,只有函数内部的局部变量不能直接访问到,而函数中的子函数能读取局部变量,因此可以把闭包简单理解成 "定义在一个函数内部的函数"

《JS 高级程序设计》里面的定义是:一个可以访问另一个函数里局部变量的函数既闭包。

阮一峰的网络日志:在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁

闭包是一种形式,它是以函数的方式表现出来。

用处:

  • 能读取其他函数内部变量的函数,并且是 「间接」访问,不能「直接」访问。
  • 让这些变量的值始终保持在内存中。

特点:

  • 可以记录诞生他的环境(全局环境是必有的),JS 也采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个 作用域是在函数定义时决定的,而不是调用时决定的。
  • 闭包是把需要的变量保存到内置属性[[Scopeds]]上,这是一个数组,默认必有一个全局环境变量。其实就是帮我们把变量重新声明在当前的作用域中。(网上看的一些知识,自己也不太懂,逼格太高)

形成闭包的三个条件:

  1. 函数嵌套(有一个函数 A , 在函数 A 内部返回一个函数 B
  2. 访问所在的作用域的父级作用域 (在函数 B 中访问函数 A 的私有作用域变量
  3. 在所在的作用域外被调用 (在函数 A 外部,有变量引用函数 B
function A() {
var num = 100;
return function B() {
// 返回值函数B
var num1 = num; // 在函数B中访问函数A的局部变量
console.log(num1);
return num1;
};
}
var fn1 = A(); // 此处调用函数A,执行的结果就是函数B
var fn2 = fn1(); // 此处调用函数fn1(),执行结果就是引用函数B

使用闭包的注意点:

闭包会使得函数中的变量都被保存在内存中每个父函数调用完,就会形成新的闭包。父函数中的变量始终会在内存中,相当于缓存内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,尤其是IE 在我们使用完闭包之后,依然回收不了闭包里面引用的变量。

但是我认为: 使用了闭包那里面的变量明明就是我们需要的变量。不能说是内存泄漏,内存泄露是指: 你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。

IE 内存泄漏的解决方法:在退出函数之前,将不使用的局部变量全部删除。大部分浏览器,通过赋值为 null,释放内存。在 IE 中 在 return 返回值下面 delete 变量。

闭包可以在父函数外部,改变父函数内部变量的值。 所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,因为都在内存中,所以不要随便改变父函数内部变量的值,否则一处变其它地方也会随之变化

个人理解:

其实我觉得,根本不需要知道闭包这个概念,一样可以使用闭包!

因为函数也是一个 var,只是它比较特殊,函数里面的 var 不能被外接直接访问,然后在函数里面定义了一个函数来访问。其实跟调用一个对象里面定义的函数然后访问对象里面 key,区别就是对象里面的一个 key 可以被外面显示调用(造成不能隐蔽的问题),闭包解决这个问题

闭包是 JS 函数作用域的副产品。

由于 JS 的函数内部可以使用函数外部的变量,正好符合了闭包的定义,而不是 JS 故意要使用闭包。

总结

闭包让程序更加安全,封装数据、暂存变量。不让外部直接访问到我们的数据。

· 阅读需 2 分钟
DK

1、for循环

const arr1 = new Array(100);

for(var i=0;i<arr1.length;i++){
arr1[i] = i;
}

console.log(arr1);

// 生成0-99的数组

2、push方法

const arr2 = new Array();

for(let i=0; i < 100; i++){
arr2.push(i);
}

console.log(arr2);

3、while

· 阅读需 4 分钟
DK

需求背景

用户退出当前页面时,修改的数据未进行保存,需要发送接口请求实现自动保存的功能。简单分析需求可知,退出页面包含 路由切换关闭浏览器标签页两种情况:

  • 路由切换:项目使用 Vue2 开发,离开页面时可以在 beforeDestroy 钩子函数中调用接口实现保存数据,但是这个方法只能在路由切换当前组件销毁前触发,无法监听到浏览器页面关闭的情况。
  • 关闭浏览器标签页:考虑在 window.onunload 钩子函数中发送请求。

· 阅读需 1 分钟
DK
/**
* js睡眠函数
* @param numberMillis {number} 毫秒值
* @returns {boolean}
*/
function sleep (numberMillis) {
let now = new Date();
let exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime)
return true;
}
}