var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var getLength = property('length');
var isArrayLike = function(collection) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; // 其中 JavaScript 中能精确表示的最大数字
};
没有数组的方法(push forEach)
arrayLike.push('sex') // 01.js:20 Uncaught TypeError: arrayLike.push is not a function
形式
console.log(array[0]); // name
console.log(arrayLike[0]); // name
array[0] = "new name";
arrayLike[0] = "new name";
console.log(array[0]); // new name
console.log(arrayLike[0]); // new name
/**
* Sorts an array.
* @param compareFn Function used to determine the order of the elements. It is expected to return
* a negative value if first argument is less than second argument, zero if they're equal and a positive
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
* ```ts
* [11,2,22,1].sort((a, b) => a - b)
* ```
*/
sort(compareFn?: (a: T, b: T) => number): this;
// The JSArray describes JavaScript Arrays
// Such an array can be in one of two modes:
// - fast, backing storage is a FixedArray and length <= elements.length();
// 存储结构是 FixedArray ,并且数组长度 <= elements.length() ,push 或 pop 时可能会伴随着动态扩容或减容
// Please note: push and pop can be used to grow and shrink the array.
// - slow, backing storage is a HashTable with numbers as keys
// 存储结构是 HashTable(哈希表),并且数组下标作为 key
class JSArray: public JSObject {
public:
// [length]: The length property.
DECL_ACCESSORS(length, Object)
// ...
// Number of element slots to pre-allocate for an empty array.
static const int kPreallocatedArrayElements = 4;
};
let res = new Array(100000).fill(10)
console.log(res)
var arr=new Array(100000).fill(100);
console.time('timer')
arr[0];
console.timeEnd('timer')
console.time('timer')
arr[100000-1];
console.timeEnd('timer')
vue.js:634 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "todo"
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
// 缓存prop的keys 为了是将来更新的props可以使用数组进行迭代,而不是动态的对象枚举
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
// 不是root根组件
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
// 通过判断是否在开发环境
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
// 如果不是,说明此修改来自子组件,触发warning提示
/**
* 传入的第4个函数是自定义的set函数,当props被修改的时候就会触发第四个参数的函数
*/
defineReactive(props, key, value, () => {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
// 如果是开发环境,会在触发Set的时候判断是否此key是否处于updatingChildren中被修改
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
src>core>observer>index.js
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
要分享的题目
这次分享的前端相关的小知识点:整体分为六几大类
分享的内容
[TOC]
前言
很开心能够把这次整理的几个前端小问题分享出来,完成的代码案例第一时间发布在 web-interview-questions
这也是笔者希望通过点点滴滴的记录把工作中或者在其他场景中遇到的问题整理在仓库中,如果看完本篇文章不妨点了赞
之后的前端知识大多会行文,希望一块记录,一块学习,有的是参阅了前端大佬,开源万岁,致谢
推荐阅读
建议可以先看下这几篇文章
这次分享的前端相关的小知识点:整体分为六几大类
准备
00-已知数据格式,实现一个函数 fn 找出链条中所有的父级 id
栈 LIFO
栈是一种遵从后进先出(LIFO)原则的有序集合。新添加的或待删除的元素都保存在栈的同一端,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底
push(element(s)):添加一个(或几个)新元素到栈顶。
pop():移除栈顶的元素,同时返回被移除的元素。
peek():返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的
元素,仅仅返回它)。
isEmpty():如果栈里没有任何元素就返回true,否则返回false。
clear():移除栈里的所有元素。
size():返回栈里的元素个数。这个方法和数组的length属性很类似。
队列 FIFO
广度优先搜索BFS
广度优先搜索算法会从指定的第一个顶点开始遍历图,先访问其所有的相邻 点,就像一次访问图的一层。
深度优先搜索DFS
深度优先搜索算法将会从第一个指定的顶点开始遍历图,沿着路径直到这条路 径最后一个顶点被访问了,接着原路回退并探索下一条路径。换句话说,它是先深 度后广度地访问顶点
那么整体来说分为两种方案:
基于递归的DFS,本身就是一种调用栈,在每个调用栈中,保存当前栈元素,再根据给定的value作对比决定继续递归查找还是中断递归。注意递归的中断逻辑,和每个调用栈元素的保存
其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次
01-输出以下代码执行的结果并解释为什么
类(伪)数组(arraylike)
就是像数组的对象(某些对象看起来像但不是)
通过索引属性访问元素
拥有 length 属性的对象
underscore 中的定义
没有数组的方法(push forEach)
形式
间接调用
转为真正的数组
数组的push
大白话
其实push的时候会首先查询数组(伪数组)的 length 属性,接着在数组的最后一个添加上新的元素即 arr[length]
'splice': Array.prototype.splice
为什么对象添加了splice属性后并没有调用就会变成类数组对象这个问题,这是控制台中 DevTools 猜测类数组的一个方式
splice
属性是函数类型length
属性且为正整数02-使用sort() 对数组进行排序 - 【3,15,8,29,102,22】
sort()
方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的采用的
utf-16
,常见的字符数字
英语大小写
汉字
总结
数字》英语大写》英语小写》汉字
阮老师 字符编码
步骤
案例
03-关于箭头函数
mdn-箭头函数
阮老师es6-箭头函数
什么是箭头函数
优势
关于函数的参数默认值
写起来更短
没有单独的
this
箭头函数使得表达更加简洁。
没有箭头函数
函数是根据如何被调用来定义这个函数的
this
用箭头函数
箭头函数不会创建自己的
this,它只会从自己的作用域链的上一层继承this
普通函数与箭头函数有什么不同
...变量名
),用于获取函数的多余参数,这样就不需要使用arguments
对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。yield
命令,因此箭头函数不能用作 Generator 函数。JavaScript中的new操作符
面试题
根据
new操作符
相关的知识点一般会 延伸出以下的面试题 ,面试官你是否有很多问号mdn关于new运算符关键字的描述
以上4条是
MDN
上关于new 操作符(或者说关键字)的面试,简单的来体验下利用构造函数来new
一个对象然后在构造函数添加原型方法
经过上文的简单案例我们可以得知,
new
一个构造函数得到一个对象,它的原型属性(也就是 proto )与该构造函数的原型是全等new
通过构造函数Persion
创建出来的实例可以访问到构造函数中的属性,就行这样言简意赅:new出来的实例对象通过原型链和构造函数联系起来
构造函数说白了也是一个函数,那是函数就可以有返回值
有了给构造函数返回一个值得想法,那就通过不同的
数据类型
进行测试得出结论小结
也就是说,构造函数一般不需要
return
手写一个自己的myNew
如果自己实现一个new 的话,首先要满足它的几点效果
一个构造函数会返回一个对象,那函数里就应该有对象
并将其
__proto__
属性指向构造函数的prototype
属性调用构造函数,绑定this
返回原始值需要忽略,返回对象需要正常处理
测试成果
箭头函数使用
new
new
命令,否则会抛出一个错误this
指向的固定化,并不是因为箭头函数内部有绑定this
的机制,实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this
。正是因为它没有this
,所以也就不能用作构造函数。04-vue 生命周期进阶
基本流程
加载渲染的过程
父组件挂载完毕肯定是等里面的子组件都挂载完毕后才算父组件挂载完毕了,所以父组件的mounted在最后。
子组件更新过程
子组件更新过程(子组件更新影响到父组件的情况):
父beforeUpdate -> 子beforeUpdate->子updated -> 父updted
子组件更新过程(子组件更新不影响父组件的情况):子beforeUpdate -> 子updated
父组件更新过程
eactivated函数的触发时间是在视图更新时触发。因为当视图更新时才能知道keep-alive组件被停用了。
父组件更新过程(父组件影响子组件的情况):
父beforeUpdate -> 子beforeUpdate->子updated -> 父updted
父组件更新过程(父组件不影响子组件的情况):父beforeUpdate -> 父updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
小补充
总结
Vue父子组件生命周期钩子的执行顺序遵循:从外到内,然后再从内到外,不管嵌套几层深,也遵循这个规律
05-双向绑定和 vuex 是否冲突
06-js中的数组
前言
数组、链表、栈、队列都是线性表,它表示的结构都是一段线性的结构,与之对应的就是非线性表,例如树、图、堆等,它表示的结构都非线性。
思考
什么 是数组
以上是js数组在内存中大致位置 ,或者
为什么这么说,因为在
js
中 数组的存储意思是说,我们可以看到
JSArray
是继承自JSObject
的,所以在 JavaScript 中,数组可以是一个特殊的对象,内部也是以 key-value 形式存储数据,所以 JavaScript 中的数组可以存放不同类型的值数组的优点
根据下标随机访问的时间复杂度为 O(1)
数组特性
我们已经知道数组是一段连续储存的内存,当我们要将新元素插入到数组k的位置时呢?这个时候需要将k索引处之后的所有元素往后挪一位,并将k索引的位置插入新元素.
删除操作其实与插入很类似,同样我要删除数组之内的k索引位置的元素,我们就需要将其删除后,为了保持内存的连续性,需要将k之后的元素通通向前移动一位,这个情况的时间复杂度也是O(n).
比如我们要查找一个数组中是否存在一个为2的元素,那么计算机需要如何操作呢?
如果是人的话,在少量数据的情况下我们自然可以一眼找到是否有2的元素,而计算机不是,计算机需要从索引0开始往下匹配,直到匹配到2的元素为止
我们已经强调过数组的特点是拥有相同的数据类型和一块连续的线性内存,那么正是基于以上的特点,数组的读取性能非常的卓越,时间复杂度为O(1),相比于链表、二叉树等数据结构,它的优势非常明显.
那么数组是如何做到这么低的时间复杂度呢?
假设我们的数组内存起始地址为
start
,而元素类型的长度为size
,数组索引为i
,那么我们很容易得到这个数组内存地址的寻址公式:比如我们要读取
arr[3]
的值,那么只需要把3
代入寻址公式,计算机就可以一步查询到对应的元素,因此数组读取的时间复杂度只有O(1).总结
JavaScript 中,
JSArray
继承自JSObject
,或者说它就是一个特殊的对象,内部是以 key-value 形式存储数据,所以 JavaScript 中的数组可以存放不同类型的值。它有两种存储方式,快数组与慢数组,初始化空数组时,使用快数组,快数组使用连续的内存空间,当数组长度达到最大时,JSArray
会进行动态的扩容,以存储更多的元素,相对慢数组,性能要好得多。当数组中hole
太多时,会转变成慢数组,即以哈希表的方式( key-value 的形式)存储数据,以节省内存空间。参考
性能测试
https://jsperf.com/
07-
a.b.c.d
和a['b']['c']['d']
,哪个性能更高?对于常见编译型语言(例如:Java)来说,编译步骤分为:词法分析->语法分析->语义检查->代码优化和字节码生成。
对于解释型语言(例如 JavaScript)来说,通过词法分析 -> 语法分析 -> 语法树,就可以开始解释执行了。
参考阅读
08-性能优化
[TOC]
引言
1. 关键词
进程与线程
1. 进程
程序的一次执行, 它占有一片独有的内存空间.是操作系统执行的基本单元。
(1). 浏览器进程
2 .线程
是进程内的一个独立执行单元,是CPU调度的最小单元。程序运行的基本单元 线程池(thread pool): 保存多个线程对象的容器, 实现线程对象的反复利用
HTTP 请求
1. DNS解析
2 .TCP协议的三次握手四次挥手
3 .HTTPS和HTTP
HTTP 响应
浏览器渲染原理
1. 浏览器功能
网络
资源管理
网页浏览
等等……
2. 浏览器内核
参考阅读百度百科
不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同,这也是网页编写者需要在不同内核的浏览器中测试网页显示效果的原因。
3. 浏览器渲染机制
Flow Based Layout
)HTML
解析成DOM
,把CSS
解析成CSSOM
,DOM
和CSSOM
合并就产生了渲染树(Render Tree
)。RenderTree
,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。Render Tree
的计算通常只需要遍历一次就可以完成,但table
及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用table
布局的原因之一4. 浏览器渲染引擎
(1). 主要模块
HTML解析器
CSS解析器
JavaScript引擎
布局(layout)回流
绘图模块
(2). 渲染过程
渲染树的构建、布局、及绘制中文
5 .浏览器渲染阻塞
(1). CSS样式渲染阻塞
link引入的外部css才能够产生阻塞
(2). JS阻塞
性能优化
1. 减少HTTP的请求
2. 减少DOM的重绘与回流
(1). 重绘
由于节点的几何属性发生改变或者由于样式发生改变而不会影响布局的,称为重绘,例如
outline
,visibility
,color
、background-color
等,重绘的代价是高昂的,因为浏览器必须验证DOM树上其他节点元素的可见性。(2). 回流
回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会导致了其所有子元素以及DOM中紧随其后的节点、祖先节点元素的随后的回流。有的大佬也习惯称之为重排
什么情况下浏览器会发生回流
(3). 浏览器优化
现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。
主要包括以下属性或方法:
offsetTop
、offsetLeft
、offsetWidth
、offsetHeight
scrollTop
、scrollLeft
、scrollWidth
、scrollHeight
clientTop
、clientLeft
、clientWidth
、clientHeight
width
、height
getComputedStyle()
getBoundingClientRect()
(4). 小结
回流必定会发生重绘,重绘不一定会引发回流 怎么最小化重绘重排?
CSS
使用
transform
替代top
使用
visibility
替换display: none
,因为前者只会引起重绘,后者会引发回流(改变了布局避免使用
table
布局,可能很小的一个小改动会造成整个table
的重新布局。尽可能在
DOM
树的最末端改变class
,回流是不可避免的,但可以减少其影响。尽可能在DOM树的最末端改变class,可以限制了回流的范围,使其影响尽可能少的节点。避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的
span
标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的span
标签,然后找到span
标签上的a
标签,最后再去找到div
标签,然后给符合这种条件的span
标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平。将动画效果应用到
position
属性为absolute
或fixed
的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择requestAnimationFrame
,详见探讨 requestAnimationFrame。避免使用
CSS
表达式,可能会引发回流。将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,例如
will-change
、video
、iframe
等标签,浏览器会自动将该节点变为图层。CSS3 硬件加速(GPU加速),使用css3硬件加速,可以让
transform
、opacity
、filters
这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color
这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。JavaScript
style
属性,或者将样式列表定义为class
并一次性更改class
属性。DOM
,创建一个documentFragment
,在它上面应用所有DOM操作
,最后再把它添加到文档中。参考阅读
09 -props
了解Prop
基本用法
常见类型
字符串数组的形式
对象的形式
汇总
整体来说可以分为
传递静态的值
和通过v-bind 传递动态的值
post: { id: 1, title: 'My Journey with Vue' }
在 Vue 中,子组件为何不可以修改父组件传递的 Prop ?
尝试修改会发生什么事情
首先创建一个文件来演示
props
传值(父组件的数据传递给子组件)结果是什么,数据也是可以修改成功的,但是控制台会报一个警告
单向数据流
简单的来说,vue这样处理从父组件来的数据,是为了方便监测数据的流动,如果一旦出现的错误,可以更为迅速的定位到错误的位置,
什么情况下,我们会改变这个prop
借助data
借助计算属性
如果修改了,Vue 是如何监控到属性的修改并给出警告的
这里我们可以去源码里找答案,毕竟真实的警告暗示是
vue
来给出的思考
如果是传入的是引用的数据类型,控制台会警告嘛?
推荐阅读