执行上下文、函数调用栈
var a = 1;
function A() {
a = 3;
return ;
function a () { console.log(); };
}
A();
console.log(a);
聪明的你一定觉得这道题so easy,是吗?大笔一挥,答案a等于3.
错!!!
正确答案是1.
函数A()执行时,创建函数A的执行上下文,此时在A的上下文中,因为函数声明提前,变量a被声明为一个函数对象,当代码执行阶段时,a被赋值为3,继续执行return ;函数A结束执行。而全局变量a并未被赋值修改,所以console.log(a);等于1.
当调用一个函数时(激活),一个新的执行上下文就会被创建。而一个执行上下文的生命周期可以分为两个阶段。
- 创建阶段 在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向。
- 代码执行阶段 创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。 image.png
变量、赋值
let a = {v: 'temp'};
let b = a;
a = a.x = {v: 'temp'};
console.log(a);
// {v: 'temp'}
console.log(b);
// {v: 'temp', x: {v: 'temp'}}
说明:引用类型赋值时复制的是引用地址
函数传参
let a= 1;
let b= {a: 1};
function sum(m,n) {
m= 2;
n.a= 2;
}
sum(a,b);
console.log(a);
// 1
console.log(b);
// {a: 2};
说明:函数的参数是值传递的,而引用类型赋值的是其引用的内存地址。所以sum函数内,m=1,n与b指向同一块内存地址,函数执行后,console.log(b)打印{a: 2}。
函数调用栈和任务队列
//请写出下面代码段的执行结果
setTimeout( function () { console.log(0)},0);
Promise.resolve().then( function () { console.log(5)});
console.log(3);
for( var i= 10; i< 15; i++) {
setTimeout( function (){ console.log(i)}, i);
}
浏览器执行js代码永远是一个单线程,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。
队列数据结构
- 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。
- 任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
- macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
- micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
- setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
分析:
浏览器执行js过程中,遇到setTimeout(fn,time)
函数,调用浏览器定时线程计时,在time毫秒后将fn加入宏任务队列;
浏览器继续执行后续代码,遇到Promise将.then(fn)中fn加入微任务队列;
继续执行,打印3;
for循环5次,定时器线程分别在10ms、11ms、12ms、13ms、14ms后将function () { console.log(i);}
加入宏任务队列;
for循环结束后,本次宏任务队列执行完毕,执行微任务队列中function () { console.log(5);}
,打印5;
微任务队列执行完毕,继续执行宏任务队列,打印0, 15, 15, 15, 15, 15
所以输出结果为:3, 5, 0, 15, 15, 15, 15, 15
数组随机排序
var a = [1,2,3,4,5,6,7,8,9,10];
a.sort(function(a,b){ return Math.random() > 0.5;});
Array.prototype.sort()
语法
arr.sort()
arr.sort(compareFunction)
- 参数
compareFunction
可选。用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。- 返回值
返回排序后的数组。原数组已经被排序后的数组代替。- 说明
如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。例如 "Banana" 会被排列到 "cherry" 之前。当数字按由小到大排序时,9 出现在 80 之前,但因为(没有指明 compareFunction),比较的数字会先被转换为字符串,所以在Unicode顺序上 "80" 要比 "9" 要靠前。
如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:
如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本)
如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
如上方法并不是真正意思上的乱序,一些元素并没有机会相互比较, 最终数组元素停留位置的概率并不是完全随机的。
v8在处理sort方法时,使用了插入排序和快排两种方案。
当目标数组长度小于10时,使用插入排序;反之,使用快速排序。
其实不管用什么排序方法,大多数排序算法的时间复杂度介于O(n)到O(n²)之间,
元素之间的比较次数通常情况下要远小于n(n-1)/2,
也就意味着有一些元素之间根本就没机会相比较(也就没有了随机交换的可能),
这些 sort 随机排序的算法自然也不能真正随机。
其实我们想使用array.sort进行乱序,理想的方案或者说纯乱序的方案是数组中每两个元素都要进行比较,
这个比较有50%的交换位置概率。这样一来,总共比较次数一定为n(n-1)。
而在sort排序算法中,大多数情况都不会满足这样的条件。因而当然不是完全随机的结果了。
作者:云中桥
链接:https://juejin.im/post/5d004ad95188257c6b518056
// Fisher–Yates算法,这个算法是由 Ronald Fisher 和 Frank Yates 首次提出的。
// 这个算法其实非常的简单,就是将数组从后向前遍历,然后将当前元素与随机位置的元素进行交换。
function shuffle(arr) {
let m = arr.length;
while (m > 1){
let index = Math.floor(Math.random() * m--);
[arr[m] , arr[index]] = [arr[index] , arr[m]]
}
return arr;
}
数组去重
- 利用数组的lastIndexOf()方法。
arr= arr.filter( function (v,i,array) {
if(array.lastIndexOf(v) === i) {
return true;
}
return false;
});
filter不修改原数组,返回新数组。
- 利用ES6 新增Set对象实现
//2.1
arr = Array.from(new Set(arr));
//2.2
arr = [... new Set(arr)];
- 利用Object对象键名唯一性实现。
var obj = {},
del = [];
arr.forEach(function (v) {
if( !obj[typeof v+''+v]) {
del.push(v);
obj[typeof v+''+v] = 1;
}
});
网友评论