js基础(部分知识点,不定期更新!)

141 阅读18分钟

数据类型

基本类型

  • String
  • Number
  • boolean
  • undefined
  • null

对象(引用)类型

  • Object:任意对象
  • Function:一种特殊的对象(可以运行)
  • Array:一种特殊的对象(数据是有序的,属性是下标)

判断数据类型

  • typeof:返回数据类型的字符串表达(使用该方法时,均为小写)

    可以判断 String/Number/boolean/function/undefined

    null使用typeof进行判断时会返回object

    Array使用typeof进行判断时也会返回object

  • instanceof:用来判断对象的具体类型

  • ===(全等运算符,不会做数据转换)

    ===只能判断undefinednull

undefinednull的区别?

undefined代表定义了未赋值,而null表示定义了赋值为null。有时为了释放对象所占用的内存,可以将对象的引用设为null,从而使得该对象成为垃圾,从而被垃圾回收器进行回收释放。

严格区别变量类型与数据类型?

数据类型:基本类型和对象类型

变量类型:基本类型和引用类型

静态类型检查

静态类型语言

类型检查发生在编译阶段,因此除非修复错误,否则会一直编译失败

动态类型语言

只有在程序运行的时候错误才会被发现,因此即使代码中包含了会导致程序运行时阻止脚本正常运行的错误类型,这段代码也可以通过编译

js属于动态类型语言

静态类型检查的方法

  1. Flow是Facebook开发和发布的一个开源的静态类型检查库,它允许你逐渐地向JavaScript代码中添加类型。
  2. TypeScript是一个会编译为JavaScript的超集(尽管它看起来几乎像一种新的静态类型语言)

this问题

解析器在调用函数时,每次都会向函数传递一个隐含的参数,这个隐含的参数就是thisthis指向的是一个对象,这个对象称之为函数的上下文对象,根据函数的调用方式不同,this会指向不同的对象。

  • 在任何函数中this都指向window对象。(函数调用时,默认是window调用)

以构造函数的形式调用时,this是新创建的那个对象。

  • 在任何方法中this都指向调用者。

  • 使用callapply调用时,this是指定的那个对象。

  • 在事件的响应函数中,响应函数是给谁绑定的,this就指向谁。

    示例说明:

    let name = "全局变量";
    
    function fun() {
    console.log(this.name);
    }
    
    fun(); // 函数调用,相当于window.fun()
    
    let obj = {
    name: "花花",
    sayName: fun
    }
    
    // 方法调用
    obj.sayName(); // 全局变量,this指向window
    
    function fun1() {
    console.log(this.name);
    }
    
    let obj1 = {
    name: "花花",
    sayName: fun1
    }
    
    obj1.sayName(); // 花花,this指向obj1
    
    function MyClass() {
    console.log(this);
    }
    // 构造函数的执行流程见下文
    let obj = new MyClass(); // MyClass {}
    

使用工厂方法创建对象

当我们需要创建许多一类的对象时,直接使用对象的创建方法会导致代码出现大量的冗余:

 let person1 = {
     name: "花花",
     sex: "男"
 }
 
 let person2 = {
     name: "滋滋",
     sex: "女"
 }

这时可以用函数的方式创建一个对象,并将其返回,通过参数的形式接受对象参数:

 function createPerson(name, sex) {
     let obj = new Object();
     obj.name = name;
     obj.sex = sex;
     return obj;
 }
 
 let person1 = createPerson("花花", "男");

构造函数

在上面使用工厂方法时,如果我们需要另外创建一类具有相同属性的对象时,仍然需要类似的方法进行创建,而这里使用工厂方法创建对象时,使用的都是Object构造函数,因此无法区分。

image.png 为了解决这个问题,可以另外创建构造函数的方式。

构造函数就是一个普通函数,创建方式与普通函数没有区别,习惯性首字母大写

区别就是调用方式的不同:普通函数直接调用,而构造函数需要使用new关键字进行调用。

构造函数的执行流程:

  1. 立刻创建一个新的对象(开辟一块内存地址)
  2. 将新建的对象设置为函数中的this
  3. 逐行执行函数中的代码
  4. 将新建的对象作为返回值返回

使用构造函数创建对象后,很容易就区分了不同类别的对象。

image.png 使用构造函数时需要注意的一个问题是关于函数(方法)的使用,比如如下代码,我们在构造函数中创建了一个方法:

 function Person(name) {
     this.name = name;
     this.say = function() {
         console.log("我是:" + this.name);
     }
 }
 
 function Animal(name) {
     this.name = name;
     this.say = function() {
         console.log("我是:" + this.name);
     }
 }
 
 let person = new Person("花花");
 let dog = new Animal("小狗");
 
 console.log(person.say == dog.say);  // false

这是因为我们在构造函数内容定义方法,而每次调用构造函数都会新建一个方法,这些方法都是不同的方法;而另一个问题就是这些方法都是相同的功能,创建这么多方法无疑是对资源的浪费,我们可以将方法抽离出来,然后赋值回去,从而使得所有构造函数共享一个方法:

 function say() {
     console.log("我是:" + this.name);
 }
 
 function Person(name) {
     this.name = name;
     this.say = say;
 }
 
 function Animal(name) {
     this.name = name;
     this.say = say;
 }
 
 let person = new Person("花花");
 let dog = new Animal("小狗");
 
 console.log(person.say == dog.say);  // true                 

注意:这里将say方法定义在全局作用域里,污染了全局作用域的命名空间;同时不太安全(其他命名可能会将其覆盖掉)。

