JS基础及高级程序设计笔记

js的数据类型

  • String 字符串
  • Number 数值
  • Boolean 布尔值
  • Null 空值
  • Undefined 未定义
  • Object 对象

String、Number、Boolean、Null、Undefined属于基本数据类型,而Object属于引用数据类型

  1. String 需要用引号引起来

  2. Number包括整数和浮点数

typeof

使用typeof可以检查一个变量的类型,检查字符串时返回string,检查数字时返回number

JS中最大的数字Number.MAX_VALUE=1.7976931348623157e+308,当超过了最大值时将返回 Infinity,它表示正无穷。Infinity也是一个字面量,用typeof检查Infinity时会返回一个number类型

当非数字值进行运算时,返回的结果为NaN(Not a Number)表示不是一个数字。NaN也是一个字面量,用typeof检查NaN时会返回一个number类型

JS中最小的正值Number.MIN_VALUE=5e-324

  1. Boolean只有两个值true和false,用typeof检查布尔值时会返回boolean
  2. Null只有一个值null,null专门用来表示一个空的对象,使用typeof检查null时会返回一个object
  3. Undefined只有一个值undefined,当声明了一个变量但不给值时,它的值就是undefined

parseInt函数在不同浏览器在执行parseInt(070)会得到不同的值,因为浏览器把070当成的进制不同,为保证一致,这种多进制解析时使用parseInt(070, 10)的方式解析

Number()转换数字时带有其他符号将返回NaN

parseInt()则会解析开头是数字的字符串,如果开头就是其他字符则返回NaN

Boolean()当传递数字时0和NaN返回fasle其他都是true;当传递字符串时除了空串其余都是true,传递null和undefined时都是false,对象也会转成true

算术运算

当对非Number类型的值进行运算时,会将这些值转换为Number然后再计算,true表示1,false和null表示0

然和数和NaN计算都是NaN,和undefined计算也是NaN

字符串相加时,是将两个字符串进行拼接(任何值和字符串相加都会将另一个值转换为字符串再进行拼接)

注意:除了加法遇到字符串时会转换成字符串拼接,其余运算符都是将值转为number再进行运算

可以利用算术运算符来做类型转换,字符串-0(或*1或/1)结果类型是number(原理就是运用了Number()函数),数组+“”结果类型是string(原理就是运用了String()函数)

一目运算符 -/+ 可以将任意类型的值转换成number

a++ = a 表达式的值等于原值

++a = a+1 表达式的值等于原值加1

// 练习
var n1 = 10, n2 = 20;
var n = n1++;
console.log('n=' + n) // n=10
console.log('n1=' + n1); // n1=11
n = ++n1;
console.log('n=' + n) // n=12
console.log('n1=' + n1); // n1=12
n = n2--;
console.log('n=' + n) // n=20
console.log('n2=' + n2); // n2=19
n = --n2;
console.log('n=' + n) // n=18
console.log('n2=' + n2); // n2=18

逻辑运算符

&& 两个数字进行与运算时,如果两个都为true,则返回后面的数字。如果一个数字为0,则怎么都返回0。如果两个值都为false则返回前一个值

&&运算时会将两边的值转换成boolean类型再计算,如果为真则返回后面的值,如果为假则返回前面的值(可以用逻辑短路进行理解)。逻辑或的情况和这种一样

关系运算符

大于小于等于计算时,会将两边的值转换成number后再计算

任何值和NaN做任何比较都是false

特殊情况:如果关系运算符两侧都是字符串时,则不会传换成number,而是去比较两个字符串的unicode编码的值(比较时是按照字符串的一个一个字符进行比较的)

注意:比较两个字符串类型的数字时,可能会达不到预期的效果,如:“123” > “5” 返回的是false。如果要想得到正确的结果,需要将任意一遍转成number既可,如在“123”前加+号,+“123” > “5” 返回true

在使用==相等运算符时,undefined是等于null的,NaN不和任何值相等(包括它本身)

如果要判断一个值是否为NaN的时候可以使用isNaN()函数来判断

全等 ===

全等会判断值的类型,如果类型不同直接返回false,如果相同再去判断值

使用全等比较undefined和null时返回的是false

编码

在js中使用unicode编码,\u四位编码,如"\u2620"

在html中使用unicode编码,&#四位编码;,如

& #9760;

,注意js中的编码是十六进制的,在html中使用时需要转换成十进制的数字

代码块

JS中的代码块只具有分分组的作用,代码块中的变量在外面是完全可见的

switch

JS在执行switch语句的判断时使用的是全等判断

对象

  1. 内建对象,由ES标准定义的对象,在任何的ES的实现中都可以使用,比如:Math、String、Number、Boolean、Function、Object……

  2. 宿主对象,由JS的运行环境提供的对象,目前来讲主要指浏览器提供的对象,比如:BOM(浏览器对象模型)、DOM(文档对象模型)

  3. 自定义对象,由开发人员创建的对象

创建对象

var obj = new Object(); // 或 var obj = {};

为对象添加属性,语法:对象.属性名 = 属性值; ,或var obj = {属性名:属性值};

如果读取对象中没有的属性将会返回undefined

删除对象的属性,语法:delete 对象.属性名;

对象的属性名不强制要求遵守命名规范,什么名称都可以,但最好按照规范去做。如果要使用特殊的属性名,不能采用.的方式来操作,如 obj.123 = 123;这个做法会报语法错误,但是可以使用obj[“123”] = 123;来让obj拥有123属性,读取这种属性时也得采用中括号的形式

修改基础数据类型的值不会传递到其他变量上,修改对象会,如下案例

var a = 123;
var b = a;
a++;
console.log(a); // 124
console.log(b); // 123
// 修改对象的属性
var obj1 = new Object()
obj1.name = "zs";
var obj2 = obj1;
console.log(obj2.name); // zs
obj1.name = "ls";
console.log(obj2.name); // ls

如果将对象设置为null,不会影响其他原来引用之前内容的对象

in

in检查一个对象中是否有指定的属性,语法:“属性名” in 对象 ,如果有返回true

函数

函数也是一个对象

创建函数对象

var fun = new Function();

用typeof检查一个函数对象时会返回function

可以将封装的代码以字符串的形式传到给构造函数

var fun = new Function("console.log(123);");

使用函数声明创建函数

语法: function 函数名([形参1,…]) { 语句… }

使用函数表达式创建函数(匿名函数)

var 函数名 = function([形参1,...]) {
    语句...
};

如果函数需要产生但是没有传递时,形参的值会设置为undefined

函数传参时会传递一个隐含的参数this,直接调用函数时this是window对象,使用自定义对象调用函数时this是调用函数的那个自定义对象

函数嵌套

function fun1() {
    function fun2() {
        console.log(123)
    };
    return fun2; // 返回fun2函数对象
}
var a = fun1(); // 调用了fun1函数,返回了fun2函数对象
a(); // 执行fun2函数
fun1()(); // 先调用fun1拿到返回值后再调用返回值fun2
// a() 等价于 fun1()()

调用匿名函数

function() { alert(1) }; // 这个写法会报错,后面的大括号会被当场一个代码块,报错说函数没有函数名
(function() { alert(1) }); // 用圆括号包裹起来即可
// 如果要调用匿名函数只需要在后面再加上一对圆括号即可
(function() {alert(1)})(); //这种写法也叫立即执行函数,这种函数只会执行一次,如果有产生则在后面的括号中传递

遍历对象的属性

var obj = {
    name: 'zs',
    age: 18
};
for (var f in obj) {
    console.log(f); // 输出name、age
   	// 如果要取遍历的值,则使用
    console.log(obj[f]); // 输出zs、18
}

变量作用域

  1. 全局作用域
  • 直接编写在script标签中的JS代码,都在全局作用域
  • 全局作用域在页面打开时创建,在页面关闭时销毁
  • 在全局作用域中有一个全局对象window
    • 创建的变量都会作为window对象的属性保存
    • 创建的函数都会作为window对象的方法保存
  1. 函数作用域
  • 调用函数时创建函数作用域,函数执行完毕后,函数作用域销毁
  • 每调用一次函数就会创建一个新的作用域,他们之间是相互独立的
  • 函数作用域中可以访问全局作用域中的变量
  • 函数作用域操作时,先查找函数作用域中的变量,如果没有再去全局作用域中找(如果是函数前提则是上一级作用域)。如果在全局作用域中依然没有找到,则会报错ReferenceError
  • 函数作用域中也有变量的声明提前

变量的声明提前

使用var关键字声明的变量,会在所以代码执行之前被声明

console.log(a);
var a = 123;
// 上面代码等同于
var a;
console.log(a);
a = 123;
// 因为a语句被创建了,所以执行上面代码不会报错,只是a还没有被赋值是一个undefined

如果函数以声明的方式写在后面,但是在前面调用了该函数,此时调用是成功的。

如果以函数表达式的方式创建一个函数对象,在对象前面调用了函数则会报错,说undefined不会一个函数。

构造函数(类)

构造函数就是一个普通函数,但是在调用的时候使用new关键字来创建,此时创建的就是一个对象,而这个对象的名称就是函数的名称。这个函数也被称为类

创建方式

// 创建构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function() {
        console.log(this.name)
    }
}
// 创建对象
var per = new Person('zs', 18);
console.log(per); // Person {name: "zs", age: 18, sayName: ƒ  注意这里打印的类型已经变成Person了而不是Object
per.sayName();

