一、静态作用域
如何确定作用域链
- js的作用域链如何确定,取决于函数声明写在哪里,而不是函数调用写在哪里
- js的作用域链和函数调用写在哪里没有任何关系 => 静态作用域,词法作用域
- js的作用域属于静态作用域(词法作用域).
let x = 20;
function fn() {
let x = 10;
show(); // fn => 全局
}
function show() {
console.log(x);// show => 全局 // 20
}
fn();
let x = 20; (全局x)
fn();
let x = 10; (fn内的x)
show();
console.log(x);
二、函数间传递数据
- 如何让一个函数A去获取另一个函数B的局部变量的值?
- 在B函数中调用函数A,并传入B函数的局部变量给A函数.
- 这个做法是很多前端数据通信解决方案的核心逻辑。
- 例如:jsonp,框架里的组件通信.状态管理.s
function A(x) {
console.log(x);
}
function B(){
let x = 100;
A(x);
}
B();
三、事件传参
<body>
<button id='btn1'>按钮1</button>
<div></div>
<p></p>
<button id='btn2'>按钮2</button>
<h3></h3>
<span></span>
</body>
3.0:方式一
<script>
// 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
// 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span
let aBtn = document.querySelectorAll('button');
let oDiv = document.querySelectorAll('div')[0];
let oP = document.querySelectorAll('p')[0];
let oH3 = document.querySelectorAll('h3')[0];
let oSpan = document.querySelectorAll('span')[0];
aBtn[0].onclick = function() {
oDiv.innerText = '这是div';
oP.innerText = '这是一个p标签';
}
aBtn[1].onclick = function() {
oH3.innerText = '这是一个h3';
oSpan.innerText = '这是一个span';
}
</script>
方式二
<script>
// 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
// 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span
let aBtn = document.querySelectorAll('button');
let oDiv = document.querySelectorAll('div')[0];
let oP = document.querySelectorAll('p')[0];
let oH3 = document.querySelectorAll('h3')[0];
let oSpan = document.querySelectorAll('span')[0];
// 看代码 => 猜谜语 => 考虑返回值 => 你看见的是谜面 => 你得把谜底猜出来
// 不能这样写,这样写会把undefined赋值给点击事件,这是没有任何用处的.
aBtn[0].onclick = setInnerText (oDiv, oP, '这是div', '这是一个p标签');
aBtn[1].onclick = setInnerText (oH3, oSpan, '这是一个h3', '这是一个span');
function setInnerText (el1, el2, text1, text2) {
el1.innerText = text1;
el2.innerText = text2;
}
</script>
3.1
<script>
// 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
// 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span
let aBtn = document.querySelectorAll('button');
let oDiv = document.querySelectorAll('div')[0];
let oP = document.querySelectorAll('p')[0];
let oH3 = document.querySelectorAll('h3')[0];
let oSpan = document.querySelectorAll('span')[0];
// 事件传参的第一种解决方案:
// 在匿名函数内调用函数并传参.
aBtn[0].onclick = function () {
setInnerText (oDiv, oP, '这是div', '这是一个p标签');
};
aBtn[1].onclick = function () {
setInnerText (oH3, oSpan, '这是一个h3', '这是一个span');
};
function setInnerText (el1, el2, text1, text2) {
el1.innerText = text1;
el2.innerText = text2;
}
</script>
3.2
<script>
// 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
// 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span
let aBtn = document.querySelectorAll('button');
let oDiv = document.querySelectorAll('div')[0];
let oP = document.querySelectorAll('p')[0];
let oH3 = document.querySelectorAll('h3')[0];
let oSpan = document.querySelectorAll('span')[0];
// 事件传参的第一种解决方案:
// 在匿名函数内调用函数并传参.
aBtn[0].onclick = setInnerText (oDiv, oP, '这是div', '这是一个p标签');
aBtn[1].onclick = setInnerText (oH3, oSpan, '这是一个h3', '这是一个span');
// 闭包传参.
// 点击按钮,触发的是setInnerText函数,还是setInnerText的子函数?
// 点击按钮,触发的是setInnerText的子函数
function setInnerText (el1, el2, text1, text2) {
return function() {
el1.innerText = text1;
el2.innerText = text2;
}
}
</script>
四、bind传参
<body>
<button id='btn1'>按钮1</button>
<div></div>
<p></p>
<button id='btn2'>按钮2</button>
<h3></h3>
<span></span>
</body>
4.0
<script>
// 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
// 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span
let aBtn = document.querySelectorAll('button');
let oDiv = document.querySelectorAll('div')[0];
let oP = document.querySelectorAll('p')[0];
let oH3 = document.querySelectorAll('h3')[0];
let oSpan = document.querySelectorAll('span')[0];
// 事件传参的第一种解决方案:
// 在匿名函数内调用函数并传参.
// bind返回一个函数。用来给点击按钮触发。
// bind返回的函数如何给它传参? => 直接给bind传参即可.(第一个参数不是)
aBtn[0].onclick = setInnerText.bind (null, oDiv, oP, '这是div', '这是一个p标签');
aBtn[1].onclick = setInnerText.bind (null, oH3, oSpan, '这是一个h3', '这是一个span');
function setInnerText (el1, el2, text1, text2) {
el1.innerText = text1;
el2.innerText = text2;
}
</script>
4.1
<body>
<button>按钮</button>
</body>
<script>
const oBtn = document.querySelectorAll('button')[0];
// oBtn.onclick = show.bind(null, '你好');
// 在show后面加.bind
// bind的第一个参数写null.
oBtn.onclick = show.bind(null, '你好坏哦,我好喜欢!');
function show(msg) {
console.log(msg);
}
</script>
五、递归
递归 => 函数自己触发自己 (跟循环有点像)
表现形式 => 一个函数声明内部有自己的调用.
递归都需要一个条件来终止递归.(跟循环一样);
如果一个递归停不下来,会报下面的错误.
Uncaught RangeError: Maximum call stack size exceeded
5.0
<script>
let count = 0;
function show() {
// 打印
console.log(900);
// 计数+1
count++;
// 如果不足3次,继续递归打印
if (count < 3) {
show();
}
}
// for (let count = 0; count < 3; count++) {
// console.log(900);
// }
show();
</script>
5.1
let arr = [1, 2, 3, 4, 5];
// 写一个递归,打印这个数组的所有元素
let i = 0;
function fn() {
console.log(arr[i]);
i++;
if (i < arr.length) {
fn();
}
}
fn();
5.2
let arr = [1, 2, 3, 4, 5];
function fn(i) {
console.log(arr[i]);
i++;
if (i < arr.length) {
fn(i);
}
}
fn(0);
5.3
let arr = [1, 2, 3, 4, 5];
// 传参写法。
function fn (i) {
i--;
if (arr[i] !== undefined) {
console.log(arr[i]);
fn (i);
}
}
fn(5);
六、工作中的递归
<script>
// 对象套对象,如果需要访问所有的对象属性.需要递归.
function fn (obj) {
console.log(obj.name)
}
let obj = {
name: '小陈',
obj: {
name: '小赵',
obj: {
name: '小迪'
}
}
}
// 利用同一个函数,传递不同成参数,实现打印name.
// fn(obj);
// fn(obj.obj);
// fn(obj.obj.obj);
</script>
七、递归处理多层对象嵌套
<script>
// 对象套对象,如果需要访问所有的对象属性.需要递归.
let obj = {
name: '小陈',
obj: {
name: '小赵',
obj: {
name: '小迪',
obj: {
name: '小飞',
obj: {
name: '小文'
}
}
}
}
}
// 利用同一个函数,传递不同成参数,实现打印name.
// fn(obj);
// fn(obj.obj);
// fn(obj.obj.obj);
// fn(obj.obj.obj.obj);
// fn(obj.obj.obj.obj.obj);
function fn (obj) {
// 打印实参的name属性
console.log(obj.name);
// 如果当前的obj存在obj属性,并且这个obj属性是对象,就递归打印name
if (obj.obj !== undefined && typeof obj.obj === 'object') {
fn(obj.obj);
}
}
fn(obj);
</script>
八、递归执行顺序
递归执行时,最后调用的fn先结束调用,最先调用的fn最后结束调用。
函数执行栈 => 先进后出,后进先出.
递归的性能不好.递归次数越多,性能越不好.
function fn(i) {
i++;
if (i < 4) {
console.log('递归前' + i);
fn(i);
console.log('递归后' + i);
}
}
fn(0);
递归执行顺序分析:
A:
console.log('递归前1');
console.log('递归前2');
console.log('递归前3');
console.log('递归后1');
console.log('递归后2');
console.log('递归后3');
B
console.log('递归前1');
console.log('递归后1');
console.log('递归前2');
console.log('递归后2');
console.log('递归前3');
console.log('递归后3');
C
console.log('递归前1');
console.log('递归前2');
console.log('递归前3');
console.log('递归后3');
console.log('递归后2');
console.log('递归后1');
九、带return的递归
<script>
// 1*2*3*4*........
// num拿到的是第一次fn调用return的值.
let num = fn(3);
// fn调用了3次.
// 最后一次调用把结果1return给第二次调用,第二次调用把2*1return给第一次调用,第一次调用把3*2*1return给num
function fn(n) {
// 如果n是1,就返回1,如果不是1,就递归进行阶乘.
// return n === 1 ? 1 : n * fn(n-1);
if (n === 1) {
return 1
} else {
return n * fn(n-1)
}
}
// num1 = 1;
// num2 = 2 * num1;
// num3 = 3 * num2;
// 兔子数列 => 斐波那契数列 => 从第三项开始,每项都是前两项的和。
// 1,1,2,3,5,8,13,21............
// let count = feibonaqi(10); => 45位 (算不动了)
</script>
十、引用类型
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
10.0
let arr = [1, 2, 3];
// 下面只操作item,原数组的元素是不会改变的
arr.forEach((item, i) => {
item++;
arr[i]++;
});
let arr = [{num: 1}, {num: 2}, {num: 3}];
// 如果数组的元素是对象,则操作item和arr[i]效果是一样的.
arr.forEach((item, i) => {
item.num++;
arr[i].num++;
})
let aLi = document.querySelectorAll('li');
aLi.forEach((item, i) => {
aLi[i].style.backgroundColor = 'red';
item.style.backgroundColor = 'red';
})
10.1
js有6种数据类型:number,string,boolean,undefined,null,object
其中object可以分为:array,function,plain object,Math,arguments,node(节点),Date,....
如果按照存储的方式不同:
1:基本类型.(number,string,boolean,undefined,null)(存储在栈中).
2:引用类型.(object)(存储在堆中).
// 基本类型的赋值现象:
// 赋值的本质就是复制.
let x = 10;
// 把x的值复制一份,再赋值给y.(1个10变成2个10)
let y = x;
// 把第二个10变成20,第一个10没有变化.
y = 20;
console.log(x);// 10
// oYm是引用类型.
let oYm = {name: '小陈'};
// 引用赋值.
let obj = oYm;
// 修改obj的name,从oYm上也可以看到变化.
obj.name = '小赵';
console.log(oYm.name);//
基本类型变量内存储的是值本身.
- 引用类型变量内存储的不是对象本身,而是对象的引用(地址).
- 堆中的对象是不能直接访问的,需要通过对象的引用(地址)来访问.
- 小陈这个人(对象).
- 小陈的相片(对象的引用).看相片就能知道相片背后的是哪个人.
- 基本类型存值本身 => 钱包装钱.钱就是基本类型.(钱直接装在钱包里).
- 引用类型存的是对象的引用 => 钱包里放的是相片,而不是小陈这个人.(钱包内装的不是小陈这个人)
10.2
// 引用赋值之后,两个变量代表同一个对象,所以这个变,那个也变.
let oYm = {
name: '小赵'
}
// 引用赋值,复制的是对象的引用.一个引用变成两个引用(不是一个对象变成两个对象)
// 两个引用都指向同一个对象.这样在两个引用上都可以看到相同的变化。
let obj = oYm;
obj.name = '小赵';
console.log(obj.name);// 小赵
console.log(oYm.name);// 小赵
// 原始对象.
let oYm = {
name: '小陈'
}
引用赋值
let obj = oYm;
// 把obj的引用从存储幂幂的引用变成存储超越的引用.
// 把obj变成一个新的对象,不会影响oYm.
obj = {
name: '小赵',
}
console.log(obj.name);// 小赵
console.log(oYm.name);// 小陈
10.3
当前的修改时在修改对象,还是在修改引用?
修改纯对象:
对象.属性名 = xxxxxxx
对象[属性名] = xxxxxxx
修改数组:
数组[下标] = xxxxx;
数组.属性 = xxxxx;
push,pop,shift,unshift,splice,reverse,sort
修改引用(变量重新赋新值):
对象 = 新的值.
数组 = [];
function fn(list, num) {
list.push(100);
num = 100;
};
let arr = [1,2,3];
let x = 10;
// 引用赋值
list = arr;
// 基本赋值
num = x;
fn(arr, x);
console.log(arr);// [1,2,3,100]
console.log(x);// 10
let arr1 = 'john'.split('');
arr2和arr1是同一个数组
let arr2 = arr1.reverse();
let arr3 = 'jones'.split('');
arr2push,也就是arr1push.
arr2.push(arr3);
console.log(arr1.length + ' ' + arr1.slice(-1));
// 5 ['j','o','n','e','s']
const x = 10;
// const声明的是常量,值不能修改
x = 20;
// obj是引用类型.存储的是引用.
const obj = {name: 8080};
// 存储的是引用,不能修改
obj = 200;
// 修改引用背后的对象,不会报错.
obj.name = '8888';
// const声明的数组,还能push吗?
// 能,push修改的是数组本身,而不是数组的引用.
const arr = [1,2,3];
arr.push(4);
// 不能直接修改数组引用
arr = [];
十一、浅拷贝
<script>
// 深浅拷贝 => 引用类型的属性拷贝.(把一个对象的属性拷贝给另一个对象).
let oYm = {
name: '小陈',
age: 32,
sex: '女'
}
// 最简单的拷贝
// let obj = {...oYm};
let obj = {};
// 循环对象的写法.对象有多少个属性,就会循环多少次.
// prop就是每次循环到的属性名.
// 语法 => for(let 属性名 in 对象名)
for (let prop in oYm) {
obj[prop] = oYm[prop];
}
// obj.name = oYm.name;
// obj.age = oYm.age;
// obj.sex = oYm.sex;
</script>
十二、浅拷带来的问题
浅拷贝在拷贝时,如果属性是引用类型,则拷贝前和拷贝后的属性指向同一个对象.
这样会导致修改其中一个,另一个也会跟着变.
<script>
let oYm = {
name: '小陈',
exhusband: {
name: '小邓'
}
}
let obj = {};
// for in 浅拷贝.
for (let prop in oYm) {
obj[prop] = oYm[prop];
}
// obj.name = oYm.name;
// 这是引用赋值,会导致obj.exhusband和oYm.exhusband指向同一个对象.
// obj.exhusband = oYm.exhusband;
// 修改obj的属性,实际上也是在修改oYm的属性.
obj.exhusband.name = '小飞';
// 小飞
console.log(oYm.exhusband.name);
</script>
十三、深拷贝
- 为了解决浅拷贝带来的问题,我们需要深拷贝
- 浅拷贝拷贝前后是相同的对象.
- 深拷贝拷贝前后是不同的对象.
<script>
let oYm = {
name: '小陈',
exhusband: {
name: '小邓'
}
}
// 最简单的深拷贝.
// let obj = JSON.parse(JSON.stringify(oYm));
// 两步完成深拷贝
let str = JSON.stringify(oYm);
let obj = JSON.parse(str);
obj.exhusband.name = '小飞';
// false
console.log(oYm.exhusband === obj.exhusband);
// 小邓
console.log(oYm.exhusband.name);
</script>
十四、JSON对象
JSON => 是一个不标准的对象 => 但是现在大多数浏览器都支持,因此都习惯当成标准的对象来用.
JSON对象 => 处理纯对象.
什么json对象 => 就是纯对象.
for in => 循环对象.对象没有length属性,因此无法通过for来循环.
JSON.parse => 把json字符串转换为json对象
JSON.stringify => 把json对象转换为json字符串.
// 纯对象(json对象)
// let oYm = {
// name: '小陈'
// }
// JSON.parse() => 把json字符串转换为json对象
// JSON.stringify() => 把json对象转换为json字符串
let oYm = {name: '小陈'}
// 打印oYm转换为json字符串之后的结果
let str = JSON.stringify(oYm);
console.log(str);
console.log(typeof str);
let obj = JSON.parse(str);
console.log(obj);
console.log(typeof obj);
// 经过JSON.parse转换之后,oYm和obj就不是同一个对象了。
console.log(obj === oYm);