原型prototype

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

  • 注意:

    1. 如果函数作为普通函数调用prototype,没有任何作用。
    2. 当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性__proto__,指向该构造函数的原型对象,我们可以通过__proto__访问该属性。
  • 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,因此可以将对象中共有的内容,统一设置到原型对象中,从而解决了上述构造函数中出现的方法定义在全局作用域的问题。

  • 函数、构造函数创建的对象与原型对象之间的关系:

image.png

  • 当访问一个对象的某个属性或者方法时,会先从自身寻找,若没有找到,则会通过__proto__从原型对象身上寻找(原型链),若沿着原型链到头了还未找到,则返回undefined。那么我们如何确定某个属性是否是对象自身所具有的或者说是从原型身上取来的呢?我们知道in方法,可以判断对象中是否含有某个属性:
function MyClass() {

}

let mc = new MyClass();
mc.__proto__.name = "花花";
mc.age = 18;

console.log(mc.name); // "花花"
// 使用in检查
console.log("name" in mc); // truehasOwnProperty("age")); // true

从以上代码中,我们可以发现,mc对象中并没有name属性,但是返回的却是true,也就是说当对象自身没有该属性时,它仍会沿着原型链查找原型,所以我们需要换个方法:使用对象的hasOwnProperty()方法:

// 使用对象的hasOwnProperty()检查
console.log(mc.hasOwnProperty("name")); // false
// 通过以上方法,我们判断出mc对象自身是没有name这个属性的,那我们如何确定hasOwnproperty方法是
// mc对象所拥有的呢?明显我们并没有定义该方法
console.log(mc.hasOwnProperty("hasOwnProperty")); // false
// 此时我们发现,mc对象自身的确没有该方法,那么该方法又是否是从mc对象的原型上读取到的呢?
console.log(mc.__proto__.hasOwnProperty("hasOwnProperty")); // false
// 然而,mc对象的原型上也没有,mc对象的原型即原型对象,也有它自己的原型,再读取原型试试?
console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty")); // true
// 此时,我们终于发现,该方法是从mc对象原型的原型上所读取到的,该原型就是万物之主Object
// 那继续找原型链呢?
console.log(mc.__proto__.__proto__.__proto__); // null
// 由此可以看出Object没有原型,即它就是最原始的那个!

上述流程中,原型链如图:

image.png

instanceof的原理

我们知道,可以通过instanceof判断一个实例是否属于某种类型,其实现原理就是沿着实例的原型链不断查找原型对象

手动实现:

function newInstanceOf (ins, pro) {
  let proObj = pro.prototype; // 要查找的原型对象
  let insObj = ins.__proto__; // 实例的原型链
  while (true) {
    if (insObj === null) {
      return false;
    } else if (insObj === proObj) {
      return true;
    }
    else {
      insObj = insObj.__proto__;
    }
  }
}

let obj = {};
console.log(newInstanceOf(obj, Object)); // true
function Foo() {}
let foo = new Foo();
console.log(newInstanceOf(foo, Foo)); // true
let arr = [];
console.log(newInstanceOf(arr, Array)); // true