instanceof

使用instanceof可以检查一个对象是否是一个构造函数的实例

原型 prototype

每创建一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是所谓的原型对象

如果函数作为普通函数调用prototype没有任何作用

当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含属性,该隐含属性指向构造函数的原型对象,我们可以通过 __proto__来访问该属性

原型的使用

function MyClass() {
	this.sayHello = function() { console.log('hello') }
}
var c1 = new MyClass();
var c2 = new MyClass();
console.log(c1.sayHello == c2.sayHello); // false
// 上面的情况是每创建一个MyClass对象时,都会新创建一个sayHello函数,这个做法无疑是会消耗一定内存的
// 如果在创建一个公共的各个对象之间都不相互影响的方法的时候,可以使用原型来创建这些函数,如下
MyClass.prototype.sayHi = function() { console.log('hi') };
console.log(c1.sayHi == c2.sayHi); // true
// 此时new出来的MyClass对象就都有了sayHi函数,而且这个函数只存在一份,不会因为创建多个对象而生成多个函数
c1.sayHi(); // 这种方式是隐式的调用原型中的属性或方法
c1.__proto__.sayHi(); // 这个是显示的调用原型

hasOwnProperty()

hasOwnProperty用于查看对象自身有没有该属性,这个方法不会检查原型中含有的属性

重写toString

function A(name) {
    this.name = name;
}
A.prototype.toString = function() {
    return "我叫" + this.name;
}
var a = new A('zs');
console.log(a);

垃圾回收(GC)

JS会自动进行垃圾回收,只需要将对象设置为null即可

数组

数组也是一个对象,与普通对象不同的是,普通对象使用只发出作为属性名,而数组使用数字作为索引操作元素

创建数组

语法:var arr = new Array(); 或 var arr = [];

使用new Array()创建数组时,可以传递参数,如果参数是一个数字表示数组的初始长度,如果是多个值则是数组中的元素

向数组中添加元素

语法:数组[索引] = 值

读取数组中的元素,如果读取的索引不存在,不会报错而是返回undefined

语法:数组[索引]

数组的长度,获取到的是数组的最大索引加1,数组的长度是可以设置的

语法:数组.length

在数组的最后添加元素,arr[arr.length] = 值,因为length是最大索引加1所以可以实现往数组最后添加元素

数组常用函数

  • push([值1,值2…])

    • 向数组的末尾添加一个或多个元素,并返回数组的新长度
  • pop()

    • 删除数组的最后一个元素,并将该元素返回
  • unshift([值1,值2…])

    • 向数组开头添加一个或多个元素,并返回数组的新长度
  • shift()

    • 删除数组的第一个元素,并将该元素返回
  • foreach(function([value, index, arr]))

    • 遍历数组(IE8以上才支持),在传递的函数中默认传入三个参数,value当前数组值,当前索引,当前遍历的数组对象
  • slice([start, end])

    • 从数组中根据索引提取元素,从start索引截取到end索引(左闭右开),该方法不会改变原数组而是将截取到的元素放到新数组中返回。如果值传递一个参数则表示从该参数索引位置到末尾
  • splice([index, length, [值1, 值2…]])

    • 删除数组中的指定元素,该方法会将原数组中的元素删除,并将删除的元素返回。index开始删除的索引,length删除的个数
    • 如果传递第三个及以上的值,则会把索引指定位置的元素删除并将后面的值1, 值2…插入到原数组中
  • concat([值1, 值2…])

    • 连接两个或多个数组,并将新数组返回
  • join([sep])

    • 将数组中的值拼接到字符串,中间用sep参数连接默认是逗号
  • reverse()

    • 反转数组,该方法直接修改原数组
  • sort(function(before, after) { return number; })

    • 根据Unicode编码排序,直接在原数组中排序
    • 直接对数字排序会造成11排到2的前面
    • 在sort中传递一个回调函数并返回一个数字可以自定义排序方式
      • 如果在回调函数中返回一个大于0的数,则两个元素交换位置

数组去重

var arr = [1, 2, 2, 3, 1, 2, 3, 4];
for (var i = 0; i < arr.length; i++) {
    for (var j = i + 1; j < arr.length; j++) {
        if (arr[i] == arr[j]) {
            arr.splice(j, 1);
            j--;
        }
    }
}
console.log(arr);

call()和apply()

这两个方法都是函数对象的方式,需要通过函数对象来调用,当对函数调用call()和apply()都会调用函数执行

在调用call()和apply()时,可以将一个对象指定为第一个参数,此时这个对象将会称为函数执行时的this

call()方法可以将实参在对象之后依次传递

apply()方法需要将实参封装到一个数组中统一传递

this的情况

  • 以函数形式调用时,this永远都是window
  • 以方法的形式调用时,this是调用方法的对象
  • 以构造函数的形式调用时,this是新创建的那个对象
  • 使用call和apply调用时,this是指定的那个对象

arguments

在调用函数时,浏览器每次都会传递两个隐藏的参数

  1. 函数的上下文对象this
  2. 封装实参的对象arguments
    • arguments是一个类数组对象,它可以通过索引来操作,也可以获取长度
    • 在调用函数时,传递的参数都会在argument保存
    • arguments.length可以用来获取实参的长度
    • 即使不定义形参,也可以通过arguments来使用实参,如arguments[0]
    • arguments有一个属性callee
      • 这个属性对应一个函数对象,就是当前执行的函数对象

Date对象

