1.栈内存Stack & 堆内存Heap
- declare & defined
- ECStack执行环境栈(Execution Context Stack)和 EC(Execution Context)
- GO(Global Object)
- VO(Varible Object)
var a = 12;
var b = a;
b = 13;
console.log(a);
基本类型:按值操作,所以也叫值类型
引用类型:操作的是堆内存的地址(按引用地址操作的)
2.有关堆栈内存的题
2.1 创建应用数据类型值得步骤及使用
var a = {
n: 12
};
var b = a;
b['n'] = 13;
console.log(a.n);
2.2
2.3
3.函数的底层处理机制及作用域和作用域链
4.浏览器垃圾回收机制GC(Garbage Collection)
5.闭包
let x = 5;
function fn(x){
return function(y){
console.log(y + (++x);
}
}
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);
//扩展:去掉fn中的形参x
let x = 5;
function fn(){
return function(y){
console.log(y + (++x);
}
}
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);
5.1 闭包练习题1
let a = 0,
b = 0;
function A(a){
A = function(b){
alert(a + b++);
};
alert(a++);
}
A(1);
A(2);
5.2 闭包练习题2
/**
* EC(G)
* 变量提升:
* var a;
* var b;
* var c;
* test = 0x000; [[scope]]:EC(G)
*
*/
var a = 10,
b = 11,
c = 12;
function test(a){
/**
* EC(TEST)
* 作用域链:<EC(TEST),EC(G)>
* 形参赋值:a = 10;
* 变量提升:
* var b;
*/
a = 1;
var b = 2;
c = 3;
}
test(10);
console.log(a,b,c);// 10 11 3
5.3 闭包练习题3
/**
* EC(G)
* 变量提升:
* var a;
* b = 0x000; [[scope]]:EC(G)
* 代码执行
*/
var a = 4;
function b(x,y,a){
/**
* EC(B)
* 作用域链:<EC(B),EC(G)>
* 初始化this:window
* 初始化arguments(实参集合,类数组):
* {
* 0:1,
* 1:2,//=>10 此时a的值也会跟着改为10
* 2:3,
* length:3,
* callee:function b(){...}
* }
* =>在JS的非严格模式下,当“初始化arguments”和“形参赋值”完成后,会给两者建立一个“映射”机制:集合中的每一项和对应的形参变量绑定在一起了,一个修改都会跟着更改!而且只会发生在“代码执行之前”建立这个机制!
* =>在JS的严格模式下,没有映射机制,也没有arguments.callee这个属性;箭头函数中没有arguments;
* 形参赋值:x = 10 y=2 a=3
* 变量提升:--
*/
console.log(a);//3
arguments[2] = 10;
console.log(a);//10
}
a = b(1,2,3);//a=undefined 因为函数没有返回值
console.log(a);// undefined
5.4 闭包练习题4
// 递归
function fn(){
// 存储的是当前函数本身:很多时候这样操作方便递归调用
console.log(arguments.callee);
}
-----------
let obj = {
name: 'AA',
fn: function (n){
n++;
if (n > 12) return;
console.log(n);
//obj.fn(n); this->obj
arguments.callee(n); // this->arguments 相当于重新递归fn
}
};
obj.fn(10);
(function (i){
if(i >= 3) return;
console.log(i);
arguments.callee(++i);
})(i);
----------------------
/*
JS 严格模式下,不支持arguments.callee,此时我们该如何让匿名函数实现递归呢?
=>匿名函数具名化:这个一个非常规范的操作,就是给匿名函数设置一个名字
+ 此时这个名字可以在当前函数形参的私有上下文中使用,代表单签函数本身
+ 此名字不能在外部上下文中使用
+ 在本函数的上下文中使用,他的值是不允许修改的
+ 如果当前的名字被上下文中的其他变量声明过,他的值是可以改动的,则名字是私有变量和具名化的函数没有任何关系了
*/
"use strict"
(function b (i){
if(i >= 3) return;
console.log(i);
b(++i);
})(i);
console.log(b); //Uncaught ReferenceError: b is not defined 在函数外面访问不到
----------
(function b (){
console.log(b); //-> f b() {...}
b = 100; //他的值是不允许修改的
console.log(b); //-> f b() {...}
})();
------------
(function b (){
console.log(b); //-> undefined
var b = 100; //
console.log(b); //-> 100
})();
----------------
var b = 10;
(function b(){
// 因为匿名函数具名化之后,此时上下文中的b都是匿名函数本身(其修改值无效)
b = 20;
console.log(b); //-> f b() {...}
})();
console.log(b);//10
-----------------------------------------
function fn(x,y,z){
// 初始化arguments: [10,20] -> 它是类数组,我现在只是写成这种方便看的格式了
// 形参赋值:x=10 y=20 z=undefined
// z没有和arguments中任何一项产生映射机制
x = 100;
console.log(arguments[0]);//100
arguments[1] = 200;
console.log(y);//200
z = 300;//代码运行后修改的z,不产生映射
console.log(arguments[2]);//undefined
}
fn(10,20);
5.5 闭包练习题5
5.6 闭包练习题6
/**
* EC(G)
* 变量提升:
* var test;
*/
var test = (function(i){
/**
* 自执行函数执行 EC(ANY)
* 作用域链:<EC(ANY),EC(G)>
* 形参赋值:i=2
* 变量提升:--
* 代码执行
*/
return function (){
/**
* EC(TEST)
* 作用域链:<EC(TEST),EC(ANY)>
* 初始arguments:{0:5,length:1...}
* 形参赋值:--
* 变量提升:--
* 代码执行
*/
alert(i *= 2); // i = i*2 -> i是EC(ANY)闭包中的 -> '4'
} //=>return 0x001; [[scope]]:EC(ANY)
})(2);
// test = 0x001
test(5);
5.7 闭包练习题7
6.关于闭包套娃的面试题
7.let、const和var的区别
let和var的区别?
+ 变量提升
+ 重复声明
+ 暂时性死区
+ 和GO的关系
- JS中声明变量
+ 传统:var function
+ ES6:let const import
- let VS const
+ let和const声明的都是“变量”,具体的值是常量!
+ let声明一个变量,变量存储可以改值
+ const声明的变量,一旦赋值,则不能再和其他的值关联(不允许指针重新指向)
+ 基于const声明变量,必须设置初始值
- 变量提升:在当前上下文,代码执行之前,会把所有带“var/function”关键字的进行提前的声明或者定义
+ 带“var”的只是提前声明
+ 带“function”的是声明+定义
- 带var和不带var的区别
+ 带var 的时候就是声明变量,不带var的时候,没有变量提升
+ 在全局作用域下,带var 还是不带var 都是给GO添加了一个属性(也相当于给window),属性名就是此变量,属性值就是变量值
- let VS var
+ var存在变量提升,let不存在
+ 全局上下文中,基于var声明的变量,也相当于给GO(全局对象window)新增一个属性,并且任何一个发生值的改变,另外一个也会跟着变化(映射机制); 但是基于let声明的变量,就是全局变量VO(G),和GO没有任何的关系;
+ 在相同的上下文中,let不允许重复声明(不论你之前基于何种方式声明,只要声明过,则都不能基于let重复声明了);而var很松散,重复声明也无所谓,反正浏览器也只按照声明一次处理
+ 在代码之前,浏览器会自己处理很多事情:词法分析、变量提升
+ 在词法分析阶段,如果发现有基于let/const并且重复声明变量操作则直接报语法错误,整个代码都不会做任何的执行
+ 暂时性死区(浏览器暂存的BUG)
+ console.log(n); //Uncaught ReferenceError: n is not defined
+ console.log(typeof n); // undefined 基于typeof金策一个未被声明的变量,不会报错,结果是undefined
+ let不允许在代码执行之前使用变量
+ let/const/function会产生块级私有上下文,而var是不会的
上下文&作用域
+ 全局上下文
+ 函数执行形成的“私有上下文”
+ 块级作用域(块级私有上下文)
+ 处了 对象/函数...的大括号之外(例如:判断体、循环体、代码块...)都可能会产生块级上下文
8.闭包应用
- JS高阶编程技巧(本质:“闭包”的机制完成的)
- 闭包的特点是:保护 和 保存
- 弊端:占用内存,消耗浏览器的性能,闭包可以用,但是不能滥用
8.1 闭包进阶应用1:循环处理
全局变量污染问题
// setTimeout([function],[interval]):设置一个定时器,等待[interval]
/*
i是全局变量
i=0 第一轮循环
setTimeout(() => {// 设置定时器的时候,这个函数是创建不是执行
console.log(i);
}, 1000)
i=1 第二轮循环
setTimeout(() => {
console.log(i);
}, 2000);
i=2 第三轮循环
setTimeout(() => {
console.log(i);
}, 3000);
i = 3 循环结束
------1000ms时间到了
执行函数 ()=>{ console.log(i); }
+ 在形成的私有上下文中遇到变量i,发现并不是自己私有的,找上级上下文(全局)下的 i
+ 结果都是循环结束后的
*/
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 3 3 3
}, (i + 1) * 1000)
}
解法一:自执行函数
for (var i = 0; i < 3; i++) {
// 每一轮循环,自执行函数执行,都会产生一个私有的上下文:并且是把当前这一轮循环,全局变量i的值作为实参,传递给私有上下文中的形参i
/**
* EC(AN1) 形参赋值:i=0
* EC(AN2) 形参赋值:i=1
* EC(AN3) 形参赋值:i=2
*/
//=>每一个形成的私有上下文,都会创建一个“箭头函数堆”,并且把其赋值给 window.setTimeout,这样等价于,当前上下文中的某些内容,被上下文以外的东西给占用了,形成的上下文不会释放(私有变量i的值也不会被释放) =>闭包
(function (i) {
setTimeout(() => {
console.log(i); //1,2,3
}, (i + 1) * 1000);
})(i);
}
解法二:proxy返回执行的函数
// let xxx = proxy(0) -> proxy执行会产生闭包,闭包中私有的形参变量存储传递的实参信息
const proxy = i => {
return () => {
console.log(i); // 1,2,3
}
};
for (var i = 0; i < 3; i++) {
setTimeout(proxy(i), (i + 1) * 1000);
// 到达时间后,执行的是Proxy返回的小函数
}
解法三:let块级上下文
for (let i = 0; i < 3; i++) {
// 每一轮循环都会产生一个私有的块级上下文,如果上下文中没有什么东西被外面占用,则本轮循环结束,私有块级上下文也会被释放掉;但是一旦有东西被占用,则会产生闭包,性能上会有所消耗
setTimeout(()=>{
// ...处理很多事情,但是函数中不需要使用i的值
}, (i + 1) * 1000));
}
案例:解决点击按钮显示索引的几种实现方法
- 案例
<!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>Document</title>
</head>
<body>
<button>1</button>
<button>2</button>
<button>3</button>
<script>
// NodeList类数组集合
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function () {
//每个都是3,因为循环完全局变量已经是3了
// 当前点击按钮的索引:3
console.log(`当前点击按钮的索引:${i}`);
}
}
</script>
</body>
</html>
- 方法一:闭包
// 第一种写法
var buttons = document.querySelectorAll('button');
for( var i = 0; i< buttons.length; i++){
// 每一轮循环都会形成一个闭包,存储一个私有变量 i 的值(当前循环传递的i的值)
// + 自执行函数执行,产生一个上下文EC(A) 私有形参变量i= 0/1/2
// + EC(A)上下文中创建一个小函数,并且让全局buttons中的某一项占用创建的函数
(function (i){
buttons[i].onclick = function (){
console.log(`当前点击按钮的索引:${i}`);
};
})(i);
}
//第二种写法
var buttons = document.querySelectorAll('button');
for(var i = 0; i < buttons.length; i++){
buttons[i].onclick = (function(i){
return function(){
console.log(`当前点击按钮的索引:${i}`);
};
})(i);
}
//第三种写法let
var buttons = document.querySelectorAll('button');
for( let i = 0; i< buttons.length; i++){
buttons[i].onclick = function (){
console.log(`当前点击按钮的索引:${i}`);
}
- 方法二:自定义属性
// 性能强于闭包
var buttons = document.querySelectorAll('button');
for( var i = 0; i< buttons.length; i++){
// 每一轮循环都给单签按钮(对象)设置一个自定义属性:存储他的索引
buttons[i].myIndex = i;
buttons[i].onclick = function (){
// this => 当前点击的按钮
console.log(`当前点击按钮的索引:${this.myIndex}`);
}
- 方法三:事件委托
//在结构上设定自定义属性index,存储按钮的索引
<button index='0'>1</button>
<button index='1'>2</button>
<button index='2'>3</button>
// 方案三:比之前的性能提高40%-60%
// + 不论点击body中的谁,都会触发body的点击事件
// + ev。target是事件源:具体点击的是谁
document.body.onclick = function(ev){
var target = ev.target,
targetTag = target.tagName;
// 点击的是button按钮
if(targetTag === "BUTTON"){
var index = target.getAttribute('index');
console.log(`当前点击按钮的索引:${index}`);
}
}
8.2 闭包进阶应用2:基于闭包实现早期的模块化思想
单例设计模式
- 在没有对象类型值之前,我们很容易产生“全局变量的污染”
- 解决变量冲突:闭包机制 -> 保护
- 把描述同一个事物的属性,存放到相同的命名空间(对象)下,以此来进行分组,减少全局变量的污染;每一个对象都是Object这个类的不同的实例,这种设计模式叫做“单例设计模式”
let utils = (function () {
let iswindow = true,
num = 0;
const queryElement = function queryElement(selector) {};
const formatTime = function formatTime(tiem) {};
// 把需要供外界调用的方法,存储到一个命名空间(对象)中
return {
queryElement,
formatTime
};
})();
8.3 闭包进阶应用3:惰性函数
/*
获取元素的样式
+ window.getComputeStyle([元素对象]) 不兼容IE6-8
+ [元素对象].currentStyle
属性名 in 对象:检测当前这个属性是否属于这个对象
性能上的问题:
在某个浏览器中渲染页面(渲染代码)
+ 第一次执行get_css 需要验证浏览器的兼容性
+ 后期每一次执行 get_css ,浏览器的兼容性检测都会执行一遍 “这个操作时没有必要的”
*/
function get_css(element, attr) {
if (window.getComputedStyle) {
return window.getComputedStyle(element)[attr];
}
return element.currentStyle[attr];
}
// 基于“惰性函数”提高上述的性能“
function get_css(element, attr) {
if (window.getComputedStyle) {
// 第一次执行get_css,根据浏览器的兼容情况,对外部的get_css函数进行重构
get_css = function (element, attr) {
return window.getComputedStyle(element)[attr];
}
} else {
get_css = function (element, attr) {
return element.currentStyle[attr];
}
}
// 第一次执行也是需要获取到结果的,所以我们把重构的函数执行一次
return get_css(element, attr);
}
var w = get_css(documnet.body, 'width');
console.log(w);
//后续再次执行get_css,执行是第一次重构后的小方法,,无需再次校验兼容性
var h = get_css(documnet.body, 'height');
console.log(w);
8.4 闭包进阶应用4:柯里化函数&重写reduce
- 执行函数,形成一个闭包,把一些信息(私有变量和值)存储起来(保存作用)
- 以后其下级上下文中如果需要用到这些值,直接基于作用域链查找机制,拿来直接用即可
实参依次相加
function fn(){
// 执行fn传递的实参数信息
let outer_args = Array.from(arguments);
return function anonymous(){
// 存储执行小函数传递的实参信息
let innerArgs =Array.from(arguments);
//存储量词执行函数传递的实参信息
let params = outer_args.concat(innerArgs);
// 数组相加 forEACH循环相加; join分隔,eval变为函数表达式
return result = params.reduce(function(result,item){
return result + item;
});
};
}
/*
const fn = (...outer_args) =>{
return (...innerArgs)=>{
return outer_args.concat(innerArgs).reduce((result,item)=>{
return result + item;
});
}
}
const fn = (...outer_args) =>(...innerArgs)=>outer_args.concat(innerArgs).reduce((result,item)=> result + item);
*/
let res = fn(1,2)(3);
console.log(res); //=>6 1+2+3