垃圾回收(GC

所谓垃圾,当一个对象没有任何变量或属性对其进行引用,此时我们就永远无法操作该对象(因为我们已经丢失了连接到该对象的途径),这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,因此我们必须对其进行处理(清理),可是我们已经丢失引用该对象的方法(换句话说就是对了该对象所在的内存地址),那我们如何清理呢?显然该工作并不是人为执行的,在JS引擎中,有自动垃圾回收机制,会自动地将垃圾对象从内存中销毁,我们只需要通过解除对对象的引用即可,到一定时间,JS引擎就会将其清理掉(释放掉该对象所占用的内存),我们解除引用的过程其实就是给JS引擎的一个信号。

let obj = new Object();

obj = null; // 解除引用

当我们将obj设为null时,即解除了对堆内存中对象的引用

image.png

数组常用方法

push

arr.push(data),向数组中添加元素(在末尾追加),可以添加若干个,用’,’分隔。

返回值:数组更新后的长度

 let arr = ['11', '22', '33'];
 let res = arr.push('44', '55');
 
 console.log(res); // 5
 console.log(arr); // ["11", "22", "33", "44", "55"]

pop

arr.pop(),删除数组的最后一个元素。

返回值:删除的元素

 let arr = ['11', '22', '33'];
 res = arr.pop();
 console.log(res); // 33

unshift

arr.unshift(data),向数组开头添加若干个元素。

返回值:更新后的数组长度

 let arr = ['11', '22', '33'];
 
 res = arr.unshift('44');
 console.log(res); // 4
 console.log(arr); // ["44", "11", "22", "33"]

注意:由于是向数组开头添加元素,因此添加完元素之后,原本的元素所在下标已经改变!!

shift

arr.shift(),删除数组第一个元素。

返回值:删除的元素。

 let arr = ['11', '22', '33'];
 
 res = arr.shift();
 console.log(res); // 11
 console.log(arr); // ["22", "33"]

from

Array.from(),该方法用于根据一个具有length属性或者可迭代的数据来生成一个数组并返回

every

检查数组中的每个元素是否通过检查

filter

  • 对数组中的元素进行过滤,接受一个参数,为当前遍历的元素,返回true时保留当前元素,为false则过滤掉

    const arr = [1, 10, 100, 3];
    
    const res = arr.filter(item => item % 2 === 0); // [10, 100]
    

reduce

arr.reduce(function(total, currentVal, currentValIndex, arr){}, initialVal),该方法可以看成是一个累加器

  • total:记录每次累加的总值,每次执行都要return,依次来更新total的值
  • initialval:初始化total的值

some

some() 方法用于检测数组中的元素是否满足指定条件(函数提供,参数:回调函数)

some() 方法会依次执行数组的每个元素:

  1. 如果有一个元素满足条件,则表达式返回true, 剩余的元素不会再执行检测。
  2. 如果没有满足条件的元素,则返回false。

注意:

  • some() 不会对空数组进行检测。
  • some() 不会改变原始数组。

参数说明:array.some(function(currentValue,index,arr),thisValue)

参数描述
currentValue必须。当前元素的值
index可选。当前元素的索引值
arr可选。当前元素属于的数组对象
thisValue可选。对象作为该执行回调时使用,传递给函数,用作 “this” 的值。 如果省略了 thisValue ,“this” 的值为 “undefined”

splice

arr.splice(start, length, ...)可以用来删除数组中的指定元素,并向数组中添加新的元素。

  • start:开始位置的索引。
  • length:删除的元素个数。
  • 第三个及以后:可以传递一个新的元素,这些元素会插入到开始索引位置前面。
  • 使用splice方法会改变原数组。
  • 返回值:被删除的元素封装成一个数组
 let arr = ['11', '22', '33'];
 
 let res = arr.splice(1, 1);
 console.log(res); // ["22"]
 
 let res = arr.splice(1, 0, '44');
 console.log(res); // []
 console.log(arr); // ["11", "44", "22", "33"]

slice

arr.slice(start, end)可以从数组中提取指定元素。

  • start:截取开始的位置(包含)。

  • end:截取结束的位置(不包含)。

    该参数可以省略不写,此时会截取从开始位置往后的所有元素。

  • start和end均可为负值,表示从后往前数,-1表示最后一个。

  • 返回值:将截取的元素封装到一个新的数组并返回。

 let arr = ['11', '22', '33'];
 
 let res = arr.slice(1, 2);
 console.log(res); // ["22"]
 
 res = arr.slice(1);
 console.log(res); // ["22", "33"]

concat

arr.concat(arr1,...)连接两个或多个数组。

  • 不会改变原数组。
  • 不仅可以传递数组,也可以传递元素。

返回值:合并后的新数组。

 let arr = ['11', '22', '33'];
 let arr1 = ['44', '55'];
 
 let res = arr.concat(arr1);
 
 console.log(res); // ["11", "22", "33", "44", "55"]
 console.log(arr); // ["11", "22", "33"]
 
 res = arr.concat(arr1, '66', '77');
 console.log(res); // ["11", "22", "33", "44", "55", "66", "77"]

join

arr.join(sep),将数组转换成一个字符串。

  • sep:字符串的分隔符,可以省略,默认为,
  • 不会改变原数组。

返回值:转化后的字符串。

let arr = ['11', '22', '33'];

let res = arr.join();

console.log(res); // 11,22,33
console.log(typeof res); // string

reverse

arr.reverse()反转数组。

  • 改变原数组。
let arr = ['11', '22', '33'];

arr.reverse();

console.log(arr); // ["33", "22", "11"]

sort

arr.sort(),对数组中的元素进行排序。

  • 改变原数组。

  • 排序方法:

    1. 默认按照Unicode编码进行排序。

    2. 即使是纯数字也是按照Unicode编码进行排序(可能导致错误的结果)。因此需要自定义排序规则。

      自定义排序规则:添加一个回调函数作为参数,该函数需要定义两个形参,浏览器会分别使用数组中的元素作为实参调用回调函数;

      使用哪个元素不确定,但是第一个参数在数组中一定在第二个参数前面;

      如果返回一个大于0的值,则会交换这两个元素的位置;如果返回一个小于或等于0的值,则元素位置不变。

let arr = [1, 3, 5, 2, 6];
arr.sort();
console.log(arr); // [1, 2, 3, 5, 6]

// 默认使用Unicode编码排序,存在问题。
let arr = [11, 1, 5, 2, 6];
arr.sort();
console.log(arr); // [1, 11, 2, 5, 6]

// 自定义排序规则

let arr = [11, 1, 5, 2, 6];
arr.sort((a, b) => {
  return a - b;
});
console.log(arr); // [1, 2, 5, 6, 11]

// TODO:该排序的底层原理后续可以深究

includes

arr.includes(element, fromIndex)该方法,可以查找数组中是否存在某个元素

  • 返回true/false
  • fromIndex默认为0,负值表示索引从后开始数(参数可选)

展开运算符...

通过展开运算符,可以实现数组的拼接

let list1 = [1, 2];
let list2 = [3, 4];
list1  [...list1, ...list2]; // list1 =  [1, 2, 3, 4]

数组遍历

forEach

arr.forEach((item, index, arr) => {}, thisValue)
  • 该方法需要一个函数作为参数(回调函数)。
  • 数组中有几个元素就会执行几次回调函数(每次遍历的元素会以实参的形式传递进来,总共接受三个参数:当前遍历的元素,当前遍历的元素的下标,遍历的数组)。
  • 该方法会改变原数组。
  • 这个方法只支持IE8以上(>8)的浏览器。

thisValue用于指定回调函数中的this,如果省略了thisValue,或者传入 nullundefined,那么回调函数的this为全局对象。

注意:若回调函数中使用的是箭头函数,则thisValue参数将毫无意义,因为箭头函数本身没有thisthis始终指向声明该函数的作用域

 arr.forEach((item, index, arr) => {
   console.log(item);
   console.log(index);
   console.log(arr);
 })

map

arr.map((item, index, arr) => {}, thisValue)

  • 返回处理后的新数组,新数组中的元素为回调函数每次执行时返回的值
  • 该方法不会改变原数组(若是改变原数组中对象的属性,则会影响原数组,该说法不绝对严谨)

thisValue用于指定回调函数中的this,如果省略了thisValue,或者传入 nullundefined,那么回调函数的this为全局对象。

注意:若回调函数中使用的是箭头函数,则thisValue参数将毫无意义,因为箭头函数本身没有thisthis始终指向声明该函数的作用域

数组去重

利用Set

set中的元素具有唯一性(会自动去重),先将数组转换成set,再将set转换成数组

let arr = [1,2,4,5,1,2];
let res = [...new Set(arr)];
console.log(res); // [1, 2, 4, 5]

利用splice

通过循环嵌套,判断数组中是否有重复元素

let arr = [1,2,4,5,1,2];
function judge(arr) {
  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);
      }
    }
  }
}

