张登友,张登友的博客,张登友的网站——
JavaScript笔记
数据分为
- 静态数据(指一些永久性的数据)
- 动态数据(程序运行过程中,动态产生的临时数据,一般存储在内存中)
JS 中一共有八种数据类型
基本数据类型(值类型):String 字符串、Number 数值、BigInt 大型数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol。
引用数据类型(引用类型):Object 对象。
面试问:引用数据类型有几种?
面试答:只有一种,即 Object 类型。
数据类型之间最大的区别:
基本数据类型:参数赋值的时候,传数值。
引用数据类型:参数赋值的时候,传地址。
栈内存和堆内存
JS 中,所有的变量都是保存在栈内存中的。
基本数据类型的值,直接保存在栈内存中。值与值之间是独立存在,修改一个变量不会影响其他的变量。
引用数据类型:
对象是保存到堆内存中的。每创建一个新的对象,就会在堆内存中开辟出一个新的空间;而变量保存了对象的内存地址(对象的引用),保存在栈内存当中。如果两个变量保存了同一个对象的引用,当一个通过一个变量修改属性时,另一个也会受到影响。
模板字符串中插入变量
var name = 'qianguyihao';
var age = '26';
console.log('我是' + name + ',age:' + age); //传统写法
console.log(`我是${name},age:${age}`); //ES6 写法。符号是反引号(在 tab 键的上方)
模板字符串中插入表达式
const a = 5;
const b = 10;
console.log('this is ' + (a + b) + ' and\nnot ' + (2 * a + b) + '.'); // 传统写法需要添加转义字符换行
// 下面这行代码,故意做了换行。
console.log(`this is ${a + b} and
not ${2 * a + b}.`);
模板字符串可以换行
const result = {
name: 'zdy',
age: 25,
sex: '男',
};
// 模板字符串支持换行
const html = `<div>
<span>${result.name}</span>
<span>${result.age}</span>
<span>${result.sex}</span>
</div>`;
console.log(html); // 打印结果也会换行
模板字符串中可以调用函数
console.log(html); // 打印结果也会换行
function getName() {
return 'zdynb';
}
console.log(`www.${getName()}.cn`); // 打印结果:www.zdynb.cn
模板字符串支持嵌套使用
const nameList = ['刺客', '伍六七', '哈哈哈'];
function myTemplate() {
// join('') 的意思是,把数组里的内容合并成一个字符串
return `<ul>
${nameList.map((item) => `<li>${item}</li>`).join('')}
</ul>`;
}
document.body.innerHTML = myTemplate();
注:map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。示例:
const array1 = [1, 4, 9, 16];
// pass a function to map
const map1 = array1.map(x => x * 2);
console.log(map1);
// expected output: Array [2, 8, 18, 32]
十、数值范围
- 最大值:
Number.MAX_VALUE
,这个值为: 1.7976931348623157e+308 - 最小值:
Number.MIN_VALUE
,这个值为: 5e-324
如果使用 Number 表示的变量超过了最大值,则会返回 Infinity。
无穷大(正无穷):Infinity
无穷小(负无穷):-Infinity
NAN
typeof NaN
的返回结果是 number。Undefined 和任何数值计算的结果为 NaN。NaN 与任何值都不相等,包括 NaN 本身。
连字符和加号的区别
- 如果加号两边都是 Number 类型是数字相加。
- 否则,就是连字符(用来连接字符串)。
隐式转换
-
、*
、/
、%
这几个符号会自动进行隐式转换。
"2"+1
得到的结果其实是字符串,但是"2"-1
得到的结果却是数值 1,这是因为计算机自动帮我们进行了“隐式转换”
浮点数运算精度
- 整数的运算基本可以保证精确
- 小数的运算,可能会得到一个不精确的结果
var a = 0.1 + 0.2;
console.log(a); //打印结果十分意外:0.30000000000000004
简单的精度问题:可以使用
toFix()
方法进行小数的截取。数学运算的开源库
- decimal.js:属于很全面的运算库,文件很大,压缩后的文件就有 500kb。如果你的项目涉及到大型的复杂运算,可以使用 Math.js。
- Math.js:属于轻量的运算库,压缩后的文件只有 32kb。大多数项目的数学运算,使用 decimal.js 足够了。
Null:空对象
Null 类型的值只有一个,就是 null。比如
let a = null
。使用 typeof 检查一个 null 值时,会返回 object。null 虽然是一个单独的数据类型,但null 相当于是一个 object,只不过地址为空(空指针)而已
undefined:未定义类型
- 变量已声明但未赋值时,此时它的值就是
undefined
- 变量未声明(未定义)时,此时,如果用
typeof
检查这个变量时,会返回undefined
- 函数无返回值时,那么这个函数的返回值就是 undefined。也可以这样理解:在定义一个函数时,如果末尾没有 return 语句,那么,其实就是
return undefined
。 - 调用函数时未传参,那么这个参数的值就是 undefined。
null 和 undefined 的区别
- null 和 undefined 有很大的相似性。看看
null == undefined
的结果为true
也更加能说明这点。 - 但是
null === undefined
的结果是 false。它们虽然相似,但还是有区别的,其中一个区别是,和数字运算时:- 10 + null 结果为 10。
- 10 + undefined 结果为 NaN。
规律:
- 任何值和 null 运算,null 可看做 0 运算。
- 任何数据类型和 undefined 运算都是 NaN。
typeof 运算符
typeof 的写法 | 返回结果 |
---|---|
typeof 数字 | number |
typeof 字符串 | string |
typeof 布尔型 | boolean |
typeof 对象 | object |
typeof 方法 | function |
typeof null | object |
typeof undefined | undefined |
typeof NaN | number |
- 因为 null 代表的是空对象。
- 空数组
[]
、空对象{}
在使用 typeof 时,返回值也是 object。因为空数组、空对象都是引用数据类型 Object。 - typeof 无法区分数组,但 instanceof 可以。
变量的类型转换的分类
类型转换分为两种:显式类型转换、隐式类型转换。
显示类型转换
toString()
String()
Number()
parseInt(string)
parseFloat(string)
Boolean()
隐式类型转换
isNaN ():任何不能被转换为数值的参数,都会让这个函数返回 true
执行过程:
(1)先调用
Number(参数)
函数;(2)然后将
Number(参数)
的返回结果是否为数值。如果不为数值,则最终结果为 true;如果为数值,则最终结果为 false。
自增/自减运算符:
++
、—-
执行过程:
(1)先调用
Number(参数)
函数;(2)然后将
Number(参数)
的返回结果进行 加 1 操作。
正号/负号:
+a
、-a
- 任何值做
+a
、-a
运算时, 内部调用的是 Number() 函数。不会改变原数值(这里说的是正号/负号,不是加号/减号)
- 任何值做
加号:
+
- 变量+”” 或者 变量+”abc”(此处为连接符,非加号)
运算符:
-
、*
、/
、%
- 运算时,会将这些值转换为 Number 然后再运算(内部调用的是 Number() 函数)
隐式类型转换(特殊)
- 逻辑运算符:
&&
、||
、!
。非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算。&&
、||
的运算结果是原值,!
的运算结果为布尔值。 - 关系运算符:
<
、>
<=
>=
等。关系运算符,得到的运算结果都是布尔值:要么是 true,要么是 false。
其他简单类型—->String
隐式类型转换:字符串拼接
格式:变量+”” 或者 变量+”abc”(实际上内部是调用的 String() 函数)
调用 toString()方法,格式:
- 变量.toString();
- var result = 变量.toString();
- null 和 undefined 这两个值没有 toString() 方法,所以不能用 toString() 。如果调用,会报错。
【重要】该方法不会影响到原变量,它会将转换的结果返回。当然我们还可以直接写成
a = a.toString()
,这样的话,就是直接修改原变量。使用String()函数,格式:
String(变量);
使用 String()函数做强制类型转换时:
- 对于 Number、Boolean、Object 而言,本质上就是调用 toString()方法。
prompt():用户的输入
prompt()
是用来弹出能够让用户输入的对话框。用户不管输入什么,都当字符串处理。
其他的数据类型 —-> Number
使用Number()函数
字符串—>数字
如果字符串是一个空串或者是一个全是空格的字符串,则转换为 0
只要字符串中包含了其他非数字的内容(
小数点
按数字来算),则转换为 NaN。使用 Number() 函数之后,如果无法转换为数字,就会转换为 NaN布尔—>数字
- true转成1
- false转成0
null—>数组,结果为0
undefined—>数字,结果为NAN
隐式类型转换:正负号
+a
、-a
注意,这里说的是正号/负号,不是加号/减号。
任何值做
+a
、-a
运算时, 内部调用的是 Number() 函数。不会改变原数值。使用 parseInt()函数:字符串 -> 整数
parseInt()**:将传入的数据当作字符串**来处理,从左至右提取数值, 一旦遇到非数值就立即停止;停止时如何还没有提取到数值, 那么就返回NaN。
转换情况有以下几种:
字符串 –> 数字
(1)只保留字符串最开头的数字,后面的中文自动消失
(2)如果字符串不是以数字开头,则转换为 NaN
(3)如果字符串是一个空串或者是一个全是空格的字符串,转换时会报错
Boolean –> 数字,结果为:NaN
Null –> 数字,结果为:NaN
Undefined –> 数字,结果为:NaN
Number(true)
和parseInt(true)/parseFloat(true)
的区别:Number() :转换为数字;如果转换不了则返回 NaN。
parseInt()/parseFloat() :提取出最前面的数字部分;没提取出来,那就返回 NaN。
parseInt()、parseFloat()会将传入的数据当作字符串来处理。也就是说,如果对非 String使用 parseInt()、parseFloat(),会先将其转换为 String 然后再操作。【重要】
parseFloat()函数:字符串 –> 浮点数(小数)
parseFloat()的作用是:将字符串转换为浮点数。
转换为 Boolean
数字 –> 布尔。 0 和 NaN是 false,其余的都是 true。比如
Boolean(NaN)
的结果是 false。字符串 —> 布尔。空串是false,其余的都是 true。全是空格的字符串,转换结果也是 true。字符串
'0'
的转换结果也是 true。null 和 undefined 都会转换为 false。
引用数据类型会转换为 true。空数组
[]
和空对象{}
,转换结果也是 trueconst result1 = ''; const result2 = { a: 'data1', b: 'data2' }; if (result1) { console.log('result1数值为空,不执行'); } if (result2 && result2.a) { // 接口返回了 result2,且 result2.a 里面有值 console.log('result2数据不为空,可以执行'); }
运算符分类
算数运算符
运算符 描述 + 加、字符串连接 - 减 * 乘 / 除 % 获取余数(取余、取模) 假设用户输入 345,怎么分别得到 3、4、5 这三个数呢?
// 百分号:取余。只关心余数。 // 得到3的方法:345 除以100,得到3.45然后取整,得到3。即:parseInt(345/100) // 得到4的方法:345 除以100,余数是45,除以10,得到4.5,取整。即: parseInt(345 % 100 / 10) // 得到5的方法:345 除以10,余数就是5。即: parseInt(345 % 10)
取模(取余)运算
- 如果 n < 0,那就先把 n 取绝对值后,再计算。等价于 m % (-n)。
- 如果 n 是 0,那么结果是 NaN。
- 在 n > 0 的情况下:
- 如果 m>=n,那就正常取余。
- 如果 m<n,那结果就是 m。
- 取余运算结果的正负性,取决于 m,而不是 n。比如:
10 % -3
的运算结果是 1。-10 % 3
的运算结果是-1。
自增/自减运算符
-
a++
先把 a 的值赋值给表达式,然后 a 再自增 -
++a
a 先自增,然后再把自增后的值赋值给表达式
一元运算符
只需要一个操作数(正号 +
,负号 -
可以对一个其他的数据类型使用+
,来将其转换为 number)
var a = '123';
console.log(typeof a);
var a = true;
a = +a; //一元运算符
逻辑运算符
(注意:能参与逻辑运算的,都是布尔值)
&&
与(且):两个都为真,结果才为真。||
或:只要有一个是真,结果就是真。!
非:对一个布尔值进行取反。
赋值运算符
=
直接赋值。比如var a = 5
。意思是:把 5 赋值给 a。+=
。a += 5 等价于 a = a + 5-=
。a -= 5 等价于 a = a - 5*=
。a * = 5 等价于 a = a*5/=
。a /= 5 等价于 a = a / 5%=
。a %= 5 等价于 a = a % 5
比较运算符
- 大于号
- < 小于号
- = 大于或等于
- <= 小于或等于
- == 等于
- === 全等于
- != 不等于
- !== 不全等于
console.log(1 > true); //false
console.log(1 >= true); //true
console.log(1 > '0'); //true
//console.log(10 > null); //true
//任何值和NaN做任何比较都是false
console.log(10 <= 'hello'); //false
console.log(true > false); //true
特殊情况:如果符号两侧的值都是字符串时,不会将其转换为数字进行比较。比较两个字符串时,比较的是字符串的Unicode 编码。【非常重要,这里是个大坑,很容易踩到】
三元运算符
也叫条件运算符
条件表达式 ? 语句1 : 语句2;
执行的流程:
条件运算符在执行时,首先对条件表达式进行求值:
如果该值为 true,则执行语句 1,并返回执行结果
如果该值为 false,则执行语句 2,并返回执行结果
如果条件的表达式的求值结果是一个非布尔值,会将其转换为布尔值然后再运算。
运算符的优先级
优先级从高到低
.
、[]
、new
()
++
、--
!
、~
、+
(单目)、-
(单目)、typeof
、void
、delete
*
、/
、%
+
(双目)、-
(双目)<<
、>>
、>>>
关系运算符:
<
、<=
、>
、>=
==
、!==
、===
、!==
&
^
|
&&
||
?:
=
、+=
、-=
、*=
、/=
、%=
、<<=
、>>=
、>>>=
、&=
、^=
、|=
,
注意:逻辑与 &&
比逻辑或 ||
的优先级更高。
备注:如果不清楚哪个优先级更高,可以把括号用上。
接口的返回码 retCode的if推荐写法
直接通过 return 的方式,让 function 里的代码不再继续往下走,因为要用到 return ,所以整段代码是封装到一个 function 里的。
let retCode = 1003; // 返回码 retCode 的值可能有很多种情况
handleRetCode(retCode);
// 方法:根据接口不同的返回码,处理前端不同的显示状态
function handleRetCode(retCode) {
if (retCode == 0) {
alert('接口联调成功');
return;
}
if (retCode == 101) {
alert('活动不存在');
return;
}
if (retCode == 103) {
alert('活动未开始');
return;
}
if (retCode == 104) {
alert('活动已结束');
return;
}
if (retCode == 1001) {
alert('参数错误');
return;
}
if (retCode == 1002) {
alert('接口频率限制');
return;
}
if (retCode == 1003) {
alert('未登录');
return;
}
if (retCode == 1004) {
alert('(风控用户)提示 活动太火爆啦~军万马都在挤,请稍后再试');
return;
}
// 其他异常返回码
alert('系统君失联了,请稍候再试');
return;
}
switch精简写法
适时去掉break
let day = 2;
switch (day) {
case 1:
case 2:
case 3:
case 4:
case 5:
console.log('work');
break; // 在这里放一个 break
case 6:
case 7:
console.log('relax');
break; // 在这里放一个 break
default:
break;
}
}
for循环
for(①初始化表达式; ②条件表达式; ④更新表达式){
③语句...
}
while循环
while(条件表达式){
语句...
}
//执行流程
while语句在执行时,先对条件表达式进行求值判断:
如果值为true,则执行循环体:
循环体执行完毕以后,继续对表达式进行判断
如果为true,则继续执行循环体,以此类推
如果值为false,则终止循环
do…while循环
do…while语句在执行时,会先执行循环体
do{
语句...
}while(条件表达式)
循环的label使用
outer: for (var i = 0; i < 5; i++) {
console.log('外层循环 i 的值:' + i);
for (var j = 0; j < 5; j++) {
break outer; // 直接跳出outer所在的外层循环(这个outer是自定义的label)
console.log('内层循环 j 的值:' + j);
}
}
循环的continue
continue 可以用来跳过当次循环,继续下一次循环。
同样,continue 默认只会离他最近的循环起作用。
同样,如果需要跳过指定的当次循环,可以使用 label 标签。
for (var i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue;
}
console.log('i的值:' + i);
}
对象的分类
1.内置对象:
由ES标准中定义的对象,在任何的ES的实现中都可以使用
比如:Object、Math、Date、String、Array、Number、Boolean、Function等。
2.宿主对象:
由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象。
比如 BOM DOM。比如
console
、document
。
3.自定义对象:
- 由开发人员自己创建的对象
通过 new 关键字创建出来的对象实例,都是属于对象类型,比如Object、Array、Date等。
生成【x,y】之间的随机整数
生成两个整数之间的随机整数,并且要包含这两个整数
/*
* 生成两个整数之间的随机整数,并且要包含这两个整数
*/
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(getRandom(1, 10));
url 编码和解码
encodeURIComponent(); //把字符串作为 URI 组件进行编码
decodeURIComponent(); //把字符串作为 URI 组件进行解码
let url = 'http://www.zdynb.cn';
let str = encodeURIComponent(url);
console.log(decodeURIComponent(str));
打印中文的星期
var myDate = new Date();
var dayArr = ['星期日', '星期一', '星期二', '星期三', '星期四','星期五', '星期六'];
console.log(myDate.getDay()); // 打印结果:1
console.log(dayArr[myDate.getDay()]); // 打印结果:星期一
格式化日期
Moment.js 轻量级的JavaScript时间库
已封装的日期函数
function formatDate() {
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
let week = date.getDay();
let weekday = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
week=weekday[week];
let hour = date.getHours();
hour = hour < 10 ? '0' + hour : hour;
let minuate = date.getMinutes()
minuate = minuate < 10 ? '0' + hour : hour;
second = date.getSeconds();
second = second < 10 ? '0' + second : second;
let now = '今天是: ' + year + '年' + month + '月' + day + '日' + ' ' + hour + ':' + minuate + ':' + second + ' '+ week;
return now;
}
发布会倒计时
let div = document.getElementsByTagName('div')[0];
let timer = setInterval(() => {
countDown('2022/02/03 11:20:00');
}, 1);
function countDown(myTime) {
let nowTime = new Date();
let future = new Date(myTime);
let timeSum = future.getTime() - nowTime.getTime(); //获取时间差
let day = parseInt(timeSum / 1000 / 60 / 60 / 24); // 天
let hour = parseInt((timeSum / 1000 / 60 / 60) % 24); // 时
let minu = parseInt((timeSum / 1000 / 60) % 60); // 分
let sec = parseInt((timeSum / 1000) % 60); // 秒
let millsec = parseInt(timeSum % 1000); // 毫秒
//时间小于10补一个0
day = day < 10 ? '0' + day : day;
hour = hour < 10 ? '0' + hour : hour;
minu = minu < 10 ? '0' + minu : minu;
sec = sec < 10 ? '0' + sec : sec;
if (millsec < 10) {
millsec = '00' + millsec;
} else if (millsec < 100) {
millsec = '0' + millsec;
}
// 兜底处理
if (timeSum < 0) {
div.innerHTML = '距离发布会还有00天00小时00分00秒000毫秒';
clearInterval(timer);
return;
}
// 前端显示
div.innerHTML = '距离发布会还有' + day + '天' + hour + '小时' + minu + '分' + sec + '秒' + millsec + '毫秒';
}
翻转数组
let arr = [10,20,30,40,50];
let newArr=[];
for (let i=0;i<arr.length;i++){
newArr[i]=arr[arr.length-i-1]
}
冒泡排序
let arr = [30,10,20,40,60];
for (let i=0;i<arr.length;i++){
for (let j=0;j<arr.length-i-1;j++){
if (arr[j]>arr[j+1]){
let temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
console.log(JSON.stringify(arr));//结果[10,20,30,40,60]
将 A 数组中某个属性的值,存储到 B 数组中
let arr1 = [{
name: '自定义',
age: 28
}, {
name: '哈哈哈',
age: 29
}]
const arr2 = arr1.map((item) => item.name); //存储单个属性
const arr3 = arr1.map((item) => ({//存储多个属性
myname: item.name,
myage: item.age
}))
计算数组中所有元素项的总和
let aa=arr.reduce((prev,item)=>{
return prev+item;
});
console.log(aa);
数组去重
let arr=[1,2,2,3,3,4,4,5,6,7,6,7,8]
for (let i=0;i<arr.length;i++){
for (let j=i+1;j<arr.length;j++){
if (arr[i]==arr[j]){
arr.splice(j,1);
j--;
}
}
}
console.log(arr);
return的作用
return 的作用是结束方法(终止函数)。
注意:
return 的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果。
在函数中,return后的语句都不会执行(函数在执行完 return 语句之后停止并立即退出函数)
如果return语句后不跟任何值,就相当于返回一个undefined
如果函数中不写return,则也会返回undefined
返回值可以是任意的数据类型,可以是对象,也可以是函数。
return 只能返回一个值。如果用逗号隔开多个值,则以最后一个为准。
fn() 和 fn 的区别【重要】
fn()
:调用函数。调用之后,还获取了函数的返回值。fn
:函数对象。相当于直接获取了整个函数对象。
break、continue、return 的区别
break :结束当前的循环体(如 for、while)
continue :跳出本次循环,继续执行下次循环(如 for、while)
return :1、退出循环。2、返回 return 语句中的值,同时结束当前的函数体内的代码,退出当前函数。
作用域链
只要是代码,就至少有一个作用域
写在函数内部的局部作用域
如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域
作用域链:内部函数访问外部函数的变量,采用的是链式查找的方式来决定取哪个值,这种结构称之为作用域链。查找时,采用的是就近原则。
函数内 this 的指向
根据函数的调用方式的不同,this 会指向不同的对象:
1.以函数的形式(包括普通函数、定时器函数、立即执行函数)调用时,this 的指向永远都是 window。比如
fun();
相当于window.fun();
2.以方法的形式调用时,this 指向调用方法的那个对象
3.以构造函数的形式调用时,this 指向实例对象
4.以事件绑定函数的形式调用时,this 指向绑定事件的对象
5.使用 call 和 apply 调用时,this 指向指定的那个对象
求数组的最大最小值
const arr=[3,34,7,8,4];
const max=Math.max.apply(Math,arr); //最大值
const min=Math.min.apply(Math,arr); //最小值
高阶函数
函数 A 接收函数 B 作为参数,或者把函数 C 作为返回值输出时,我们称 函数 A 为高阶函数
高阶函数是 对其他函数进行操作 的函数
闭包函数
指有权访问另一个函数作用域中变量的函数
如果这个作用域可以访问另外一个函数内部的局部变量,那就产生了闭包;而另外那个作用域所在的函数称之为闭包函数。
function fn(){
let a=20;
function fn2(){
console.log(a);
};
fn2()
}
fn();//此处fn为闭包函数
//另一种写法
function fn() {
let a = 20;
return function () {
console.log(a);
}
}
const foo = fn();
foo()
构造函数的概念
构造函数:是一种特殊的函数,主要用来创建和初始化对象,也就是为对象的成员变量赋初始值。它与 new
一起使用才有意义。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个构造函数里面。
类、实例
使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。
通过一个构造函数创建的对象,称为该类的实例。
instanceof
使用 instanceof 可以检查一个对象是否为一个类的实例
对象 instanceof 构造函数;
Json
JSON 的属性必须用双引号引起来
对象和 json 没有长度,json.length 的打印结果是 undefined。所以也就不能用 for 循环遍历
in 运算符
通过该运算符可以检查一个对象中是否含有指定的属性。如果有则返回 true,没有则返回 false。
'属性名' in 对象;
深拷贝和浅拷贝
拷贝引用的时候,是属于传址,而非传值。
深拷贝会把对象里所有的数据重新复制到新的内存空间,是最彻底的拷贝。
const myObj = {
name: 'qianguyihao',
age: 28,
};
// 【写法1】浅拷贝:把 myObj 拷贝给 obj1
const obj1 = {};
Object.assign(obj1, myObj);
// 【写法2】浅拷贝:把 myObj 拷贝给 obj2
const obj2 = Object.assign({}, myObj);
// 【写法3】浅拷贝:把 myObj 拷贝给 obj31。注意,这里的 obj31 和 obj32 其实是等价的,他们指向了同一个内存地址
const obj31 = {};
const obj32 = Object.assign(obj31, myObj);
深拷贝
let obj1 = {
name: 'qianguyihao',
age: 28,
info: {
desc: 'hello',
},
color: ['red', 'blue', 'green'],
};
let obj2 = {};
deepCopy(obj2, obj1);
console.log(obj2);
obj1.info.desc = 'github';
console.log(obj2);
// 方法:深拷贝
function deepCopy(newObj, oldObj) {
for (let key in oldObj) {
// 获取属性值 oldObj[key]
let item = oldObj[key];
// 判断这个值是否是数组
if (item instanceof Array) {
newObj[key] = [];
deepCopy(newObj[key], item);
} else if (item instanceof Object) {
// 判断这个值是否是对象
newObj[key] = {};
deepCopy(newObj[key], item);
} else {
// 简单数据类型,直接赋值
newObj[key] = item;
}
}
}
事件的三要素:
谁引发的后续事件,谁就是事件源
事件源:引发后续事件的html标签
事件:js已经定义好了
事件驱动程序:对样式和html的操作。也就是DOM
innerHTML和innerText的区别
value:标签的value属性。
innerHTML:双闭合标签里面的内容(包含标签)。
innerText:双闭合标签里面的内容(不包含标签)。(老版本的火狐用textContent)
无缝滚动轮播图
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>无标题文档</title>
<style type="text/css">
* {
padding: 0;
margin: 0;
list-style: none;
border: 0;
}
.all {
width: 500px;
height: 200px;
padding: 7px;
border: 1px solid #ccc;
margin: 100px auto;
position: relative;
}
.screen {
width: 500px;
height: 200px;
overflow: hidden;
position: relative;
}
.screen li {
width: 500px;
height: 200px;
overflow: hidden;
float: left;
}
.screen ul {
position: absolute;
left: 0;
top: 0px;
width: 3000px;
}
.all ol {
position: absolute;
right: 10px;
bottom: 10px;
line-height: 20px;
text-align: center;
}
.all ol li {
float: left;
width: 20px;
height: 20px;
background: #fff;
border: 1px solid #ccc;
margin-left: 10px;
cursor: pointer;
}
.all ol li.current {
background: yellow;
}
#arr {
display: none;
}
#arr span {
width: 40px;
height: 40px;
position: absolute;
left: 5px;
top: 50%;
margin-top: -20px;
background: #000;
cursor: pointer;
line-height: 40px;
text-align: center;
font-weight: bold;
font-family: '黑体';
font-size: 30px;
color: #fff;
opacity: 0.3;
border: 1px solid #fff;
}
#arr #right {
right: 5px;
left: auto;
}
</style>
<script>
window.onload = function () {
//需求:无缝滚动。
//思路:赋值第一张图片放到ul的最后,然后当图片切换到第五张的时候
//直接切换第六章,再次从第一张切换到第二张的时候先瞬间切换到
//第一张图片,然后滑动到第二张
//步骤:
//1.获取事件源及相关元素。(老三步)
//2.复制第一张图片所在的li,添加到ul的最后面。
//3.给ol中添加li,ul中的个数-1个,并点亮第一个按钮。
//4.鼠标放到ol的li上切换图片
//5.添加定时器
//6.左右切换图片(鼠标放上去隐藏,移开显示)
//1.获取事件源及相关元素。(老三步)
var all = document.getElementById("all");
var screen = all.firstElementChild || all.firstChild;
var imgWidth = screen.offsetWidth;
var ul = screen.firstElementChild || screen.firstChild;
var ol = screen.children[1];
var div = screen.lastElementChild || screen.lastChild;
var spanArr = div.children;
//2.复制第一张图片所在的li,添加到ul的最后面。
var ulNewLi = ul.children[0].cloneNode(true);
ul.appendChild(ulNewLi);
//3.给ol中添加li,ul中的个数-1个,并点亮第一个按钮。
for (var i = 0; i < ul.children.length - 1; i++) {
var olNewLi = document.createElement("li");
olNewLi.innerHTML = i + 1;
ol.appendChild(olNewLi)
}
var olLiArr = ol.children;
olLiArr[0].className = "current";
//4.鼠标放到ol的li上切换图片
for (var i = 0; i < olLiArr.length; i++) {
//自定义属性,把索引值绑定到元素的index属性上
olLiArr[i].index = i;
olLiArr[i].onmouseover = function () {
//排他思想
for (var j = 0; j < olLiArr.length; j++) {
olLiArr[j].className = "";
}
this.className = "current";
//鼠标放到小的方块上的时候索引值和key以及square同步
// key = this.index;
// square = this.index;
key = square = this.index;
//移动盒子
animate(ul, -this.index * imgWidth);
}
}
//5.添加定时器
var timer = setInterval(autoPlay, 1000);
//固定向右切换图片
//两个定时器(一个记录图片,一个记录小方块)
var key = 0;
var square = 0;
function autoPlay() {
//通过控制key的自增来模拟图片的索引值,然后移动ul
key++;
if (key > olLiArr.length) {
//图片已经滑动到最后一张,接下来,跳转到第一张,然后在滑动到第二张
ul.style.left = 0;
key = 1;
}
animate(ul, -key * imgWidth);
//通过控制square的自增来模拟小方块的索引值,然后点亮盒子
//排他思想做小方块
square++;
if (square > olLiArr.length - 1) {//索引值不能大于等于5,如果等于5,立刻变为0;
square = 0;
}
for (var i = 0; i < olLiArr.length; i++) {
olLiArr[i].className = "";
}
olLiArr[square].className = "current";
}
//鼠标放上去清除定时器,移开后在开启定时器
all.onmouseover = function () {
div.style.display = "block";
clearInterval(timer);
}
all.onmouseout = function () {
div.style.display = "none";
timer = setInterval(autoPlay, 1000);
}
//6.左右切换图片(鼠标放上去显示,移开隐藏)
spanArr[0].onclick = function () {
//通过控制key的自增来模拟图片的索引值,然后移动ul
key--;
if (key < 0) {
//先移动到最后一张,然后key的值取之前一张的索引值,然后在向前移动
ul.style.left = -imgWidth * (olLiArr.length) + "px";
key = olLiArr.length - 1;
}
animate(ul, -key * imgWidth);
//通过控制square的自增来模拟小方块的索引值,然后点亮盒子
//排他思想做小方块
square--;
if (square < 0) {//索引值不能大于等于5,如果等于5,立刻变为0;
square = olLiArr.length - 1;
}
for (var i = 0; i < olLiArr.length; i++) {
olLiArr[i].className = "";
}
olLiArr[square].className = "current";
}
spanArr[1].onclick = function () {
//右侧的和定时器一模一样
autoPlay();
}
function animate(ele, target) {
clearInterval(ele.timer);
var speed = target > ele.offsetLeft ? 10 : -10;
ele.timer = setInterval(function () {
var val = target - ele.offsetLeft;
ele.style.left = ele.offsetLeft + speed + "px";
if (Math.abs(val) < Math.abs(speed)) {
ele.style.left = target + "px";
clearInterval(ele.timer);
}
}, 10)
}
}
</script>
</head>
<body>
<div class="all" id='all'>
<div class="screen" id="screen">
<ul id="ul">
<li><img src="images/1.jpg" width="500" height="200"/></li>
<li><img src="images/2.jpg" width="500" height="200"/></li>
<li><img src="images/3.jpg" width="500" height="200"/></li>
<li><img src="images/4.jpg" width="500" height="200"/></li>
<li><img src="images/5.jpg" width="500" height="200"/></li>
</ul>
<ol>
</ol>
<div id="arr">
<span id="left"><</span>
<span id="right">></span>
</div>
</div>
</div>
</body>
</html>
scrollTop 和 scrollLeft 封装方法
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
body {
height: 6000px;
width: 5000px;
}
</style>
</head>
<body>
<script>
//需求:封装一个兼容的scroll().返回的是对象,用scroll().top获取scrollTop,用scroll().left获取scrollLeft
window.onscroll = function () {
// var myScroll = scroll();
// myScroll.top;
console.log(scroll().top);
console.log(scroll().left);
}
//函数封装
function scroll() {
return { //此函数的返回值是对象
left: window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop,
right: window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft
}
}
</script>
</body>
</html>
offset/scroll/client 的区别
区别1:宽高
offsetWidth = width + padding + border
offsetHeight = height + padding + border
scrollWidth = 内容宽度(不包含border)
scrollHeight = 内容高度(不包含border)
clientWidth = width + padding
clientHeight = height + padding
区别2:上左
offsetTop/offsetLeft:
- 调用者:任意元素。(盒子为主)
- 作用:距离父系盒子中带有定位的距离。
scrollTop/scrollLeft:
- 调用者:document.body.scrollTop(window调用)(盒子也可以调用,但必须有滚动条)
- 作用:浏览器无法显示的部分(被卷去的部分)。
clientY/clientX:
- 调用者:event
- 作用:鼠标距离浏览器可视区域的距离(左、上)
输出键盘被按下按键的编码
document.onkeydown = function(event) {
event = event || window.event;
console.log(event.keyCode);
};
jQuery的入口函数
jQuery占用了我们两个变量:$
和 jQuery($===jQuery)
//1.文档加载完毕,图片不加载的时候,就可以执行这个函数。
$(document).ready(function () {
alert(1);
})
//2.文档加载完毕,图片不加载的时候,就可以执行这个函数。
$(function () {
alert(1);
});
//3.文档加载完毕,图片也加载完毕的时候,在执行这个函数。
$(window).ready(function () {
alert(1);
})
let 和 const 的特点【重要】
- 不属于顶层对象 Window
- 不允许重复声明
- 不存在变量提升
- 暂时性死区
- 支持块级作用域
相反, 用var
声明的变量:存在变量提升、可以重复声明、没有块级作用域。
const 来定义常量 (内存地址不能变化的量)
示例
// 注释方式和C很像,这是单行注释
/* 这是多行
注释 */
// 语句可以以分号结束
doStuff();
// ... 但是分号也可以省略,每当遇到一个新行时,分号会自动插入(除了一些特殊情况)。
doStuff()
// 因为这些特殊情况会导致意外的结果,所以我们在这里保留分号。
///////////////////////////////////
// 1. 数字、字符串与操作符
// Javascript 只有一种数字类型(即 64位 IEEE 754 双精度浮点 double)。
// double 有 52 位表示尾数,足以精确存储大到 9✕10¹⁵ 的整数。
3; // = 3
1.5; // = 1.5
// 所有基本的算数运算都如你预期。
1 + 1; // = 2
0.1 + 0.2; // = 0.30000000000000004
8 - 1; // = 7
10 * 2; // = 20
35 / 5; // = 7
// 包括无法整除的除法。
5 / 2; // = 2.5
// 位运算也和其他语言一样;当你对浮点数进行位运算时,
// 浮点数会转换为*至多* 32 位的无符号整数。
1 << 2; // = 4
// 括号可以决定优先级。
(1 + 3) * 2; // = 8
// 有三种非数字的数字类型
Infinity; // 1/0 的结果
-Infinity; // -1/0 的结果
NaN; // 0/0 的结果
// 也有布尔值。
true;
false;
// 可以通过单引号或双引号来构造字符串。
'abc';
"Hello, world";
// 用!来取非
!true; // = false
!false; // = true
// 相等 ===
1 === 1; // = true
2 === 1; // = false
// 不等 !=
1 !== 1; // = false
2 !== 1; // = true
// 更多的比较操作符
1 < 10; // = true
1 > 10; // = false
2 <= 2; // = true
2 >= 2; // = true
// 字符串用+连接
"Hello " + "world!"; // = "Hello world!"
// 字符串也可以用 < 、> 来比较
"a" < "b"; // = true
// 使用“==”比较时会进行类型转换...
"5" == 5; // = true
null == undefined; // = true
// ...除非你是用 ===
"5" === 5; // = false
null === undefined; // = false
// ...但会导致奇怪的行为
13 + !0; // 14
"13" + !0; // '13true'
// 你可以用`charAt`来得到字符串中的字符
"This is a string".charAt(0); // = 'T'
// ...或使用 `substring` 来获取更大的部分。
"Hello world".substring(0, 5); // = "Hello"
// `length` 是一个属性,所以不要使用 ().
"Hello".length; // = 5
// 还有两个特殊的值:`null`和`undefined`
null; // 用来表示刻意设置的空值
undefined; // 用来表示还没有设置的值(尽管`undefined`自身实际是一个值)
// false, null, undefined, NaN, 0 和 "" 都是假的;其他的都视作逻辑真
// 注意 0 是逻辑假而 "0"是逻辑真,尽管 0 == "0"。
///////////////////////////////////
// 2. 变量、数组和对象
// 变量需要用`var`关键字声明。Javascript是动态类型语言,
// 所以你无需指定类型。 赋值需要用 `=`
var someVar = 5;
// 如果你在声明时没有加var关键字,你也不会得到错误...
someOtherVar = 10;
// ...但是此时这个变量就会在全局作用域被创建,而非你定义的当前作用域
// 没有被赋值的变量都会被设置为undefined
var someThirdVar; // = undefined
// 对变量进行数学运算有一些简写法:
someVar += 5; // 等价于 someVar = someVar + 5; someVar 现在是 10
someVar *= 10; // 现在 someVar 是 100
// 自增和自减也有简写
someVar++; // someVar 是 101
someVar--; // 回到 100
// 数组是任意类型组成的有序列表
var myArray = ["Hello", 45, true];
// 数组的元素可以用方括号下标来访问。
// 数组的索引从0开始。
myArray[1]; // = 45
// 数组是可变的,并拥有变量 length。
myArray.push("World");
myArray.length; // = 4
// 在指定下标添加/修改
myArray[3] = "Hello";
// javascript中的对象相当于其他语言中的“字典”或“映射”:是键-值对的无序集合。
var myObj = {key1: "Hello", key2: "World"};
// 键是字符串,但如果键本身是合法的js标识符,则引号并非是必须的。
// 值可以是任意类型。
var myObj = {myKey: "myValue", "my other key": 4};
// 对象属性的访问可以通过下标
myObj["my other key"]; // = 4
// ... 或者也可以用 . ,如果属性是合法的标识符
myObj.myKey; // = "myValue"
// 对象是可变的;值也可以被更改或增加新的键
myObj.myThirdKey = true;
// 如果你想要获取一个还没有被定义的值,那么会返回undefined
myObj.myFourthKey; // = undefined
///////////////////////////////////
// 3. 逻辑与控制结构
// 本节介绍的语法与Java的语法几乎完全相同
// `if`语句和其他语言中一样。
var count = 1;
if (count == 3){
// count 是 3 时执行
} else if (count == 4){
// count 是 4 时执行
} else {
// 其他情况下执行
}
// while循环
while (true) {
// 无限循环
}
// Do-while 和 While 循环很像 ,但前者会至少执行一次
var input;
do {
input = getInput();
} while (!isValid(input))
// `for`循环和C、Java中的一样:
// 初始化; 继续执行的条件; 迭代。
for (var i = 0; i < 5; i++){
// 遍历5次
}
// && 是逻辑与, || 是逻辑或
if (house.size == "big" && house.colour == "blue"){
house.contains = "bear";
}
if (colour == "red" || colour == "blue"){
// colour是red或者blue时执行
}
// && 和 || 是“短路”语句,它在设定初始化值时特别有用
var name = otherName || "default";
// `switch`语句使用`===`检查相等性。
// 在每一个case结束时使用 'break'
// 否则其后的case语句也将被执行。
grade = 'B';
switch (grade) {
case 'A':
console.log("Great job");
break;
case 'B':
console.log("OK job");
break;
case 'C':
console.log("You can do better");
break;
default:
console.log("Oy vey");
break;
}
///////////////////////////////////
// 4. 函数、作用域、闭包
// JavaScript 函数由`function`关键字定义
function myFunction(thing){
return thing.toUpperCase();
}
myFunction("foo"); // = "FOO"
// 注意被返回的值必须开始于`return`关键字的那一行,
// 否则由于自动的分号补齐,你将返回`undefined`。
// 在使用Allman风格的时候要注意.
function myFunction()
{
return // <- 分号自动插在这里
{
thisIsAn: 'object literal'
}
}
myFunction(); // = undefined
// javascript中函数是一等对象,所以函数也能够赋给一个变量,
// 并且被作为参数传递 —— 比如一个事件处理函数:
function myFunction(){
// 这段代码将在5秒钟后被调用
}
setTimeout(myFunction, 5000);
// 注意:setTimeout不是js语言的一部分,而是由浏览器和Node.js提供的。
// 函数对象甚至不需要声明名称 —— 你可以直接把一个函数定义写到另一个函数的参数中
setTimeout(function(){
// 这段代码将在5秒钟后被调用
}, 5000);
// JavaScript 有函数作用域;函数有其自己的作用域而其他的代码块则没有。
if (true){
var i = 5;
}
i; // = 5 - 并非我们在其他语言中所期望得到的undefined
// 这就导致了人们经常使用的“立即执行匿名函数”的模式,
// 这样可以避免一些临时变量扩散到全局作用域去。
(function(){
var temporary = 5;
// 我们可以访问修改全局对象("global object")来访问全局作用域,
// 在web浏览器中是`window`这个对象。
// 在其他环境如Node.js中这个对象的名字可能会不同。
window.permanent = 10;
})();
temporary; // 抛出引用异常ReferenceError
permanent; // = 10
// javascript最强大的功能之一就是闭包。
// 如果一个函数在另一个函数中定义,那么这个内部函数就拥有外部函数的所有变量的访问权,
// 即使在外部函数结束之后。
function sayHelloInFiveSeconds(name){
var prompt = "Hello, " + name + "!";
// 内部函数默认是放在局部作用域的,
// 就像是用`var`声明的。
function inner(){
alert(prompt);
}
setTimeout(inner, 5000);
// setTimeout是异步的,所以 sayHelloInFiveSeconds 函数会立即退出,
// 而 setTimeout 会在后面调用inner
// 然而,由于inner是由sayHelloInFiveSeconds“闭合包含”的,
// 所以inner在其最终被调用时仍然能够访问`prompt`变量。
}
sayHelloInFiveSeconds("Adam"); // 会在5秒后弹出 "Hello, Adam!"
///////////////////////////////////
// 5. 对象、构造函数与原型
// 对象可以包含方法。
var myObj = {
myFunc: function(){
return "Hello world!";
}
};
myObj.myFunc(); // = "Hello world!"
// 当对象中的函数被调用时,这个函数可以通过`this`关键字访问其依附的这个对象。
myObj = {
myString: "Hello world!",
myFunc: function(){
return this.myString;
}
};
myObj.myFunc(); // = "Hello world!"
// 但这个函数访问的其实是其运行时环境,而非定义时环境,即取决于函数是如何调用的。
// 所以如果函数被调用时不在这个对象的上下文中,就不会运行成功了。
var myFunc = myObj.myFunc;
myFunc(); // = undefined
// 相应的,一个函数也可以被指定为一个对象的方法,并且可以通过`this`访问
// 这个对象的成员,即使在函数被定义时并没有依附在对象上。
var myOtherFunc = function(){
return this.myString.toUpperCase();
}
myObj.myOtherFunc = myOtherFunc;
myObj.myOtherFunc(); // = "HELLO WORLD!"
// 当我们通过`call`或者`apply`调用函数的时候,也可以为其指定一个执行上下文。
var anotherFunc = function(s){
return this.myString + s;
}
anotherFunc.call(myObj, " And Hello Moon!"); // = "Hello World! And Hello Moon!"
// `apply`函数几乎完全一样,只是要求一个array来传递参数列表。
anotherFunc.apply(myObj, [" And Hello Sun!"]); // = "Hello World! And Hello Sun!"
// 当一个函数接受一系列参数,而你想传入一个array时特别有用。
Math.min(42, 6, 27); // = 6
Math.min([42, 6, 27]); // = NaN (uh-oh!)
Math.min.apply(Math, [42, 6, 27]); // = 6
// 但是`call`和`apply`只是临时的。如果我们希望函数附着在对象上,可以使用`bind`。
var boundFunc = anotherFunc.bind(myObj);
boundFunc(" And Hello Saturn!"); // = "Hello World! And Hello Saturn!"
// `bind` 也可以用来部分应用一个函数(柯里化)。
var product = function(a, b){ return a * b; }
var doubler = product.bind(this, 2);
doubler(8); // = 16
// 当你通过`new`关键字调用一个函数时,就会创建一个对象,
// 而且可以通过this关键字访问该函数。
// 设计为这样调用的函数就叫做构造函数。
var MyConstructor = function(){
this.myNumber = 5;
}
myNewObj = new MyConstructor(); // = {myNumber: 5}
myNewObj.myNumber; // = 5
// 每一个js对象都有一个‘原型’。当你要访问一个实际对象中没有定义的一个属性时,
// 解释器就回去找这个对象的原型。
// 一些JS实现会让你通过`__proto__`属性访问一个对象的原型。
// 这虽然对理解原型很有用,但是它并不是标准的一部分;
// 我们后面会介绍使用原型的标准方式。
var myObj = {
myString: "Hello world!"
};
var myPrototype = {
meaningOfLife: 42,
myFunc: function(){
return this.myString.toLowerCase()
}
};
myObj.__proto__ = myPrototype;
myObj.meaningOfLife; // = 42
// 函数也可以工作。
myObj.myFunc() // = "hello world!"
// 当然,如果你要访问的成员在原型当中也没有定义的话,解释器就会去找原型的原型,以此类推。
myPrototype.__proto__ = {
myBoolean: true
};
myObj.myBoolean; // = true
// 这其中并没有对象的拷贝;每个对象实际上是持有原型对象的引用。
// 这意味着当我们改变对象的原型时,会影响到其他以这个原型为原型的对象。
myPrototype.meaningOfLife = 43;
myObj.meaningOfLife; // = 43
// 我们知道 `__proto__` 并非标准规定,实际上也没有标准办法来修改一个已存在对象的原型。
// 然而,我们有两种方式为指定原型创建一个新的对象。
// 第一种方式是 Object.create,这个方法是在最近才被添加到Js中的,
// 因此并不是所有的JS实现都有这个方法
var myObj = Object.create(myPrototype);
myObj.meaningOfLife; // = 43
// 第二种方式可以在任意版本中使用,不过必须通过构造函数。
// 构造函数有一个属性prototype。但是它 *不是* 构造函数本身的原型;相反,
// 是通过构造函数和new关键字创建的新对象的原型。
MyConstructor.prototype = {
myNumber: 5,
getMyNumber: function(){
return this.myNumber;
}
};
var myNewObj2 = new MyConstructor();
myNewObj2.getMyNumber(); // = 5
myNewObj2.myNumber = 6
myNewObj2.getMyNumber(); // = 6
// 字符串和数字等内置类型也有通过构造函数来创建的包装类型
var myNumber = 12;
var myNumberObj = new Number(12);
myNumber == myNumberObj; // = true
// 但是它们并非严格等价
typeof myNumber; // = 'number'
typeof myNumberObj; // = 'object'
myNumber === myNumberObj; // = false
if (0){
// 这段代码不会执行,因为0代表假
}
// 不过,包装类型和内置类型共享一个原型,
// 所以你实际可以给内置类型也增加一些功能,例如对string:
String.prototype.firstCharacter = function(){
return this.charAt(0);
}
"abc".firstCharacter(); // = "a"
// 这个技巧经常用在“代码填充”中,来为老版本的javascript子集增加新版本js的特性,
// 这样就可以在老的浏览器中使用新功能了。
// 比如,我们知道Object.create并没有在所有的版本中都实现,
// 但是我们仍然可以通过“代码填充”来实现兼容:
if (Object.create === undefined){ // 如果存在则不覆盖
Object.create = function(proto){
// 用正确的原型来创建一个临时构造函数
var Constructor = function(){};
Constructor.prototype = proto;
// 之后用它来创建一个新的对象
return new Constructor();
}
}