Date是JS封装时间相关操作的类(它是一个构造函数

创建一个Date对象

var d = new Date();
console.log(d); // 输出的时间是代码执行new Date()这行的时间

创建一个指定时间的Date对象

// 需要在沟站是中传递一个表示时间的字符串作为参数
// 日期的格式: 月份/日期/年份 时:分:秒
var d = new Date("07/05/2021 18:39:00");
console.log(d); // 打印的时间就是指定的时间

getDate() 获取Date对象的日期

getDay() 获取Date对象的星期,周日为0,周六为6

getMonth() 获取Date对象的月份,一月为0,十二月为11

getFullYear() 获取Date对象的年份

getTime() 获取Date对象的时间戳,单位是毫秒

Date.now() 获取当前时间戳

Math对象

Math和其他对象不同,它不是一个构造函数

它属于一个工具类不用创建对象,它里边封装了数学运算相关的属性和方法

abs() 计算一个数的绝对值

ceil() 向上取整,只要有小数点就在整数位加1

floor() 向下取整,小数部分直接舍去

round() 四舍五入

random() 随机生成0-1之间的随机数

生成x-y之间的随机数

Math.round(Math.random()*(y-x) + x)

max() 获取多个数中的最大值

min() 获取多个数中的最小值

pow(x, y) 返回x的y次幂

sqrt() 对一个数进行开方

包装类

JS提供了三个包装类,通过包装类可以将基本数据类型的数据转换为对象

new String(“123”)

new Number(123)

new Boolean(true)

包装类开发时一般不使用

当我们对一些基本数据类型的值去调用属性和方法时,浏览器会临时使用包装类来将其转换为对象,然后再调用对象的属性和方法,调用完成之后再转回基本数据类型

var s = "123";
s.hello = "hello";
console.log(s.hello); // undefined

字符串相关方法

在底层字符串是以数组的形式保存的

  • chartAt()

    • 返回字符串中指定位置的字符,和中括号获取字符串中的字符一样
  • charCodeAt()

    • 返回字符串中指定位置字符的Unicode编码
  • String.fromCharCode()

    • 根据字符编码获取字符,使用的是十进制数,如果要用Unicode的十六进制去查询则需要加上0x
  • concat()

    • 连接两个或多个字符串
  • indexOf()

    • 检索一个字符串中指定字符的索引,找到了该字符立刻返回索引,如果没有找到则返回-1。可以传入第二个参数:开始查找的索引,当指定了开始查找的索引时,检索就从该位置开始进行检索
  • lastIndexOf()

    • 检索一个字符在字符串中最后一次出现的索引
  • slice(start, end)

    • 从字符串中截取指定的内容(支持切片),同数组的操作方式一样
  • subString(start, end)

    • 从字符串中截取指定的内容,基本和slice使用方法一样,不同的是subString不支持传递负值
      • 如果传递负值自动转换成0
      • 如果第二个参数小于第一个参数自动交换参数
  • subStr(start, length)

    • 截取字符串中的字符,start表示开始截取的位置,length表示截取的个数
  • split(sep)

    • 将字符串拆分成数组,根据sep拆分字符串,如果传递空串则将每一字符都拆分成数组的元素
  • toUpperCase()

    • 将字符串转换成大写并返回,不影响原字符串
  • toLowerCase()

    • 将字符串转换成小写并返回,不影响原字符串

正则表达式

创建正则表达式对象

语法:var 变量 = new RegExp(“正则表达式”, “匹配模式”);

使用字面量创建正则表达式

语法:var 变量 = /正则表达式/匹配模式;

var reg = new RegExp("a"); // 检查一个字符串中是否含有a
console.log(reg.text("abc")); // 用reg的规则检查"abc",因为存在a索引返回true

匹配模式

i 忽略大小写

g 全局匹配模式(匹配所有内容,得到全部结果)

事件

事件就是用户和浏览器之间的交互行为

为元素绑定事件

<button id="btn">
    我是按钮
</button>
方式一
<button id="btn" onclick-"alert('您点击了按钮')">我是按钮</button>
方式二
var btn = document.getElementById("btn");
btn.onclick = function() {
    alert("您点击了按钮")
}

获取元素节点

通过document对象调用

  1. getElementById()
    • 通过id属性获取一个元素节点对象
  2. getElementsByTagName()
    • 通过标签名获取一组元素节点对象
  3. getElementsByName()
    • 通过name属性获取一组元素节点对象,多用于input

获取元素节点的子节点

通过具体的元素节点调用

  1. getElementsByTagName()
    • 方法,返回当前节点的指定标签名后代节点
  2. childNodes
    • 属性,表示当前节点的所有子节点(这个属性会获取文本节点:两个标签之间的文本,空白。IE8及以下不会获取文本节点)
  3. children
    • 属性,表示当前节点的所有子元素,不包括文本节点
  4. firstChild
    • 属性,表示当前节点的第一个子节点,包括文本节点
  5. firstElementChild
    • 属性,获取当前元素的第一个子元素(不支持IE8及以下)
  6. lastChild
    • 属性,表示当前节点的最后一个子节点,包括文本节点

获取父节点和兄弟节点

通过具体的节点调用

  1. parentNode
    • 属性,表示当前节点的父节点
  2. previousSibling
    • 属性,表示当前节点的前一个兄弟节点(包括文本节点)
    • previousElementSibling:获取前一个兄弟节点(IE8及以下不支持)
  3. nextSibling
    • 属性,表示当前节点的后一个兄弟节点(包括文本节点)

通过CSS选择器获取元素节点

var e = document.querySelector(".box1 div");

document.querySelector()需要一个字符串作为参数。可以根据一个CSS选择器来查询一个元素节点对象

使用该方法总会返回唯一的一个元素,如果满足条件的元素有多个,则只返回第一个符合条件的元素

var e = document.querySelectorAll(".box1 div");

该方法返回一个数组,可以获取到所有符合条件的元素

元素属性

innerHTML:获取元素标签内的内容

innerText:获取元素标签内的内容,如果内容中有其他标签则会去除

className:获取元素类名

document.body 获取body的引用

document.documentElement 获取html根标签

获取页面所有元素

  1. document.getElementsByTagName("*")

  2. document.all

根据元素的class属性查询元素

var box1 = document.getElementsByClassName("box1");

getElementsByClassName()可以根据class属性值获取一组元素节点对象,但是改方法不支持IE8及以下的浏览器

节点的属性

nodeNamenodeTypenodeValue
文档节点#document9null
元素节点标签名1null
属性节点属性名2属性值
文本节点#text3文本内容

DOM增删改

appendChild():把新的子节点添加到指定节点(是父节点的最后一个节点)

removerChild():删除子节点

replaceChild():替换子节点

insertBefore():在指定节点前面插入新的子节点

createAttribute():创建属性节点

createElement(TagName):创建元素节点

createTextNode(data):创建文本节点、

getAttribute():返回指定的属性值

setAttribute():把指定属性设置或修改为指定的值

案例:

var h1 = document.createElement("h1");
var hData = document.createTextNode("我是标题");
h1.appendChild(hData);
document.body.appendChild(h1);

获取元素样式

内联样式

设置元素的内联样式

语法:元素.style.样式名 = 样式值

这种方式只会读取到元素的内联样式,如果没有设置的内联样式在读取时将会返回空

在读取有横杠的样式时需要使用驼峰命名法

如果在其他样式中加了!important,此时JS将无法修改该样式

当前样式

currentStyle

获取元素当前显示的样式

语法:元素.currentStyle.样式名

如果当前元素没有设置该样式,则获取它的默认值(auto等)

currentStyle只支持IE浏览器,其他浏览器都不支持,只读属性

getComputedStyle()

getComputedStyle是window的方法,可以直接调用。需要两个参数,第一个:要获取样式的元素,第二个:可以传递一个伪元素,一般都传null

该方法返回一个对象,对象中封装了对当前元素对应的样式,可以通过对象.样式读取样式

如果获取的样式没有设置,则会获取到真实的值(非auto),而不是默认值

但该方法不支持IE8及以下的浏览器

兼容获取元素样式

function getStyle(e, name) {
    // 这里使用window.属性的方式,而不使用直接调用是因为,直接调用不存在的变量会报错
	if (window.getComputedStyle) {
		return getComputedStyle(e, null)[name];
	} else {
        return e.currentStyle[name];
    }
}

其他样式

clientWidth:元素的可见宽度,无边框

clientHeight:元素的可见高度,无边框

上面的属性都是返回数值,并不带px(数值的单位是像素),包括内容区和内边距只读属性

offsetWidth,offsetHeight:获取元素的整个的宽度和高度,包括内容区、内边距和边框只读属性包括边框

offsetParent:获取离当前元素最近的开启了定位的祖先元素。body是所有元素的定位祖先

offsetLeft、offsetTop:获取元素相当于其定位父元素的水平和垂直偏移量

scrollWidth、scrollHeight:获取整个滚动区域的宽度和高度(显示部分加被隐藏部分)

scrollLeft、scrollTop:获取滚动条滚动的水平、垂直距离

当满足scrollHeight - scrollTop == clientHeight时,说明垂直滚动条滚到底了

当满足scrollWidth - scrollLeft == clientWidth时,说明水平滚动条滚到底了

事件对象

给元素绑定事件函数时,浏览器会默认传递事件对象对函数,使用时只需要使用形参去接收即可。

但是在IE8及以下的浏览器中,事件对象作为window对象的属性保存

案例,获取鼠标的坐标

var box = document.getElementById('box');
box.onmousemove = function(event) {
    event = event || window.event; // 兼容全部浏览器
    var x = event.clientX;
    var y = event.clientY;
    console.log('x=' + x + ', y=' + y);
}

clientX和clientY用于获取鼠标在当前可见窗口的坐标

pageX和pageY用于获取鼠标相对于当前页面的坐标(该属性在IE8及以下不支持)

screenX,screenY相对于电脑屏幕的X,Y坐标

当窗口小于页面大小时,可以使用下面的代码使clientX达到pageX的效果

var box = document.getElementById('box');
box.onmousemove = function(event) {
    event = event || window.event; // 兼容全部浏览器
    // 因为谷歌认为浏览器的滚动条是body的,其他浏览器则认为是html的
    var dt = document.body.scrollTop || document.documentElement.scrollTop;
    var dl = document.body.scrollLeft || document.documentElement.scrollLeft;
    var x = event.clientX + dl;
    var y = event.clientY + dt;
}

事件冒泡

所谓的冒泡指的就是事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发

取消冒泡

var box = document.getElementById('box');
box.onclick = function(e) {
    alert('我是div');
    e.cancelBubble = true; // 取消事件的冒泡,取消了冒泡将不会弹出我是body的对话框
}
document.body.onclick = function() {
    alert('我是body');
}

事件委派

将事件统一绑定给元素的共同的祖先元素,这样当后代元素的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应函数来处理事件

事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能

事件绑定

方式一

  • 对象.事件 = 函数

这种方式只能同时为一个元素的一个事件绑定一个响应函数,如果绑定多个后面的会覆盖前面的

方式二

  • 对象.addEventListener()

参数一:事件的字符串,不要on。参数二:回调函数。参数三:是否在捕获阶段触发,一般传false

这种方法可以同时为一个元素的相同事件绑定多个响应函数,多次绑定被触发时,函数会按照代码的执行顺序执行。但该方法不支持IE8及以下的浏览器

在回调函数中this是该对象

方式三

  • 对象.attachEvent()

参数一:事件的字符串,要on。参数二:回调函数

这个方法也可以同时为一个事件绑定多个处理函数,于addEventListener()不同的是,先绑定的函数后执行

IE浏览器使用该方法,其他浏览器上无法使用

在回调函数中this是window

兼容事件绑定

// obj:要绑定事件的对象,eventStr:事件字符串(不要on),callback:回调函数
function bind(obj, eventStr, callback) {
    if (obj.addEventListener) {
        obj.addEventListener(eventStr, callback, false);
    } else {
        // 该方法中的回调函数在使用this时是window对象
        // 让window对象调用此处的匿名函数,在匿名函数中手动指定触发函数的调用对象
        obj.attachEvent('on' + eventStr, function() {
            callback.call(obj);
        });
    }
}

事件传播

W3C将事件传播分成了三个阶段

  1. 捕获阶段
    • 在捕获阶段时从最外层的祖先元素,向目标元素进行事件的捕获,但是默认此时不会触发事件
  2. 目标阶段
    • 事件捕获到目标元素,捕获结束开始在目标元素上触发事件
  3. 冒泡阶段
    • 事件从目标元素向它的祖先元素传递,依次触发祖先元素上的事件

如果希望在捕获节点就触发事件,可以将addEventListener()的第三个参数设置为true。一般情况下我们不会希望在捕获阶段触发事件,所以这个参数一般都是false

IE8及以下的浏览器中没有捕获阶段

键盘事件

onkeydown

  • 按键被按下
  • 对于onkeydown来说如果一直按着某个按键不松手,则事件会一直触发
  • 当onkeydown连续触发时,第一次和第二次之间会间隔稍微长一点,其他的会非常的快
    • 这种设计是为了防止误操作的发生

onkeyup

  • 按键被松开

通过event的keyCode可以获取按键的编码(ASCII)

处理keyCode,事件对象中还提供了几个属性

  1. altKey
  2. ctrlKey
  3. shiftKey

这三个用来判断 alt ctrl 和 shift 是否被按下,如果按下则返回true,否则返回false

键盘事件一般都会绑定给一些可以获取到焦点的对象或者是document

在文本框中输入内容,属于onkeydown的默认行为,如果在onkeydown中取消了默认行为(return false),则输入的内容,不会出现在文本框中。

BOM (浏览器对象模型)

BOM可以使我们通过JS来操作浏览器

在BOM中为我们提供了一组对象,用来完成对浏览器的操作

Window:代表的是整个浏览器的窗口,同时window也是网页中的全局对象

Navigator:代表的当前浏览器的信息,通过该对象可以来识别不同的浏览器

Location:代表当前浏览器的地址栏信息,通过Location可以获取地址栏信息,或者操作浏览器跳转页面

History:代表浏览器的历史记录,可以通过该对象来操作浏览器的历史记录。由于隐私原因,该对象不能获取到具体的历史纪录,只能操作浏览器向前或向后翻页,而且该操作只在当次访问时有效

Screen:代表用户的屏幕信息,通过该对象可以获取到用户的显示器的相关的信息

这些BOM对象在浏览器中都是作为window对象的属性保存的,可以通过window对象来使用,也可以直接使用

Navigator

代表的当前浏览器的信息,通过该对象可以来识别不同的浏览器

由于历史原因,Navigator对象中的大部分属性都已经不能帮助我们识别浏览器了

navigator.appName:用于获取浏览器的名称,现在的浏览器基本上返回的是"Netscape"

一般我们只会使用userAgent来判断浏览器的信息,userAgent是一个字符串,这个字符串中包含有用来描述浏览器信息的内容,不同的浏览器会有不同的userAgent

火狐的userAgent

Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0

Chrome的userAgent

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36

IE8的userAgent

Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3)