judge(arr);
console.log(arr); // [1, 2, 4, 5]

函数的方法

call()apply()都是函数对象的方法,需要通过函数对象来调用。对函数调用call()apply()都会调用函数执行。

function fun() {
  console.log("this is a function");
}

fun.call(); // this is a function
fun.apply(); // this is a function
fun(); // this is a function

那这两个方法有什么用呢?如果仅仅只是为了调用函数执行,貌似还更麻烦了一点。显然不会有人干这种无聊的事情,当我们调用call()apply()时可以将一个对象指定为第一个参数,此时,这个对象会成为函数执行时的this

function fun() {
  console.log(this);
}
let obj = {};

fun.call(); // Window {window: Window, self: Window, document: document, name: "",  
            // location: Location, …}
fun.apply(); // {}

当然这两个函数还可以传递多个参数,更详细语法下文:

call()

function.call(obj, param1, param2, ...)

obj为函数执行时this指向的对象,param1param2为与函数形参对应的实参。有形参但不提供实参时,默认为undefined

function fun(a, b) {
  console.log(this);
  console.log("a = " + a + " b = " + b);
}

let obj = {};

fun.call(obj, 1, 2);  // {} a = 1 b = 2
fun.call(obj); // {} a = undefined b = undefined

apply()

function.apply(obj, [param1, param2, ...])

obj为函数执行时this指向的对象,[param1, param2, ...]为与函数形参对应的实参,与call()方法不同之处是,apply()方法不能直接将实参一个一个传递进去,而是需要用一个数组进行封装再传递。有形参但不提供实参时,默认为undefined

function fun(a, b) {
  console.log(this);
  console.log("a = " + a + " b = " + b);
}

let obj = {};

fun.apply(obj); // {} a = undefined b = undefined
fun.apply(obj, [1, 2]); // {} a = 1 b = 2

字符串的方法

字符串在底层是以字符数组的形式进行存储,如:str = "Hello",会以["H", "e", "l", "l", "o"]的形式存储,因此可以通过下标进行访问某个字符(下标从0开始)。

字符串的大部分方法都不会改变源数据,会改变源数据的方法会单独标注~

charAt()

str.charAt(index)返回字符串中指定下标的字符。

charCodeAt()

str.charCodeAt(index)返回指定下标的字符的Unicode编码。

fromCharCode()

String.fromCharCode(),可以根据字符编码获取字符。

let str = String.fromCharCode(97);
console.log(str); // a

但是该方法存在一定的不足:无法识别码点大于0xFFFF的字符。

String.fromcharCode(0x20BB7) // ஷ

ES6中对该方法进行了补充:fromCodePoint,可以识别大于0xFFFF的码点,该方法如果接受多个参数,则它们会被合并成一个字符串返回。

String.fromCodePoint(0x20BB7) // 𠮷

String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y' // true

raw()

ES6提供了raw方法,该方法可以返回一个斜杠都被转义的字符串,即该方法可以进行斜杠的自动转义。

String.raw(Hi\n)

concat()

str.concat(str2, ...),可以连个若干个字符串。作用和+一样

indexOf()

str.indexOf(str, start),检索一个字符串中是否有指定内容,若存在,则返回其第一次出现的下标;若不存在,则返回-1。

  • str:需要检索的内容。
  • start:检索的起始位置。

lastIndexOf()

str.lastIndexOf(str, start),与indexOf()方法一样,只是该方法是从后往前查找。

截取字符串

slice()

str.slice(start, end)截取指定的内容,并将截取的字符串返回。

  • start:开始位置。(包含)
  • end:结束位置。(不包含,可省略–截取到最后)
  • 可以使用负数作为参数,从后开始计算。

substring()

str.substring(start, end),与slice()方法一样。

不同之处:

  • 这个方法不能接受负值作为参数,如果传递了一个负数,默认为0,并调整两个参数的次序(0在前面)。

substr()

str.substr(start, count)用来截取字符串。

  • start:截取的开始索引。
  • count:截取字符的长度。
  • ECMAscript没有对该方法进行标准化,不提倡使用。

split()

str.split(sep)将一个字符串拆分成数组。 与数组的join方法作用相反(join方法用来将数组连接成字符串)

  • sep:表示分隔符。若分隔符为空"",则将每个字符都分隔开。另外也可以传递一个正则表达式,这样使得拆分更加灵活。
let str = "1a2b3c4d5e6f7";
let reg = /[A-z]/;

console.log(str.split(reg)); // ["1", "2", "3", "4", "5", "6", "7"]

search()

str.search(str/reg)可以搜索字符串中是否含有指定内容。当传递的参数为一个字符串时,与indexOf方法没有任何区别,但是该方法比indexOf更灵活的是可以传递一个正则表达式进行搜索。同时该方法无法设置全局模式,找到一个就直接结束了。

let str = "bc aec adc ser";
let reg = /a[a-z]c/;
console.log(str.search(reg)); // 3

match()

