js学习笔记


张登友,张登友的博客,张登友的网站——

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
  1. 简单的精度问题:可以使用 toFix() 方法进行小数的截取。

  2. 数学运算的开源库

  • decimal.js:属于很全面的运算库,文件很大,压缩后的文件就有 500kb。如果你的项目涉及到大型的复杂运算,可以使用 Math.js。
  • Math.js:属于轻量的运算库,压缩后的文件只有 32kb。大多数项目的数学运算,使用 decimal.js 足够了。

Null:空对象

  • Null 类型的值只有一个,就是 null。比如 let a = null

  • 使用 typeof 检查一个 null 值时,会返回 object。null 虽然是一个单独的数据类型,但null 相当于是一个 object,只不过地址为空(空指针)而已

undefined:未定义类型

  1. 变量已声明但未赋值时,此时它的值就是 undefined
  2. 变量未声明(未定义)时,此时,如果用 typeof 检查这个变量时,会返回 undefined
  3. 函数无返回值时,那么这个函数的返回值就是 undefined。也可以这样理解:在定义一个函数时,如果末尾没有 return 语句,那么,其实就是 return undefined
  4. 调用函数时未传参,那么这个参数的值就是 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

  1. 使用Number()函数

    • 字符串—>数字

      • 如果字符串是一个空串或者是一个全是空格的字符串,则转换为 0

      • 只要字符串中包含了其他非数字的内容(小数点按数字来算),则转换为 NaN。使用 Number() 函数之后,如果无法转换为数字,就会转换为 NaN

      • 布尔—>数字

        • true转成1
        • false转成0
    • null—>数组,结果为0

    • undefined—>数字,结果为NAN

  2. 隐式类型转换:正负号 +a-a

    注意,这里说的是正号/负号,不是加号/减号。

    任何值做+a-a运算时, 内部调用的是 Number() 函数。不会改变原数值。

  3. 使用 parseInt()函数:字符串 -> 整数

  • parseInt()**:将传入的数据当作字符串**来处理,从左至右提取数值, 一旦遇到非数值就立即停止;停止时如何还没有提取到数值, 那么就返回NaN。

  • 转换情况有以下几种:

    1. 字符串 –> 数字

      (1)只保留字符串最开头的数字,后面的中文自动消失

      (2)如果字符串不是以数字开头,则转换为 NaN

      (3)如果字符串是一个空串或者是一个全是空格的字符串,转换时会报错

    2. Boolean –> 数字,结果为:NaN

    3. Null –> 数字,结果为:NaN

    4. Undefined –> 数字,结果为:NaN

  • Number(true)parseInt(true)/parseFloat(true) 的区别:

    • Number() :转换为数字;如果转换不了则返回 NaN。

    • parseInt()/parseFloat() :提取出最前面的数字部分;没提取出来,那就返回 NaN。

  • parseInt()、parseFloat()会将传入的数据当作字符串来处理。也就是说,如果对非 String使用 parseInt()、parseFloat(),会先将其转换为 String 然后再操作。【重要】

  1. parseFloat()函数:字符串 –> 浮点数(小数)

    parseFloat()的作用是:将字符串转换为浮点数

转换为 Boolean

  1. 数字 –> 布尔。 0 和 NaN是 false,其余的都是 true。比如 Boolean(NaN)的结果是 false。

  2. 字符串 —> 布尔。空串是false,其余的都是 true。全是空格的字符串,转换结果也是 true。字符串'0'的转换结果也是 true。

  3. null 和 undefined 都会转换为 false。

  4. 引用数据类型会转换为 true。空数组[]和空对象{}转换结果也是 true

    const 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
    
    ##### 比较运算符
    
    - 大于号
    
    - <	小于号
    
    - = 	大于或等于
    
    - <=  小于或等于
    
    - == 	等于
    
    - === 全等于
    
    - !=	不等于
    
    - !== 不全等于
    
    - ```js
      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

  • ()

  • ++--

  • !~+(单目)、-(单目)、typeofvoiddelete

  • */%

  • +(双目)、-(双目)

  • <<>>>>>

  • 关系运算符:<<=>>=

  • ==!=====!==

  • &

  • ^

  • |

  • &&

  • ||

  • ?:

  • =+=-=*=/=%=<<=>>=>>>=&=^=|=

  • ,

注意:逻辑与 && 比逻辑或 || 的优先级更高。

备注:如果不清楚哪个优先级更高,可以把括号用上。

接口的返回码 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。比如consoledocument

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 来定义常量 (内存地址不能变化的量)


文章作者: 张登友
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张登友 !
  目录