IE9的userAgent

Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3)

IE10的userAgent

Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3)

IE11的userAgent

Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3; rv:11.0) like Gecko

判断各种浏览器

var ua = navigator.userAgent;
if (/firefox/i.test(ua)) {
    alert('你是火狐浏览器');
} else if (/chrome/i.test(ua)) {
    alert('你是谷歌浏览器');
} else if (/msie/i.test(ua)) {
    alert('你是IE浏览器');
} else if ('ActiveXObject' in window) {
    alert('你是IE11浏览器');
}

History

该对象可以用来操作浏览器向前或向后翻页

length属性,可以获取到当前访问的链接数量

back() 可以退回到上一个页面,左右和浏览器的回退按钮一样

forward() 可以跳转下一个页面,作用和浏览器的前进按钮一样

go() 可以用来跳转到指定的页面,它需要一个整数作为参数。

​ 1:表示向前跳转一个页面,相当于forward()

​ 2:表示向前跳转两个页面

​ -1:表示向后跳转一个页面

​ -2:表示向后跳转两个页面

Location

该对象中封装了浏览器的地址栏的信息,如果直接打印location,则可以获取到地址栏的信息(当前页面的完整路径)

如果直接将location属性修改为一个完整的路径,或相对路径,则页面会自动跳转到该路径,并且会生成相应的历史记录

assign() 用来跳转到其他的页面,作用和直接修改location一样

reload() 用于重新加载当前页面,作用和刷新按钮一样。如果在方法中传递一个true作为参数,则会强制情况缓存刷新页面,相当于按下ctrl + f5

replace() 可以使用一个新的页面替换当前页面,调用完毕也会跳转页面。不会生成历史记录,不能使用回退按钮回退页面

定时器

setInterval() 定时调用,可以将一个函数,每隔一段事件执行一次

参数一:回调函数,该函数会每个一段事件被调用一次

参数二:每次调用间隔的时间,单位是毫秒

返回值:Number类型的书简,这个数值作为定时器的唯一标识

clearInterval() 可以用来关闭一个定时器,参数:定时器标识

方法中需要一个定时器的标识作为参数,这样将关闭标识对应的定时器

延时调用

延时调用一个函数,该函数不会马上执行,而是隔一段时间以后再执行,而且只会执行一次

延时调用和定时调用的区别,定时调用会执行多次,而延时调用只会执行一次

使用方法

var timer = setTimeout(function() {
    console.log(123);
}, 3000);

关闭延时调用则使用clearTimeout()

JSON

JSON.parse() :将JSON字符串转换为js对象

JSON.stringify() :将js对象转为JSON字符串

JSON这个对象在IE7及以下的浏览器中不支持,在这些浏览器中使用时会报错

eval() 函数可以用来执行一段字符串形式的JS代码,并将执行结果返回。如果使用eval()执行的字符串中含有{},它会将{}当成是代码块,如果不希望将其当成代码块解析,则需要在字符串前后各加一个()

var str = '{"name":"zs","age":18,"gender":"男"}';
var obj = eval("(" + str + ")");

eval函数在开发中尽量不要使用,首先它的性能比较差,然后它还具有安全隐患

如果要兼容IE7及以下的浏览器使用JSON,可以使用js工具来使用JSON类


JavaScript精读(高程)

JavaScript实现

  • 核心(ECMAScript)
  • 文档对象模型(DOM)
  • 浏览器对象模型(BOM)

ECMAScript

ECMAScript,即ECMA-262定义的语言,并不局限于Web浏览器。Web浏览器只是ECMAScript实现可能存在的一种宿主环境。宿主环境提供ECMAScript的基准实现和与环境自身交互必需的扩展。

DOM

文档对象模型是一个应用编程接口,用于在HTML中使用扩展的XML。

BOM

使用BOM,开发者可以操控浏览器现实页面之外的部分。BOM主要针对浏览器窗口和子窗口,不过人们通常会把任何特定于浏览器的扩展都归在BOM的范畴内。

第2章 HTML中的JavaScript

<script>元素有下列8个属性:

  1. async:可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部脚本文件有效。
  2. charset:可选。使用src属性指定的代码字符集。这个属性很少使用,因为大多数浏览器不在乎它的值。
  3. crossorigin:可选。配置相关请求的CORS(跨源资源共享)设置。默认不使用CORS。crossorigin="anonymous"配置文件请求不必设置凭据标志。crossorigin="use-credentials"设置凭据标志,意味着出站请求会包含凭据。
  4. defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。在IE7及更早的版本中,对行内脚本也可以指定这个属性。
  5. integrity:可选。允许比对接收到的资源和指定的加密签名以验证子资源完整性(SRI, SubresourceIntegrity)。如果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络(CDN, Content DeliveryNetwork)不会提供恶意内容。
  6. language:废弃。最初用于表示代码块中的脚本语言(如"JavaScript"、“JavaScript 1.2"或"VBScript”)。大多数浏览器都会忽略这个属性,不应该再使用它。
  7. src:可选。表示包含要执行的代码的外部文件。
  8. type:可选。代替language,表示代码块中脚本语言的内容类型(也称MIME类型)。按照惯例,这个值始终都是"text/javascript",尽管"text/javascript"和"text/ecmascript"都已经废弃了。JavaScript文件的MIME类型通常是"application/x-javascript",不过给type属性这个值有可能导致脚本被忽略。在非IE的浏览器中有效的其他值还有"application/javascript"和"application/ecmascript"。如果这个值是module,则代码会被当成ES6模块,而且只有这时候代码中才能出现import和export关键字。

变量

var

  1. var操作符定义的变量会称为包含它的函数的局部变量,使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁
function test() {
    var msg = "123"; // 局部变量
}
test();
console.log(msg); // Uncaught ReferenceError: msg is not defined
  1. 在函数内定义变量时省略var操作符,可以创建一个全局变量
function test() {
    msg = "hi";
}
test();
console.log(msg); // hi
  1. 使用var时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部
function foo() {
    console.log(age);
    var age = 26;
}
  1. 反复多次使用var声明同一个变量也没有问题

let

  1. let声明的范围是块作用域,而var声明的范围是函数作用域
if (true) {
    var name = 'zs';
    console.log(name); // zs
}
console.log(name); // zs
if (true) {
    let age = 26;
    console.log(age); // 26
}
console.log(age); // ReferenceError: age没有定义
  1. let也不允许同一个块作用域中出现冗余声明
var name;
var name;
let age;
let age; // SyntaxError: 标识符age已经声明过了
  1. let声明的变量不会在作用域中被提升(let声明之前执行瞬间被称为暂时性死区)
// name会被提升
console.log(name); // undefined
var name = 'zs';
// age不会被提升
console.log(age); // ReferenceError: age没有定义
let age = 26;
  1. 使用let在全局作用域中声明的变量不会称为window对象的属性
var name = 'zs';
console.log(window.name); // zs
let age = 26;
console.log(window.age); // undefined
暂时性死区

在let声明之前的执行瞬间被称为”暂时性死区“(Temporal Dead Zone)简称TDZ

只要块级作用域里存在let命令,它所声明的变量就”绑定“这个区域,不在受外部的影响

var tmp = 123;
if (true) {
    tmp = 456; // ReferenceError
    let tmp = 789;
}
if (true) {
	// TDZ开始
	tmp = 'abc'; // ReferenceError
	console.log(tmp); // ReferenceError
	let tmp; // TDZ结束
	console.log(tmp); // undefined
	tmp = 123;
	console.log(tmp); // 123
}