str.match(reg)可以根据正则表达式,从一个字符串中将符合条件的内容提取出来。

  • 默认情况下,只会找到第一个符合条件的内容,找到后就停止。
  • 当我们设置匹配模式为g,即全局匹配模式时,就可以匹配到所有符合条件的内容了。
  • 该方法返回的是一个数组(因为可能存在多个结果)。

replace()

str.replace(old, new),可以将字符串中的内容替换为新的内容。

  • old,表示被替换的内容,该参数除了可以是字符串,也可以是一个正则表达式。
  • new,表示新的内容。

toUpperCase()

str.toUpperCase()将字符串转换成大写。

toLowerCase()

str.toLowerCase()将字符串转换成小写。

trim()

str.trim()去除字符串开头和结尾的空格。

arguments

在上文中,我们了解到,在调用函数时,浏览器每次都会传递一个隐含参数this,表示函数的上下文对象。这里的arguments为另外的隐含参数,表示封装实参的对象。

arguments是一个类数组对象,可以通过索引进行访问,也可以通过length属性获取长度,在调用函数时,我们所传递的所有实参都会被封装进该对象中,即使我们没有定义形参。这里出现了一个很神奇的现象,我们都没有定义形参,居然还可以传递实参并能够访问得到。

function fun() {
  console.log(arguments);
  console.log(arguments.length);
}

fun(); // Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ] 0

function fun1() {
  console.log(arguments[0]);
  console.log(arguments);
}
fun1(1, 2); // 1 Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]

在上面的代码中,我们验证了arguments确实是对实参的封装,同时我们还注意到,在打印出来的arguments中,都出现了一个叫callee的属性,该属性对应一个函数对象,就是当前正在执行的函数的对象。

function fun2() {
  console.log(arguments.callee);
}

fun2(); // ƒ fun2() {
        //     console.log(arguments.callee);
        //   }

正则表达式

正则表达式用于定义一些字符串规则,计算机可以根据正则表达式来检查一个字符串是否合法,或者将字符串中符合规则的内容提取出来。

语法:let reg = new RegExp("正则表达式", "匹配模式"),使用typeof检查正则对象,会返回object。除了使用构造函数创建以外,还可以使用字面量创建正则表达式:let reg = /正则表达式/匹配模式,虽然后者使用起来比前者要方便,但是前者会更灵活,比如当正则表达式为一个变量时,后者就不适用了。

匹配模式:

  1. i忽略大小写;
  2. g全局匹配模式。

可以为一个正则表达式添加一个或多个匹配模式,没有顺序要求。 如:/[a-z]/ig,表示全局搜索任意字母。

示例:

let reg = new RegExp('a');
let str = "agbser";

console.log(reg.test(str)); // true

let str1 = "gbser";
console.log(reg.test(str1)); // false

let str2 = "Agbser";
console.log(reg.test(str2)); // false

let reg1 = new RegExp("a", "i");
// 使用字面量创建
let reg2 = /a/i;
console.log(reg1.test(str2)); // true

从以上示例中我们发现可以通过正则表达式从字符串中检查是否有字符a,其实还有很多的规则可以进行检查:

  1. |,表示或的意思。reg = /a|b/,检查是否有a或b。
  2. [],也是或的意思。reg = /[ab]/,但是比|更方便,如:reg = /[a-z]/表示任意的小写字母,/[A-Z]/表示任意大写字母,/[A-z]/表示任意字母(因为A的ASCII码为65,z的ASCII码为97)。/[0-9]/表示任意数字。
  3. [^],表示除了[]里面以外的内容。/[^0-9]/表示非数字。
  4. {n},表示出现了n次,只对前面一个内容进行量化。/a{3}/表示aaa/(ab){3}/表示ababab。也可以用两个量词进行区间限制,{m,n},表示m个到n个字符。{m,}第二个量词空掉,表示至少有m个字符。
  5. +,表示至少一个字符,相当于{1,}
  6. *,表示任意个字符,相当于{0,}
  7. ?,表示0或1个字符,相当于{0,1}
  8. ^,表示开头,注意与[]中的^规则进行区别。
  9. $,表示结尾。
  10. .,表示任意字符(除了换行和行结束符),若想检查是否有.,则需要使用转义字符.,但是需要注意的是,如果我们使用构造函数创建正则对象let reg = new RegExp(".")对应到字面量则是/./,因为构造函数的参数是一个字符串,.在字符串中也是转义字符,因此转义后只有一个.了。
  11. \w,表示任意字母、数字和_,相当于/[A-z0-9_]/
  12. \W,表示除了字母、数字和_,相当于/[^A-z0-9_]/
  13. \d,表示任意数字,相当于/[0-9]/
  14. \D,表示除了数字,相当于/[^0-9]/
  15. \s,表示空格。
  16. \S,表格除了空格。
  17. \b,表示单词边界,用来限定,如:/\bchild\b/,表示要检查一个完整的单词"hello children"并不符合条件,结构应该是类似"hello child ren"这样,child作为一个独立的单词存在。
  18. \B,表示除了单词边界。

可以使用test()方法可以检查一个字符串是否符合正则表达式的规则reg.test(str) ,如果符合则返回true,否则返回false

内置对象

Math

Math对象的方法:

方法描述
ceil(x)进行向上取整
floor(x)进行向下取整
round(x)四舍五入
random()返回0–1之间的随机数,左闭右开

Es6中对Math对象的方法进行了许多补充:

Math.hypot:用于计算所有参数的平方的和的平方根,即(a²+b²)½

DOM

获取元素的类名

classList对象保存着控制当前元素类名的各个方法和属性。

Element.classList是一个只读属性,可以通过该属性实时获取元素的类名,为一个数组。

属性和方法

length:返回类名个数。

add():添加一个类名。

remove():删除一个类名。

toggle():如果有这个类名就删除,如果没有就添加。

item():根据索引获取类名。

contains():判断元素是否包含某一个类名。

文档节点相关

document.documentElement

该属性返回一个文档的文档元素,即html标签。

document.body

返回body标签。

document.createElement

通过该方法可以创建元素节点,可以传递一个参数,该参数为要创建的元素的名称。

复制节点

cloneNode()

该方法返回复制后的节点,可以传递一个参数true/false,若传递true,可以复制当前节点的所有子孙节点,若传递false(默认值),只复制当前节点。

获取元素关系

offsetParent
  • 获取当前元素的定位父元素(即获取离当前元素最近的开启了定位的祖先元素)。
  • 如果所有父元素都没有定位,则返回body
parentNode
  • 获取当前元素的父节点

获取元素位置

getBoundingClientRect()

该方法用来获取页面中某个元素的左、上、右、下分别相对浏览器窗口的位置(不包含被卷起的部分)。

该函数返回一个Object,有6个属性:top、left、right、bottom、width、height。其中top、left、width、height与常识一致,right表示元素右边界距离窗口最左边的距离,bottom表示元素下边界距离窗口最上面的距离。

offsetLeft

  • 获取相当于其定位父元素的水平偏移量。
  • 父元素都没有设置定位,默认以body为准。

offsetTop

  • 获取将对于其定位父元素的垂直偏移量。
  • 父元素都没有设置定位,默认以body为准。

scrollLeft

获取元素水平滚动的距离。

scrollTop

获取元素垂直滚动的距离。

当满足scrollHeight - scrollTop = clientHeight时,表示垂直滚动到底了。

当满足scrollWidth - scrollLeft = clientWidth时,表示水平滚动到底了。

获取元素宽高

clientHeight

获取元素的可见高度,返回数字,包括内容区和内边距。

只读属性。

当出现滚动条时,滚动条有默认的大小,因此内容区的大小会减少,此时clientHeightoffsetHeight返回的值是不同的,前者更小,因为滚动条占据了一部分大小。

clientWidth

获取元素的可见宽度,返回数字,包括内容区和内边距。

只读属性。

offsetWidth

获取整个元素的宽度,包括内容区,内边距和边框。

只读属性。

offsetHeight

获取整个元素的高度,包括内容区,内边距和边框。

只读属性。

scrollHeight

获取元素整个滚动区域的高度。

scrollWidth

获取元素整个滚动区域的宽度。

事件对象

给元素设置一个事件时,会将一个事件对象作为实参传进相应函数,在事件对象中封装了当前事件相关的一切信息。(IE8以下没有该特性,因此需要通过window.event进行调用)

box.onmousemove = function(event) {
    console.log(event); // 事件对象
}

解决兼容性问题:event = event || window.event;

事件对象属性

鼠标事件
  • onmousedown
  • onmouseup
  • onmousemove
  • onmousewheel(鼠标滚轮滚动事件,火狐不支持该属性,使用DOMMouseScroll替代,需要通过addEventListener函数来绑定)
鼠标事件对象属性
属性描述
clientX返回当前事件被触发时,鼠标指针的水平坐标(数值),该坐标表示的是浏览器可见窗口内的坐标。
clientY返回当前事件被触发时,鼠标指针的垂直坐标(数值),该坐标表示的是浏览器可见窗口内的坐标。
pageX相对于当前页面的水平坐标。(该属性不支持IE8)
pageY相对于当前页面的垂直坐标。(该属性不支持IE8)
wheelDelta判断鼠标滚轮方向,正数表示向上滚动,负数表示向下滚动。(火狐不支持,使用event.detail替代,负数表示向上滚动,正数表示向下滚动)
键盘事件
  • onkeydown
  • onkeyup

当绑定的函数中执行return false时,阻止默认行为,不会输出按下的键。

键盘事件对象属性
属性描述
keyCode获取按键的编码,按下a按键,返回65。
altKey是否按下alt,按下返回true。
ctrlKey是否按下ctrl,按下返回true。
shiftKey是否按下shift,按下返回true。
标准事件对象属性
属性描述
target返回触发此事件的元素

事件绑定

  • box.onclick = function() {},该方法只能为一个元素的一个事件绑定一个响应函数,后绑定的会覆盖掉前面绑定的。

  • box.addEventListener("click", function() {}, false),可以绑定多个响应函数,按照绑定顺序执行。该方法不支持IE8以下。

    函数参数说明:

    1. 事件名,不要on
    2. 回调函数,当事件触发时调用。
    3. 是否在捕获阶段触发事件,布尔值,一般都传递false(默认),该参数可选。

    attachEvent("onclick", function() {}),在IE8中可以用该方法进行替代。后绑定的先执行。

  • addEventListenerattachEvent进行兼容:

    function bind(obj, eventStr, callback) {
        if (obj.addEventListener) {
            obj.addEventListener(eventStr, callback);
        } else {
            obj.attachEvent("on" + eventStr, function() { // 这里之所以要通过匿名函数进行主动调用回调函数,是因为attachEvent方法中的this指向的是window,而不是obj,因此我们通过此方法进行统一。
                callback.call(obj);
            })
        }
    }
    
  • 取消事件的绑定:

    1. 将事件设为null:box.onmousemove = null;
  • 事件绑定后,一些浏览器的默认行为可能会执行,但可能会带来一些异常,如果想取消这种默认行为,可以在绑定的执行函数里执行return: false;。(默认行为例如:拖拽页面内的某些元素时,浏览器会默认去搜索引擎中搜索)

    但是如果是通过addEventListener方法绑定,则需要通过event.preventDefault来取消默认行为。