有些“死区”比较隐蔽,不太容易发现

function foo(x = y, y = 2) {
    return [x, y];
}
foo(); // 报错

在上面代码中,调用foo函数报错是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。如果y的默认值是x,就不会报错,因为此时x已经被声明了

var x = x; // 不报错
let y =y; // 报错,y未定义

上面代码报错,也是因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面就是这种情况,在变量y的声明语句还没执行完成前,就去取y的值,导致报错”y未定义“。

const

  1. const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。

  2. const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。

数据类型

ECMAScript有6种简单数据类型(也称为原始类型):Undefined、Null、Boolean、Number、String和Symbol。Symbol(符号)是ECMAScript 6新增的。还有一种复杂数据类型叫Object(对象)。Object是一种无序名值对的集合。

typeof

typeof是一个操作符而不是函数,所以不需要参数(但可以使用参数)

let msg = 'hi';
console.log(typeof msg); // "string"
console.log(typeof(msg)); // "string"
console.log(typeof 1); // "number"

调用typeof null返回的是"object"。这是因为特殊值null被认为是一个对空对象的引用。

undefined

undefined是一个假值。

在对未初始化的变量调用typeof时,返回的结果是"undefined",但对未声明的变量调用它时,返回的结果还是"undefined"。

let msg; // 这个变量被声明了,只是值为undefined
// age 没有声明
if (msg) {
    // 这个块不会执行
}
if (!msg) {
    // 这个块会执行
}
if (age) {
    // 这里会报错
}

null

逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null会返回"object"的原因。

用等于操作符(==)比较null和undefined始终返回true。

console.log(null == undefined); // true

null是一个假值。

boolean

不同类型与布尔值之间的转换规则

数据类型转换为true的值转换为false的值
Booleantruefalse
String非空字符串“”(空字符串)
Number非零数值(包括无穷值)0、NaN
Object任意对象null
UndefinedN/A(不存在)undefined

number

Number类型使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)。

因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。

let num1 = 1.; // 小数点后面没有数字,当成整数1处理
let num2 = 10.0; // 小数点后面是零,当成整数10处理

浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确。例如,0.1加0.2得到的不是0.3,而是0.30000000000000004。

ECMAScript可以表示的最小数值保存在Number.MIN_VALUE中,这个值在多数浏览器中是5e-324;可以表示的最大数值保存在Number.MAX_VALUE中,这个值在多数浏览器中是1.797693134862315 7e+308。如果某个计算得到的数值结果超出了JavaScript可以表示的范围,那么这个数值会被自动转换为一个特殊的Infinity(无穷)值。任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以Infinity(正无穷大)表示。

>9007199254740992 + 1
9007199254740992

>9007199254740992 + 2
9007199254740994

Number.MAX_SAFE_INTEGER最大安全的整数2的53次方9007199254740992

面试题:为什么0.1+0.2不等于0.3?

因为0.1和0.2在转换成二进制的时候都是小数后0011无限循环的,所有在作加法运算时不会刚好等于0.3

十进制小数转换二进制小数:用2乘十进制小数,得积取整再乘2,以此类推直到积中小数部分为0

如:0.7=(0.1 0110 0110...)B
0.7*2=1.4========取出整数部分1
0.4*2=0.8========取出整数部分0
0.8*2=1.6========取出整数部分1
0.6*2=1.2========取出整数部分1
0.2*2=0.4========取出整数部分0
0.4*2=0.8========取出整数部分0
0.8*2=1.6========取出整数部分1
0.6*2=1.2========取出整数部分1
0.2*2=0.4========取出整数部分0

// 0.1 转化为二进制
0.0 0011 0011 0011 0011...(0011无限循环)
// 0.2 转化为二进制
0.0011 0011 0011 0011 0011...(0011无限循环)

一个十进制数:ABC.DEF = A10^2 + B10^1 + C10^0 + D10^-1 + E10^-2 + F10^-3
一个二进制数:abc.def = a2^2 + b2^1 + c2^0 + d2^-1 + e2^-2 + f2^-3
这是不同进制的数值表示的根本。

例如:二进制数1101.01转化成十进制

1101.01(2)=1*20+0*21+1*22+1*23 +0*2-1+1*2-2=1+0+4+8+0+0.25=13.25

所以总结起来通用公式为:

abcd.efg(2)=d20+c21+b22+a23+e2-1+f2-2+g*2-3

NaN
  1. 任何涉及NaN的操作始终返回NaN(如NaN/10),NaN不等于包括NaN在内的任何值。使用Object.is(NaN, NaN)可以对NaN进行检测
console.log(NaN == NaN); // false
console.log(NaN > 3); // false
console.log(NaN <= 3); // false
  1. 检查一个值是否为数值应该使用isNaN()
console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // fasle, 10是数值
console.log(isNaN('10')); // false, 可以转换为数值10
console.log(isNaN('abc')); // true, 不可以转换为数值
console.log(isNaN(true)); // false, 可以转换为数值1

有3个函数可以将非数值转换为数值:Number()、parseInt()和parseFloat()

Number()函数转换规则

  • 布尔值,true转换为1,false转换为0

  • 数值,直接返回

  • null,返回0

  • undefined,返回NaN

  • 字符串,应用以下规则

    • 如果字符串包含数值字符串,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。因此,Number(‘1’)返回1,Number(‘123’)返回123,Number(‘011’)返回11(忽略前面的零)
    • 如果字符串包含有效的浮点值格式如“1.1”,则会转换为相应的浮点值(同样,忽略前面的零)
    • 如果字符串包含有效的十六进制格式如“0Xf”,则会转换为与该十六进制对应的十进制整数值
    • 如果是空字符串(不包含字符),则返回0
    • 如果字符串包含除上述情况之外的其他字符,则返回NaN
  • 对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换

parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回NaN。**这意味着空字符串也会返回NaN(这一点跟Number()不一样,它返回0)。**如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。

let num1 = parseInt('1234blue'); // 1234
let num2 = parseInt(''); // NaN
let num3 = parseInt('0xA'); // 10,解释为十六进制整数
let num4 = parseInt('22.5'); // 22
let num5 = parseInt('70'); // 70,解释为十进制整数
let num6 = parseInt('0xf'); // 15,解释为十六进制整数

parseInt()也接收第二个参数,用于指定底数(进制数),如果提供了十六进制参数,那么字符串前面的“0x”可以省略

let num1 = parseInt('0xAF', 16); // 175
let num2 = parseInt('AF', 16); // 175
let num3 = parseInt('AF'); // NaN

parseFloat()函数的工作方式跟parseInt()函数类似,都是从位置0开始检测每个字符。字符串中的小数点只有第一个有效,正常解析到第二个小数点时剩余的字符将被忽略

parseFloat()函数的一个不同之处是,始终忽略字符串开头的零

parseFloat()只解析十进制值,因此不能指定底数

let num1 = parseFloat('1234blue'); // 1234,按整数解析
let num2 = parseFloat('0xA'); // 0
let num3 = parseFloat('22.5'); // 22.5
let num4 = parseFloat('22.34.5'); // 22.34
let num5 = parseFloat('0908.5'); // 908.5
let num6 = parseFloat('3.125e7'); // 31250000

string

String(字符串)数据类型表示零或多个16位Unicode字符序列。

ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。

转换字符串的三种方法:

  1. toString(),几乎所有值都有toString()方法,null和undefined值没有toString()方法

    • 在对数值调用toString时可以传入一个底数参数
    let num = 10;
    console.log(num.toString()); // 10,默认为10进制
    console.log(num.toString(2)); // 1010
    console.log(num.toString(8)); // 12
    console.log(num.toString(10)); // 10
    console.log(num.toString(16)); // a
    
  2. String()转型函数,它始终会返回表示相应类型值的字符串,转换规则如下

    • 如果值有toString()方法,则调用该方法(不传参数)并返回结果
    • 如果值是null,返回’null’
    • 如果只是undefined,返回’undefined’
  3. 使用加号操作符,给一个值加上一个空字符串""也可以将该值转为字符串

模板字面量标签函数

标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果。

标签函数接收到的参数依次是原始字符串数组和对每个表达式求值的结果。这个函数的返回值是对模板字面量求值得到的字符串。

let a = 6, b = 9;
function tagFun(strings, ...expressions) { // expressions变量是一个包含传入第二个开始的参数的数组
	console.log(strings); // 模板字符串根据${}分割后得到的结果
    for (const exp of expressions) {
        console.log(exp); // ${}中的计算结果
    }
    return 'foobar'; // 返回调用的模板字符串需要的结果
}
let tagResult = tagFun`${a}+${b}=${a+b}`;
// 执行后输出
// ["", "+", "=", ""]
// 6
// 9
// 15
console.log(tagResult); // foobar,tagFun的返回值
原始字符串

使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或Unicode字符),而不是被转换后的字符表示。为此,可以使用默认的String.raw标签函数:

console.log(`\u00A9`); // '©'
console.log(String.raw`\u00A9`); // \u00A9

symbol

符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

符号需要使用Symbol()函数初始化,调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),这个字符串参数与符号定义或标识完全无关。

let a = Symbol(), b = Symbol();
console.log(a == b); // false
let c = Symbol('1'), d = Symbol('1');
console.log(c == d); // false