事件冒泡

事件冒泡就是指事件的向上传导,内部元素触发事件,外部元素的相同事件也会被触发。

如果不希望发生事件冒泡,可以通过事件对象event.cancelBubble = true取消冒泡。

事件委派

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

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

在进行事件委派时,可能存在我们只期望通过父元素内部的某些子元素触发事件,因此需要进行事件触发判断。

事件传播

事件传播分为3个阶段:

  1. 捕获阶段

    • 在捕获阶段时从最外层的祖先元素,向目标元素进行事件的捕获,但是默认此时不会触发事件。
    • 如果希望事件在捕获阶段就触发,可以将addEventListener方法的第三个参数设置为true
  2. 目标阶段

    • 事件捕获到目标元素,捕获结束,开始在目标元素上触发事件。
  3. 冒泡阶段

    • 事件从目标元素向它的祖先元素传递,依次触发祖先元素上的相同事件。

获取元素样式

我们知道,在原生js中,可以通过element.style.xxx的方式给元素设置样式,并且这种方式设置的是内联样式:

使用style修改样式时,每修一个样式,浏览器就要重新渲染一次页面,影响性能。

<body>
  <div>234</div>
</body>

<script>
    let div = document.querySelector("div");
    div.style.width = "200px";
<script/>

在浏览器中,我们进行验证:

image.png

通过嵌入式的方式设置样式,然后通过js进行获取,看看能否读取到:

<head>
  <style>
    div {
      background-color: skyblue;
    }
  </style>
</head>

<body>
  <div>234</div>
</body>

<script>
    let div = document.querySelector("div");
    div.style.width = "200px";
    console.log(div.style.backgroundColor);
<script/>

image.png

结果我们发现控制台输出为空,即没有获取到,这是因为通过原生element.style.xxx的方式读取样式,仍然是读取的内联样式,而无法读取到嵌入式设置的样式表中的样式:

<head>
  <style>
    div {
      background-color: skyblue;
    }
  </style>
</head>

<body>
  <div style="height: 400px">234</div>
</body>

<script>
    let div = document.querySelector("div");
    div.style.width = "200px";
    console.log(div.style.backgroundColor);
    console.log(div.style.height);
<script/>

image.png

虽然该方法无法读取到嵌入式设置的样式表中的样式,但是window属性中有一个叫getComputedStyle的方法,可以获取到样式表中的样式,语法:window.getComputedStyle(obj, pseudoElt)

  • obj:要获取的元素
  • pseudoElt:指定一个要匹配的伪元素的字符串,通常设置为null
  • 返回值:style是一个实时的对象,当元素的样式更改时,它会自动更新本身。
 // 其余代码同上
 let color = window.getComputedStyle(div, null)["backgroundColor"];
 console.log(color); // rgb(135, 206, 235)

因此,想要读取样式表中的样式,直接使用该方法即可,需要注意的是,该方法对于IE浏览器,只支持IE9+的版本,对于IE8-的版本支持的是element.currentStyle这个属性,如果想要兼顾兼容性问题的话,最好是单独封装出一个函数:

function getStyle(ele, name) {
  if(window.getComputedStyle) {
    return window.getComputedStyle(ele, null)[name];
  } else {
    // IE8
    return ele.currentStyle[name];
  }
  
  // 简洁写法:return window.getComputedStyle ? window.getComputedStyle(ele, name)[name] : ele.currentStyle[name];
}

BOM

  • 浏览器对象模型。

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

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

  • BOM对象:

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

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

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

    • History:代表浏览器的历史记录,可以通过该对象来操作浏览器的历史记录,但是由于隐私原因,该对象不能获取到具体的历史记录,只能操作浏览器向前或者向后翻页,而且该操作具有时效性,只在当前窗口有效,关闭后再打开无效。

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

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

Window方法

  • setInterval,定时器,单位毫秒,定时调用。

    setInterval(function(){}, time),返回值:一个Number类型的数据,这个数字用来作为定时器的唯一标识。

  • clearInterval,清除定时器,需要一个定时器标识作为参数。

  • setTimeout,延时器,单位毫秒,延时调用,只执行一次。

    setTimeout(function(){}, time),返回值:一个Number类型的数据,这个数字用来作为延时器的唯一标识。

  • clearTimeout,清除延时器,需要一个延时器标识作为参数。

Navigator属性

属性名描述
appName返回浏览器的名称
userAgent返回由客户机发送服务器的user-agent 头部的值

History属性

属性名描述
length获取当次访问的链接数量

方法

方法名描述
back回退到上一个页面
forward前进到下一个页面
go跳转到指定页面,需要有一个整数作为参数,标签前进或者后退几个页面(1:前进一个页面,-1:后退一个页面)

Location属性

属性名描述
hash返回一个URL的锚部分,即从#开始
host返回一个URL的主机名和端口
hostname返回URL的主机名
href返回完整的URL
pathname返回的URL路径名
port返回一个URL服务器使用的端口号
protocol返回一个URL协议
search返回一个URL的查询部分

直接打印location,可以获取到当前页面的完整路径。

直接修改location为一个完整路径或者相对路径,会自动跳转到该路径,并生成历史记录。

方法

方法名描述
assign跳转到其他页面,相当于直接修改location,目标地址为参数。
reload刷新,如果参数传入一个true,会强制清空缓存并刷新。
replace使用一个新的页面替换当前页面,类似于跳转,不会生成历史记录。