Symbol()函数不能与new关键字一起作为构造函数使用。

使用全局符号注册表

Symbol.for()对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。

let a = Symbol.for('1'), b = Symbol.for('1');
console.log(a == b); // true

即使采用相同的符号描述,在全局注册表中定义的符号跟使用Symbol()定义的符号也并不等同:

let a = Symbol('1'), b = Symbol.for('1');
console.log(a == b); // false

可以使用Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。

let s = Symbol.for('foo'); // 创建全局符号
console.log(Symbol.keyFor(s)); // foo
let s2 = Symbol('bar'); // 创建普通符号
console.log(Symbol.keyFor(s2)); // undefined

object

每个Object实例都有如下属性和方法:

  • constructor:用于创建当前对象的函数
  • hasOwnProperty(prepertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必需是字符串(如o.hasWonProperty(‘name’))或符号
  • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型
  • propertylsEnumerable(propertyName):用于判断给定的属性是否可以使用 for-in 语句枚举。与hasOwnProperty()一样,属性名必需是字符串
  • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地执行环境
  • toString():返回对象的字符串表示
  • valueOf():返回对象对应的字符串、数字或布尔值表示。通常与toString()的返回值相同

操作符

位操作符

  1. 按位非

按位非操作符用波浪符(~)表示,它的作用是返回数值的一补数。按位非的最终效果是对数值取反并减1

let num1 = 25;    // 0001 1001
let num2 = ~num1; // 1110 0110
console.log(num2); // -26
  1. 按位与

按位与操作符用和号(&)表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作。

都为1才1

  1. 按位或

按位或操作符用管道符(|)表示,同样有两个操作数。

有1就1

  1. 按位异或

按位异或用脱字符(^)表示,同样有两个操作数。

相同为0,不同为1

  1. 左移

左移操作符用两个小于号(<<)表示,会按照指定的位数将数值的所有位向左移动。

左移会保留它所操作数值的符号

  1. 有符号右移

有符号右移由两个大于号(>>)表示,会将数值的所有32位都向右移,同时保留符号(正或负)。

  1. 无符号右移

无符号右移用3个大于号表示(>>>),会将数值的所有32位都向右移。

对于正数,无符号右移与有符号右移结果相同。

对于负数,有时候差异会非常大。与有符号右移不同,无符号右移会给空位补0,而不管符号位是什么。

布尔操作符

  1. 逻辑非(!),转换规则如下
    • 对象 ==》false
    • 空字符串 ==》 true
    • 非空字符串 ==》 false
    • 0 ==》 true
    • 非0(包括Infinity) ==》 false
    • null ==》 true
    • NaN ==》 true
    • undefined ==》 true

同时使用两个叹号(! !),相当于调用了转型函数Boolean()。无论操作数是什么类型,第一个叹号总会返回布尔值。第二个叹号对该布尔值取反,从而给出变量真正对应的布尔值。

  1. 逻辑与(&&),转换规则如下
    • 如果第一个操作数是对象,则返回第二个操作数
    • 如果第二个操作数是对象,则只有第一个操作数求值为true才会返回该对象
    • 如果两个操作数都是对象,则返回第二个操作数
    • 如果有一个操作数是null,则返回null
    • 如果有一个操作数是NaN,则返回NaN
    • 如果有一个操作数是undefined,则返回undefined

如果第一个为true则返回第二个,如果第一个为false则返回第一个

  1. 逻辑或(||),转换规则如下
    • 如果第一个操作数是对象,则返回第一个操作数
    • 如果第一个操作数求值为false,则返回第二个操作数
    • 如果两个操作数都是对象,则返回第一个操作数
    • 如果两个操作数都是null,则返回null
    • 如果两个操作数都是NaN,则返回NaN
    • 如果两个操作数都是undefined,则返回undefined

关系操作符

在将它们应用到不同数据类型时也会发生类型转换和其他行为。

  • 如果操作数都是数值,则执行数值比较
  • 如果操作数都是字符串,则逐个比较字符串中对应字符的编码
  • 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较
  • 如果有任一操作数是对象,则调用其valueOf()方法,取得结果后再根据前面的规则执行比较。如果没有valueOf()操作符,则调用toString()方法,取得结果后再根据前面的规则执行比较
  • 如果有任一操作数是布尔值,则将其转换为数值再执行比较

相等操作符

  1. 等于和不等于,转换规则如下:
    • 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false转换为0,true转换为1
    • 如果一个操作数是字符串,另一个操作数是数值,则尝试见字符串转为数值,再比较是否相等
    • 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法取得其原始值,再根据前面的规则进行比较

判断时最终都是往number上转

在进行比较时,规则如下:

  • null和undefined相等
  • null和undefined不能转换为其他类型的值再进行比较(null与undefined和其他都是false)
  • **如果有任一操作数是NaN,则相等操作符返回false,不相等操作符返回true。**记住:即使两个操作数都是NaN,相等操作符也返回false,因为按照规则,NaN不等于NaN
  • 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true。否则,两者不相等
  1. 全等和不全等

全等和不全等操作符与相等和不相等操作符类似,只不过它们在比较相等时不转换操作数。全等操作符由3个等于号(===)表示,只有两个操作数在不转换的前提下相等才返回true

赋值运算符(连等赋值的问题)

let a = b = 1; // 等于号赋值的顺序是从右向左
// 上面的语句相当于b = 1; let a = b; 此时b被提升为全局变量
console.log(window.a); // undefined
console.log(window.b); // 1
(function() {
    let a = b = 1;
})();
console.log(b); // 1
console.log(a); // ReferenceError: a is not defined

第3章 语法基础

for-in 语句(相当于对象属性名)

for-in语句是一种严格的迭代语句,用于枚举对象中的非符号键属性,语法如下:

for (property in expression) statement

如果for-in循环要迭代的变量是null或undefined,则不执行循环体。

遍历对象时,值为key(对象的属性名称)

遍历数组案例(打印下标)

let arr = ['zs', 'ls', 'ww'];
for(let i in arr) console.log(i);
// 0
// 1
// 2 // 打印下标

遍历对象案例(打印对象的key)

let obj = {
  name: 'zd',
  age: 18
}
for(let i in obj) console.log(i);
// name
// age // 打印对象的key

遍历字符串案例(打印下标)

let str = "abc";
for(let i in str) console.log(i);
// 0
// 1
// 2 // 打印下标

for-of 语句(相当于对象属性值,对象不能用)

for-of语句是一种严格的迭代语句,用于遍历可迭代对象的元素,语法如下:

for (property of expression) statement

遍历的对象必需是一个可迭代对象,一个可迭代对象一定要有**Symbol(Symbol.iterator)**属性

遍历数组案例(打印元素)

let arr = ['zs', 'ls', 'ww'];
for(let i of arr) console.log(i);
// zs
// ls
// ww // 打印元素

遍历对象案例(无法调用)

let obj = {
  name: 'zd',
  age: 18
}
for(let i in obj) console.log(i);
// Uncaught TypeError: obj is not iterable // 无法遍历对象

遍历字符串案例(打印字符)

let str = "abc";
for(let i in str) console.log(i);
// a
// b
// c // 打印字符

with语句

with语句的用途是将代码作用域设置为特定的对象,其语法是:

with (expression) statement

用途示例:

let a = my.aVal, b = my.bVal;
// 以上语句中变量my可以直接获取到,可以简化写成
with (my) {
    let a = aVal, b = bVal; // 这样可以直接访问到my中的属性
}

严格模式不允许使用with语句,否则会抛出错误。(不推荐使用with语句)

switch语句

switch语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型(比如,字符串"10"不等于数值10)

第4章 变量、作用域与内存

原始值与引用值

传递参数

ECMAScript中所有函数的参数都是按值传递的

function setName(obj) { // obj是一个复制值,obj和person是互不相干的两个对象,只是此时都指向同一个地址
    obj.name = 'zs';
    obj = new Object();
    obj.name = 'ls';
}
let person = new Object();
setName(person);
console.log(person.name); // zs

const赋值

用const声明的变量必须同时初始化为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值。

const声明只应用到顶级原语或者对象。换句话说,赋值为对象的const变量不能再被重新赋值为其他引用值,但对象的键则不受限制。

const o1 = {};
o1 = {}; // TypeError: Assignment to constant variable.给常量赋值
const o2 = {};
o2.name = 'zs';
console.log(o2.name); // zs

如果想让整个对象都不能修改,可以使用Object.freeze(),这样再给属性赋值时虽然不会报错,但会静默失败:

const o3 = Object.freeze({});
o3.name = 'zs';
console.log(o3.name); // undefined

第5章 基本引用类型

原始值包装类型

为了方便操作原始值,ECMAScript提供了3种特殊的引用类型:Boolean、Number和String。

每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。来看下面的例子:

let  s1 = 'some text';
let  s2 = s1.substring(2);

s1是一个原始值,第二行在s1上调用substring()方法时,是以读模式访问s1的,也就是要从内存中读取变量保存的值。在读模式访问字符串值的任何时候,后台都会执行一下3步:

  1. 创建一个String类型的实例
  2. 调用实例上的特定方法
  3. 销毁实例

可以把这3步想象成执行了如下代码

let s1 = new String('some text');
let s2 = s1.sbstring(2);
s1 = null;

引用类型与原始值包装类型的主要区别在于对象的生命周期。在通过new实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间这意味着不能在运行时给原始值添加属性和方法。比如下面的例子:

let s1 = 'some text';
s1.color = 'red';
console.log(s1.color); // undefined

上面的代码在js内部操作时可以看成下面的代码

let s1 = 'some text';

let temp = new String(s1);
temp.color = 'red';
temp = null;

temp = new String(s1);
console.log(temp.color); // undefined
temp = null;

Object构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。注意,使用new调用原始值包装类型的构造函数,与调用同名的转型函数并不一样。

let o = new Object('123');
console.log(o instanceof String); // true
let val = '3';
let num = Number(val); // 转型函数
console.log(typeof num); // "number"
let obj = new Number(val); // 构造函数
console.log(typeof obj); // "object"

Number

toFixed():返回包含指定小数点位数的数值字符串,如果小数位超过指定参数则四舍五入

let num = 10;
console.log(num.toFixed(2)); // "10.00"

toExponential():返回以科学计数法表示的数值字符串,参数为小数点后有几位

let num = 10;
console.log(num.toExponential(1)); // "1.0e+1"

toPrecision():返回最合理的输出结果,可能是固定长度,也可以能是科学计数法

let num = 99;
console.log(num.toPrecision(1)); // "1e+2"  四舍五入变成100再使用科学计数法
console.log(num.toPrecision(2)); // "99"
console.log(num.toPrecision(2)); // "99.0"

Number.isInteger():用于辨别一个数值是否保存为整数

console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false

EEE 754数值格式有一个特殊的数值范围,在这个范围内二进制值可以表示一个整数值。这个数值范围从Number.MIN_SAFE_INTEGER(-2^53 + 1)到Number.MAX_SAFE_INTEGER(2^53 - 1)。对超出这个范围的数值,即使尝试保存为整数,IEEE 754编码格式也意味着二进制值可能会表示一个完全不同的数值。为了鉴别整数是否在这个范围内,可以使用Number.isSafeInteger()方法:

console.lo(Number.isSafeInteger(-1*(2 ** 53))); // false
console.lo(Number.isSafeInteger(-1*(2 ** 53) + 1)); // true
console.lo(Number.isSafeInteger(2 ** 53)); // false
console.lo(Number.isSafeInteger(2 ** 53) - 1); // true

String

String对象的方法可以在所有字符串原始值上调用。3个继承的方法valueOf()、toLocaleString()和toString()都返回对象的原始字符串值。

JavaScript字符串由16位码元(code unit)组成。每16位码元对应一个字符。

JavaScript字符串使用了两种Unicode编码混合的策略:UCS-2和UTF-16。对于可以采用16位编码的字符(U+0000~U+FFFF),这两种编码实际上是一样的。

let msg = 'abcd';
console.log(msg.charCodeAt(2)); // 99
console.log(99 === 0x63); // true c字符的编码(码元)就是U+0063 = '\u0063'

fromCharCode()方法用于根据给定的UTF-16码元创建字符串中的字符。这个方法可以接受任意多个数值,并返回将所有数值对应的字符拼接起来的字符串:

console.log(String.fromCharCode(0x61, 0x62, 0x63, 0x64)); // "abcd"
console.log(String.fromCharCode(97, 98, 99, 100)); // "abcd"

对于U+0000~U+FFFF范围内的字符,length、charAt()、charCodeAt()和fromCharCode()返回的结果跟预期是一样的,因为字符大小为16位,码元大小也是16位所有可以正常使用。但是在这个范围外的字符就会出错。

在Unicode中有基本多语言平面(BMP),即16位可以表示65536个字符,对于大多数语言字符集是足够了。

为了表示更多的字符,Unicode采用了一个策略,即每个字符使用另外16位去选择一个增补平面

这种每个字符使用两个16位码元的策略称为代理对

码点是Unicode中一个字符的完整表示。码点可能是16位,也可能是32位。

// 标签符号笑脸的编码是U+1F60A
let msg = 'ab😊de';
console.log(msg.length); // 6
console.log(msg.charAt(1)); // b
console.log(msg.charAt(2)); // '\uD83D' charCodeAt返回55357
console.log(msg.charAt(3)); // '\uDE0A' charCodeAt返回56842
console.log(msg.charAt(4)); // d
console.log(String.fromCodePoint(0x1F60A)); // '😊'
console.log(String.fromCharCode(97, 98, 55357, 56842, 100, 101)); // 'ab😊de'
console.log(msg.codePointAt(2)); // 128522
console.log(msg.codePointAt(3)); // 56842

迭代字符串可以智能地识别代理对的码点:

console.log([...'ab😊de']); // ["a", "b", "😊", "d", "e"]

32位码元对应使用的函数

charCodeAt() <==> codePointAt()

formCharCode() <==> fromCodePoint()

相关函数
连接字符串

concat() 不修改原串

提取子串

slice()、substr()和substring() 都不会修改原串

相同点:第一个参数表示子字符串开始的位置,第二个参数表示子字符串结束的位置。(左闭右开)

substr的第二个参数表示截取个数

不同点:slice()方法将所有负值参数都当成字符串长度加上负参数值。substr()方法将第一个负参数值当成字符串长度加上该值,将第二个负参数值转换为0。substring()方法会将所有负参数值都转换为0。

slice(len + (-val), len + (-val))

substr(len + (-val), 0)

substring(0, 0)

定位子串

indexOf()和lastIndexOf(),这两个方法从字符串中搜索传入的字符串,并返回位置(如果没找到,则返回-1)。两者的区别在于,indexOf()方法从字符串开头开始查找子字符串,而lastIndexOf()方法从字符串末尾开始查找子字符串。

这两个方法都可以接收可选的第二个参数,表示开始搜索的位置。

字符串包含

startsWith()、endsWith()和includes(),这些方法会从字符串中搜索传入的字符串,并返回是否存在的布尔值。

startsWith()和includes()可以接收可选的第二个参数,表示开始搜索的位置。

endsWith()接收可选的第二个参数表示字符串末尾的位置,如果不提供默认是字符串长度。(也是从后面开始搜索的位置)

trim()

trim(),这个方法会创建字符串的一个副本,删除前、后所有空格符,再返回结果

trimLeft()和trimRight()方法分别用于从字符串开始和末尾清理空格符

repeat()

repeat(),这个方法接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果

let s = "哦";
console.log(s.repeat(8) + '!'); // 哦哦哦哦哦哦哦哦!
padStart()和padEnd()

padStart()和padEnd()方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件。这两个方法的第一个参数是长度,第二个参数是可选的填充字符串,默认为空格(U+0020)。

let s = "foo";
console.log(s.padStart(6)); // "   foo"
console.log(s.padStart(6, 'x1')); // "x1xfoo"
console.log(s.padEnd(6)); // "foo   "
console.log(s.padEnd(6, ".")); // "foo..."
字符串迭代和解构

字符串的原型上暴露了一个@@iterator方法,可以通过它来获取字符串的迭代器

let s = "abc";
let it = s[Symbol.iterator]();
console.log(it.next()); // {value: "a", done: false}
console.log(it.next()); // {value: "b", done: false}
console.log(it.next()); // {value: "c", done: true}

有了迭代器就可以使用for-of遍历字符串,而且还可以使用解构操作来分割字符串

let s = "abc";
console.log([...s]); // ["a", "b", "c"]
字符串模式匹配

String类型专门为在字符串中实现模式匹配设计了几个方法。第一个就是match()方法,这个方法本质上跟RegExp对象的exec()方法相同。match()方法接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象。

let text = "cat, bat, sat, fat";
let pat = /.at/;
// 等价于pat.exec(text);
let matches = text.match(pat);
console.log(matches.index); // 0
console.log(matches[0]); // "cat"
console.log(matches.lastIndex); // 0

另一个查找模式的字符串方法是search(),这个方法用法和match()一样,只是返回的是模式第一个匹配的位置索引,如果没有找到则返回-1

为了简化子字符串替换操作,ECMAScript提供了replace()方法。

第一个参数是一个RegExp对象或一个字符串(这个字符串不会转换为正则表达式)

第二个参数可以是一个字符串或一个函数

如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,第一个参数必须为正则表达式并且带全局标记。

let s = "abc abc";
console.log(s.replace("bc", "xx")); // axx abc
console.log(s.replace(/bc/g, "xx")); // axx axx

第二个参数是字符串的情况下,有几个特殊的字符序列,可以用来插入正则表达式的值

字符序列替换文本
$$$
$&匹配整个模式的子字符串。与RegExp.lastMatch相同
$’匹配的子字符串之前的字符串。与RegExp.rightContext相同
$`匹配的子字符串之后的字符串。与RegExp.leftContext相同
$n匹配第n个捕获组的字符串,n是0-9。没有捕获值为空串
$nn匹配第nn个捕获组的字符串,nn是01~99。没有捕获值为空串

示例:

let s = "abc abc";
console.log(s.replace(/(a)/g, "t($1)")); // t(a)bc t(a)bc

单例内置对象

Global

  1. URL编码方法
let uri = "http://www.baidu.com/my ini.js";
console.log(encodeURI(uri)); // "http://www.baidu.com/my%20ini.js"
console.log(encodeURIComponent(uri)); // "http%3A%2F%2Fwww.baidu.com%2Fmy%20ini.js"

encodeURI():仅编码后面的资源地址和参数等

encodeURIComponent():编码整个uri地址

eval()

eval()接收一个要执行的ECMAScript字符串。当接收器发现eval()调用时,会将参数解释为世纪的ECMAScript语句,然后将其插入到该位置。

通过eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在eval()执行的时候才会被创建。

严格模式下,在eval()内部创建的变量和函数无法被外部访问。

严格模式下,给变量名为eval的变量赋值会导致报错

Global对象的属性

像undefined、NaN和Infinity等特殊值都是Global对象的属性,所有原生引用类型构造函数,比如Object和Function也是Global对象的属性

window对象

虽然ECMA-262没有规定直接访问Global对象的方式,但浏览器将window对象实现为Global对象的代理

另一种获取Global对象的方式如下:

let g = function() {return this}();

在一个函数没有明确(通过成为某个对象的方法,或通过call()/apply())指定this值的情况下执行时,this值等于Global对象

Math

Math.ceil() 始终向上舍入为最接近的整数

Math.floor() 始终向下舍入为最接近的整数

Math.round() 执行四舍五入

Math.fround() 返回数值最接近的单精度(32位)浮点值表示

Math.random() 返回一个0~1范围内的随机数,其中包含0但不包含1 [0,1)

使用round函数生成一段范围内的值:

number = Math.floor(Math.random() * total_number_of_choices + first_possible_value)

比如2~10只有9个数,可选总数是9,而最小可能的值是2,所有表达式为:

let num = Math.floor(Math.random() * 9 + 2);
function selectFrom(lower, upper) {
    let choices = upper - lower + 1;
    return Math.floor(Math.random() * choices + lower);
}
let num = selectFrom(5, 10);
console.log(num); // 5~10范围内的值,其中包含5~10

如果是为了加密而需要生成随机数,那么建议使用window.crypto.getRandomValues()

第6章 集合引用类型

Object

创建Object实例的方式有两种

第一种:使用new操作符和Object构造函数

第二种:使用对象字面量表示法

let person = {
    name: 'zs',
    age: 18
}

在使用对象字面量表示对象时,左大括号表示对象字面量开始,因为它出现在一个表达式上下文中,赋值操作符表示后面要期待一个值,因此左大括号表示一个表达式的开始。同样是左大括号,如果出现在语句上下文中,比如if语句条件后面,则表示一个语句块的开始。

表达式上下文指的是期待返回值的上下文

对象字面量表示法中,属性名如果是数值,则会自定转换为字符串

在使用对象字面量表示法定义对象时,并不会实际调用Object构造函数。

拓展方法

  1. Object.is 判断两个值是否完全相等
  2. Object.assign(target, …sources) 对象合并,将所有可枚举属性的值从一个或多个源对象分配到(把后面的属性放到第一个对象上)
  3. Object.setPrototypeOf() 和 Object.getPrototypeOf()

面试题:new的原理是什么?通过new创建对象和字面量创建有什么区别?

在调用new的过程中会指向如下操作:

  1. 创建一个空对象,构造函数中的this指向这个空对象
  2. 新对象被链接到原型
  3. 执行构造函数方法,属性和方法被添加到this引用的对象中
  4. 如果构造函数中没有返回新对象,那么返回this,即创建这个新对象,否则,返回构造函数中返回的对象

该过程js代码实现如下:

function create() {
    // 创建一个空对象
    let obj = ;
    // 获取构造函数
    let Con = [].shift.call(arguments);
    // 设置空对象的原型,将obj的__proto__指向构造函数的prototype
    obj.__proto__ == Con.prototype;
    // 把构造函数的this指向obj,并指向构造函数把结果赋值给result
    let result = Con.apply(obj, arguments);
    // 确保返回值为对象
    // 构造函数F的执行结果是引用类型,就把这个引用类型的对象返回给result
    // 构造函数F的执行结果是值类型,就返回obj这个空对象给result
    return result instanceof Object ? result : obj;
}

字面量创建对象,不会调用 Object构造函数, 简洁且性能更好;
new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。

Array

数组创建

在使用Array构造函数时,也可以省略new操作符。

let arr = Array(3); // 创建一个包含3个元素的数组
let names = Array("Greg"); // 创建一个只包含一个元素,即字符"GREG"的数组

与对象一样,在使用数组字面量表示法创建数组不会调用Array构造函数

ES6新增的用于创建数组的静态方法:from()和of()

from():用于将类数组结构(可迭代的结构,或有length属性和可索引元素的结构)转换为数组实例

const obj = { // 必需有length属性和索引属性
    0: 11,
    1: 22,
    length: 2
}
console.log(Array.from(obj)); // [11, 22]

Array.from()还接收第二个可选的映射函数参数,第三个可选参数用于指定映射函数中this的值,但这个重写的this值在箭头函数中不适用

const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1, x => x**2);
console.log(a2); // [1, 4, 9, 16]
// 这里第二个参数如果是一个箭头函数,则this将是window
const a3 = Array.from(a1, function(x) {return x**this.exp}, {exp: 3}); // [1, 8, 27, 64]

of():用以将一组参数转换为数组实例

数组长度

如果把一个值设置给超过数组最大索引的索引,则数组长度会自动扩展到该索引值加1

通过length属性,可以从数组末尾删除或添加元素

数组最多可以包含4294967295个元素,这对于大多数编程任务应该足够了。如果尝试添加更多项,则会导致抛出错误。以这个最大值作为初始值创建数组,可能导致脚本运行时间过长的错误。

检查数组

// 方法一
if (obj instanceof Array) {/*是数组*/}
// 方法二
Array.isArray(obj);

迭代器方法

检索数组内容的方法:keys()、values()和entries()。

  • keys()返回数组索引的迭代器
  • values()返回数组元素的迭代器
  • 而entries()返回索引/值对的迭代器

复制和填充方法

批量复制方法copyWithin(),以及填充数组方法fill()。这两个方法的函数签名类似,都需要指定既有数组实例上的一个范围,包含开始索引,不包含结束索引。使用这个方法不会改变数组的大小。

栈、队列的用法

使用push()、pop()方法可以把数组当成栈来用

使用shift()、push()方法可以把数组当成队列来用

通过使用unshift()、pop()可以在相反方向上模拟队列,即在数组开头添加新数据,在数组末尾取得数据

排序

reverse():反转数组

sort():默认按照升序排序(排序时会调用String()把元素都转成字符串进行比较)

let nums = [0, 1, 5, 10, 15];
nums.sort();
console.log(nums); // [0, 1, 10, 15, 5]

sort()接收一个比较函数,用于自定义排序规则==(返回大于0的数理解为两个参数需要换位置)==

function comp(v1, v2) {
    if (v1 < v2) return -1; // v1将排在v2前面
    else if (v1 > v2) return 1; // v1排在v2后面
    else return 0; // 不交换位置
}
nums.sort(comp);

操作方法

concat():在现有数组全部元素基础上创建一个新数组(不修改原数组)

如果concat()传入的参数是一个数组,可以给该数组设置[Symbol.isConcatSpreadable]来设置连接时是否将数组打平,默认为true,即默认打平

let color = ['red', 'green'];
let color2 = color.concat('yellow', ['black', 'blue']);
console.log(color); // ["red", "green"]
console.log(color2); // ["red", "green", "yellow", "black", "blue"] 连接的第二个值是一个数组,默认展开该数组的元素再进行添加

slice():创建一个包含原有数组中一个或多个元素的新数组,参数1开始索引,参数2结束索引,这两个参数都是可选的(支持切片),(该操作不影响原数组)

splice():该函数的主要目的是在数组中间插入元素,但是可以实现多种操作

  • 删除:传递2个参数,开始删除的下标、删除个数。如:arr.splice(0, 1):从0开始删除1个元素
  • 插入:传递3个参数,开始下标、删除个数、插入的值。如:arr.splice(2, 0, “red”, “blue”):从2开始删除0个元素,然后在该位置后面插入red和blue
  • 替换:传递3个参数,开始替换的下标、删除个数、插入的值。如:arr.splice(2, 1, “red”):从2开始删除1个元素,并插入red,相当于把下标为2的元素替换成red

splice()的返回值是删除的元素组成的数组,取决于第二个参数,如果是0则返回空数组,反之返回删除的元素组成的数组

搜索和位置方法

indexOf()、includes()从开头查找,lastIndexOf()从末尾查找

这3个方法都接收两个参数,参数一查找的元素,参数二是一个可选参数,开始位置

断言函数

断言函数接收3个参数:元素、索引和数组本身

find()和findIndex()方法使用了断言函数,参数一传递一个断言函数,参数二可选用于指定断言函数内部的this值

const evens = [2, 4, 6];
// 找到匹配后,永远不会检查数组的最后一个元素
evens.find((ele, index, arr) => {
    console.log(ele);
    console.log(index);
    console.log(arr);
    return ele === 4;
})

迭代方法

  • every():对数组每一项都运行传入的函数,如果对每一项函数都返回true,则这个方法返回true
  • filter():对数组每一项都运行传入的函数,函数返回true的项会组成数组之后返回
  • forEach():对数组每一项都运行传入的函数,没有返回值(无法停止,想要停下可以抛出异常并捕获)
  • map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
  • some():对数组每一项都运行传入的函数,如果有一项函数返回true,则这个方法返回true
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值