JSON

  • JSON是一种特殊形式的字符串,用于前后端交互用,因为js和后端java/php等定义的对象是不能等同的,因此需要互相传递参数时,可以通过JSON来实现

  • 这个字符串可以被任意语言识别,并且转换成其对应语法中的对象

  • JSONJS对象的格式一样,只是属性名必须加上双引号,其他与JS语法一致

  • JSON分类:

    1. 对象:{}
    2. 数组:[]
  • JSON中允许的数据类型:

    1. 字符串
    2. 数值
    3. 布尔值
    4. null
    5. 对象
    6. 数组

JSON全称:JavaScript Object Notation(JS对象表示法)

JS中为我们提供了一个工具类(就叫JSON),专门用来操作JSON字符串

  1. JSON.parse():该方法可以将JSON字符串转换为js中的对象/数组
  2. JSON.stringify():该方法可以将js中的对象/数组转为一个JSON字符串

语法:

// json -> js对象
let json = '{"name": "阿狸"}';
let arr = "[1,2,3]";
const obj = JSON.parse(arr); // [1, 2, 3]

// js对象 -> json
let obj2 = {
  name: "JSON"
}
let res = JSON.stringify(obj2);
console.log(res); // '{"name":"JSON"}'

IE7及以下不支持,可以通过eval函数替代

  • 这个函数可以执行一段字符串形式的js代码,并将执行结果返回
  • 如果执行的字符串中含有{},该函数会将其认定为代码块,如果此时{}表示的是对象,可能会报错,此时需要将字符串用()括起来
  • 开发中尽量避免使用该方法,因为它的执行性能比较差,并且具有安全隐患
  • 兼容问题,建议通过引入外部js文件来解决

Web Components

web组件主要包含以下几个内容:

  1. HTML模板,即template标签
  2. 自定义元素
  3. 影子DOMShadow DOM)和slot标签

这里有些概念与vue中的很类似,因为vue中的插槽slot也参考了一些Web Components规范的内容

HTML模板

template标签是基于HTML构建DOM子树,然后在需要的时候可以把这个子树再渲染出来,浏览器会将该标签解析为DOM子树,但跳过渲染

实践应用:

<body>
  <template class="box">
    <div>asfsfasf</div>
  </template>

  <script>
    const box = document.querySelector(".box").content;
    setTimeout(() => {
      document.body.appendChild(box);
    }, 1000);
  </script>
</body>

观察以上代码,当我们没有设置延迟器时,页面中是没有显示任何元素的,而当我们添加延迟器后,模板中的内容就会显示到页面中

image.png

没有添加延迟器时HTML文档树中显示的内容更完整

image.png 也就是说,只有当我们将模板元素添加到DOM树中,才会执行模板中的内容,根据这个特征,可以引用到延迟脚本中,即模板中为一段js代码,只有我们将该模板添加到DOM树中,才会执行这个代码段

从图中我们观察到,模板标签是基于DocumentFragment实现的,DocumentFragment是一个虚拟的节点对象,可以把它理解成占位符,或者说是文档碎片节点,可以将元素批量的添加到DocumentFragment中,然后再将DocumentFragment添加到DOM树中,可以减少DOM的渲染次数,提高效率

示例:通过DocumentFragment将三个p标签一次性地添加到body

<body>

  <script>
    const colors = ['red', 'green', 'blue'];
    let fragment = document.createDocumentFragment();
    for (let color of colors) {
      let p = document.createElement("p");
      p.style.backgroundColor = color;
      p.innerHTML = `I am ${color}`;
      fragment.appendChild(p);
    }

    document.body.appendChild(fragment);
  </script>
</body>

结果如下 image.png

影子DOM

可以将影子DOM理解成DOM树中某个元素的子树,该子树具有独立的空间,该特性表明,可以将CSS样式作用在该空间,而不是作用于顶级DOM

相关概念:

  • attachShadow():可以使用该方法创建并添加给父元素一个影子节点,并返回创建的影子节点,该方法需要传递一个初始化对象{ mode: 'open' / 'closed' },设置是否可以访问影子根节点
  • shadow host:影子宿主,即容纳影子节点的元素
  • shadow root:影子节点的根结点

基本使用:给box元素创建影子节点,并在影子节点中创建p标签

<body>
  <div class="box">This is a div</div>
  <script>
    const box = document.querySelector(".box");
    let openShadow = box.attachShadow({ mode: "open" });
    openShadow.innerHTML = `
      <p>This is a p</p>
      <style>
        p {33
          background-color: blue;
          color: white;
        }
      </style>
    `
  </script>
</body>

结果如下 image.png 我们发现影子节点的样式并只作用在影子节点中,并且外部样式也没有影响到影子节点,同时还可以发现,影子根中的元素居然没有显示,只显示了影子的内容,这是因为,影子DOM一旦渲染到元素中,浏览器就会给他赋予最高优先级,优先渲染它的内容,而不是元素(影子宿主)中原来的内容,为了避免漏掉原来的内容,可以在影子节点中使用slot标签进行占位,元素中原来的内容将会现在slot标签的位置,并且元素中的标签可以设置slot属性,对应到影子节点中slot标签的name属性,进行位置匹配

示例 image.png

需要注意的是,并非所有元素都可以添加影子节点,尝试给无效元素或者已经有了影子节点的元素添加影子节点会导致抛出错误

自定义元素

浏览器默认会将无法识别的元素作为通用元素整合进DOM树中

image.png

而自定义元素可以在此基础上更进一步,可以定义更复杂的行为或者拓展现有元素,比如元素出现时执行一些逻辑等等,也可以在DOM中将其纳入元素生命周期管理

自定义元素的定义方式是类定义,这一点就可以看出其功能的强大

由于这一点平时见的不多,感觉不咋常用,这里不多介绍