JS

298 阅读11分钟

js包括 core、bom、dom

DOM是一套操作HTML标签的API(接口/方法/属性)
BOM是一套操作浏览器的API(接口/方法/属性) www.jianshu.com/p/eac7f9dc3…

一、ES5

ECMA Script是一种由ECMA组织(前身为欧洲计算机制造商协会)制定和发布的脚本语言规范 ES5 即ECMAScript5 ,是javascript的语言的标准的一版。

严格模式

blog.csdn.net/qq_41238274…

总结

  • 变量必须声明
  • 不能使用eval作为变量和参数 eval会自己创建一个作用域
  • 必须使用new 来调用构造函数

1、不使用严格模式时

globalVal = 100
//这个变量是在window下面,是全局变量

使用严格模式

“use strict”
globalVal = 100//这种会报错
var globalVal = 100//必须用var声明

2、带eval的操作会被禁止

不使用严格模式时

var eval = 100
console.log("eval",100)//打印出100

使用严格模式

"use strict"
var eval = 100
function fun(eval){}

//上面两种情况会报错

eval函数的作用:计算某个字符串、将字符串解析出来
www.w3school.com.cn/js/jsref_ev…
不使用严格模式

var a = 100
eval("var a = 200;console.log(a)");
console.log(a);
//200 200

使用严格模式
创造了一个eval的作用域

var a = 100
eval("var a = 200;console.log(a)");
console.log(a);
//200 100

函数中this的引用

function Person(name,age){
    this.name = name;
    this.age = age;
}
//this->当前实例对象
var p = new Person("tom",20)
console.log(p.name,p.age)//打印出tom,20
//this->windows
Person("tom",20)
console.log(name,age)//打印出tom,20

运行结果

Tom
Tom
undefined
Michael
Person { name: 'Michael' }

使用严格模式

"use strict"
function Person(name,age){
    this.name = name;
    this.age = age;
}
//this->当前实例对象
var p = new Person("tom",20)
console.log(p.name,p.age)//打印出tom,20

Person("tom",20)
console.log(name,age)//报错,这时函数自调用中的this->undified

Array扩展

1、数组遍历

var array = ['java','php','js','c']
array.forEach(function(item,index){
    console.log(item,index)
}

2、数组过滤

var ret = array.filter(function(item){
  if(item.length>=3){
  return true
  }
})
console.log(ret)//打印新数组{java,php}

3、map

var ret2 = array.map(function(item){
    return "IT-"+ item
})
console.log(ret2)//打印出新数组{'IT-java','IT-php','IT-js','IT-c'}

Function扩展

js的面向对象是通过function改过来的,之前不是面向对象

var obj = {name:"tom"}
function fun(a,b){
    console.log(this,a,b)
}
fun()//window,undified,undified

调用函数的几种方式

//call与apply函数见《前端基础知识》那篇文章
fun.call(obj,1,2)//{name:"tom"},1,2
fun.apply(obj,[1,2])

fun.bind(obj)(1,2)//绑定对象
setInterval(function(){
    console.leg(this)
}.bind(obj),1000)//每隔一秒钟打印{name:"tom"}

二、ES6

块作用域

1、let和const

var a = []
for(var i=0;i<10;i++){
    a[i] = function(){
        console.log(i)
    }
}
console.log(a[6])//输出funcion
a[6]()//输出10,因为绑定的i是一个全局变量

这个问题可以用let或者闭包解决

解决方法一 IIFE(立即执行函数表达式)/闭包(具体可看《前端基础知识》)

var a = []
for(var i=0;i<10;i++){
    (function(i){//函数内又有一个函数,形成了一个闭包,延长了局部变量的作用范围
        a[i] = function(){
            console.log(i)
        }
    })(i)
}
console.log(a[6])//输出funcion
a[6]()//输出6

解决方法二 使用let

var a = []
for(let i=0;i<10;i++){
    a[i] = function(){
        console.log(i)
    }
}
console.log(a[6])//输出funcion
a[6]()//输出6
  • js中的变量提升(具体见《前端基础知识》)如果用let,则不存在变量提升
var a = 100
function fun(){
    console.log(a)
    var a = 200
}
fun()//undefined
  • var可以重复定义,let不可以
var a= 100
var a = 200
console.log(a)//200

最开始在严格模式中不允许重复定义,接着出现了let,也不允许重复定义

let a= 100
let a = 200
console.log(a)//报错
  • const也不存在变量提升
console.log(MAX)
const MAX=100//报错
const obj = {a:100}

变量的结构赋值

1、数组的结构赋值

以前的写法
var array = [1,2,3]
var a= array[0]
var b = array[1]
var c = array[1]
现在的写法
let [a,b,c] = [1,2,3]
console.log(a,b,c)//123

let [a,b] = [1,2,3]
console.log(a,b)//1,2

let [a,b,c] = [1,2]
console.log(a,b,c)//1,2,undefined

let [foo,[[bar],baz]] = [1,[[2],3]]
console.log(a,b,c)//1,2,3

...表示数组
let [x,...y] = ["foo","bar","baz"]
console.log(x,y)//foo,["bar","baz"]

let [x,y="b"]=["a"]
console.log(x,y)//a,b

2、对象的解构赋值

let { foo, bar } = { foo: "aaa", bar: "bbb" }
console.log(foo, bar)

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

let { bar, foo } = { foo: "aaa", bar: "bbb" }
console.log(foo, bar)


let { baz } = { foo: "aaa", bar: "bbb" }
console.log(baz)//undefined

let { max, min } = Math//max、min都定义为Max的对象
console.log(max(1, 3))//输出最大值,3

对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者

//变量名和对象属性名不一致
let { bar: baz } = { foo: "aaa", bar: "bbb" }
console.log("baz", baz)//baz bbb
console.log("bar", bar)//报错:bar is not defined
//默认值设定
let { x = 3 } = {}
console.log(x)

3、字符串的解构赋值

let [a, b, c, d, e] = "hello"
console.log(a, b, c, d, e)//h e l l o

//对象赋值为字符串时,对象为字符串的长度
let { length: len } = "hello"
console.log(len)//5

4、函数的解构赋值

function add([x, y]) {
    return x + y
}
console.log(add([1, 3]))//4

let arr = [[1, 2], [3, 4]]
var arr2 = arr.map(function ([a, b]) {
    return a + b
})
console.log(arr2)//[3,7]

字符串的扩展

1、模板字符串

let name = "tom"
let str = 'hello' + name
console.log(str)

let str2 = "hello"${name} 
console.log(str2)//hello tom
let str3 = "hel
      lo${name}"//回车也会显示
function fun(){
    return "Hello World"
}
let str = 'foo ${fun()} bar'
console.log(str)//将函数拼接
不使用模板字符串
let emps = [{name:"tom",age:30},{name:"jack",age:40}]
let innerHTML = "<table border=\"1\" width=\"300px\">"
emps.forEach(function(emp){
    innerHTML +="<tr><td>"+emp.name+"</td><td>"+emp.age+"</td></tr>"
})
innerHTML += "</table>""
document.write(innerHTML)
使用模板字符串时
let emps = [{name:"tom",age:30},{name:"jack",age:40}]
let innerHTML = `<table border="1" width="300px">
${emps.map(function(emp){
    return `<tr><td>${emp.name}</td><td>${emp.age}</td></tr>`
}).join("")}
</table>`

document.write(innerHTML)

2、标签模板

let a = 5
let b = 10 
function tag(stringArr,value1,value2){
    console.log("stringArr",stringArr)
    console.log("value1",value1)
    console.log("value2",value2)
}
tag `hello $(a+b) world ${a*b}!`
//{hello ,world ,!}, 15,50
原本的字符串传入第一个,另外两个模板字符串传入另外两个参数

通过函数对模板字符串进行处理,过滤掉不安全的东西

let course = "<script>alert('xxx')<\/script>"
function safeHTML(data,params){
    console.log(arguments)//所有的参数
    console.log(data)
    console.log(params)
    let s = data[0]
    for(let i = 1;i<arguments.length;i++){
        let arg = arguments[i]
        s+=arg.replace(/&/g,"&amp;")将&,>,<替换为html中的转义字符,/g表示全局
        .replace(/</g,"&lt;")
        .replace(/>/g,"&gt;")
        s+=data[i]
    }
    return s
}
let strHTML2 = safeHTML `<p>我们要开始学习${course}的课程了</p>`
document.write(strHTML2)

函数的扩展

1、函数参数的默认值

function fun(x,y="world"){
    console.log(x,y)
}
fun("hello")//hello,world
fun("hello","JS","xx")//hello ,JS

与解构赋值结合使用

function fun1({x,y=5}={}){//传入的参数是一个对象,给一个对象赋予一个默认值
    console.log("fun1",x,y)
}
fun1({})//undefined,5
fun1({x:1})//1,5
fun1({x:1,y:2})//1,2

2、rest参数

//将函数多余的参数,放入到rest参数对应的数组中
function add(...values){//这是rest参数的写法,rest参数只能放在最后一个参数的位置写
    console.log(values)
    let sum = 0
    for(let i = 0;i < values.length;i++){
        sum += values[i]
    }
    return sum;
}
console.log(add(1,2,3,4,5))

www.jianshu.com/p/50bcb376a… rest参数与arguments对象的区别

3、箭头函数
箭头函数的this指针见《前端基础知识》

var f1 = function(v){
    return v
}
console.log(f1(5))//5

var f2 = v => v  //与f1相同
console.log(f2(5))//5
//函数没有参数,用()代表函数参数部分
var f3= () => 5

//函数有多个参数,用()代表函数参数部分
var f4 = (a,b) => a+b
console.log("f4()",f4(1,2))//f4() 3

var f5 = (a,b)=>{
    var sum = a+b
    return sum
}
console.log("f5()",f5(1,2))//f5(),3

//返回结果为对象时,必须在对象外面加上一个括号
var f6=() => ({name :"tom"})

//对象作为形参时,解构赋值
var f7 = ({a,b}) => a + "," + b
console.log("f7",f7({a:1,b:2}))//f7,1,2

var f8 = (...numbers) =>numbers
console.log(f8(1,2,3,4))//1,2,3,4

下面两个函数相同

var arr1 = [1,2,3].map(function(x){
    return x*2
})
var arr2 = [1,2,3].map(x => x*2)

4、箭头函数中this的引用

具体见《前端基础知识》

function Person(name,age){
    this.name = name;
    this.age = age;
    
    //setInterval函数是由windows调用的
    setInterval(function(){
        console.log(this,name,age)//windows,name,age
    },1000)
}
function Person(name,age){
    this.name = name;
    this.age = age;
     let _this = this
    //这样子可以通过this调用
    setInterval(function(){
        console.log(this,this.name,this.age)//windows,name,age
    },1000)
}
function Person(name,age){
    this.name = name;
    this.age = age;

//箭头函数没有自己的this,他的this是继承而来,默认指向定义它的所处的对象(宿主对象)
    setInterval(() => {
        console.log(this,this.name,this.age)
    },1000)
}

let p1 = new Person("tom","12")

箭头函数不能当作构造函数使用,不可以使用new命令

let fun(){
    console.log("fun is called")
}
let f = new fun()//报错
let fun(){
    //不可以使用arguments,最好使用rest参数
    console.log(aruguments)
    console.log("fun is called")
}
fun()

数组的扩展

1、扩展运算符

//扩展运算符,是rest参数的逆运算
//将一个数组中的数据,转为参数的序列
console.log(...[1,2,3])//1,2,3
function add(x,y){
    return x + y
}
let params = [4,5]
//add(params[0],params[1])
console.log(add(...params))//9

数组的复制

let a1 = [1,2]
let a2 = a1 //相当于将a1的地址给了a2
a2[0] = 2
console.log("a1",a1)
console.log(a1 == a2)//true

//使用concat方法,将两个数组合并,并返回一个新的数组
let a1 = [1,2]
let a2 = a1.concat()
console.log(a2)
console.log(a1 == a2)//false,因为a2是一个新的数组

let a1= [1,2]
let a2 = [...a1]
console.log(a1 == a2)//false

数组的合并

let a1 = ["a","b"]
let a2 = ["c"]
let a3 = ["d","e"]
let a4 = a1.concat(a2,a3)
console.log("a4",a4)//[a,b,c,d,e]

let a5 = [...a1,...a2,...a3]
console.log(a5)//[a,b,c,d,e]

如果数组里面是对象

let obj1 = {foo:1}
let obj2 = {bar:1}
let array1 = [obj1]
let array2 = [obj2]
let array3 = array1.concat(array2)
let array4 = [...array1,...array2]
console.log(array3)//[{foo:1},{bar:1}]
console.log(array4)//[{foo:1},{bar:1}]

console.log(array3[0] == array1[0])//true.因为这是浅复制,即只指向了相同的地址,如果修改array3,则也会修改掉array1
console.log(array4[0] == array1[0])//true

与解构赋值结合

let [first,...rest] = [1,2,3,4,5]
let a1 = [..."Hello"]
console.log(a1)//["H","e","l","l","o"]

对象的扩展

1、对象属性的简介表示

var obj = {name:'',age:19}

//两种获取属性的方法
obj.name = "jack"
console.log(obj.name)

obj["name"] = "mike"
console.log(obj["name"])

必须要用[]获取属性的情况
1、属性是变量

let param = "gender"
obj.param = "male"
console.log(obj)//{..... param:male},这是有问题的

let param = "gender"
obj[param] = "male"
console.log(obj)//{.... gender:"male"}

2、属性中包含特殊字符

obj.x-z="xxxx"//会报错
obj["x-z"] = "xxxx"//成功
let foo = "bar"
let baz1 = {foo : foo}
console.log(baz1)//{foo:"bar"}

简略的写法
let baz1 = {foo}
console.log(baz1)//{foo:"bar"}

function fun1(x,y){
    return {x:x , y:y}
}
console.log(fun1(1,2))//1,2
简单写法
function fun1(x,y){
    return {x , y}
}
console.log(fun1(1,2))//1,2

2、属性名表达式

let obj1 = {}
onj1.foo = true
obj1."a"+"bc" = 123//错误,这种形式不支持表达式

obj1["a" + "bc"] = 123//正确
console.log(obj1)//{foo:true , abc:123}

函数名也可以为表达式

let obj3 = {
    ["a" + "bc"]() {
        
    }
}

3、对象方法的简洁表示

let obj1 = {
    fun:function (){
        return "hello"
    }
}
console.log(obj1.fun())

简单写法
let obj2 = {
    fun(){
        return "hello"
    }
}
console.log(obj1.fun())

【练习】
1、编写一个js函数,实现对一个数组去重的功能

let a = (tmps)=>{
    let arr = []
    for(let i = 0; i < tmps.length;i++){
        if(arr.indexOf(tmps[i])==-1){//如果tmps中的元素没有在arr中出现过,则加入arr
            arr[i] = tmps[i]
        }
    }
    return arr
}
console.log(a([1,2,3,4,1,2,3]))

2、编写一个js函数统计字符串中每个字符出现的频率

Set和Map数据结构

1、set的基本使用 set类似数组,但成员的值都是唯一的,不会有重复

let s = new Set()
let arr = [1,2,3,3,2,1]
arr.forEach(x = > s,add(x))
console.log(s)//[1,2,3]

set的遍历

for(let i of s){
    console.log(i)
}
//array转为set
let s = new Set([1,2,3,3,2,1])
//set再转为array
let array = [...s]//[1,2,3],这样子也可以去重

console.log([...new Set("aabbcc")].join(""))//abc
let s = new Set()
let a = 1
let b = "1"
s.add(a)
s.add(b)
//可以加进去,说明set中判断两个元素是否相同采用的是“===”,因此1和“1”都可以放进去

let c = {}
let d = {}
s.add(c)
s.add(d)
//也可以加进去,因为他们两个地址不同

set的方法

let s = new Set()
s.add(1).add(2).add(3)
console.log(s.size)//3
console.log(s.has("1"))//false

s.delete(2)
console.log(s.has(2))//false
//array转为set
let items = new Set([1,2,3])
console.log(items instanceof set,items instanceof Array)//true,false

//set转为array
let array = Array.from(items)
console.log(array instanceof Set,array instanceof Array)

因此上面的练习题
**“编写一个js函数,实现对一个数组去重的功能”**可以写成下面的代码

function change(array){
    return Array.from(new Set(array))
}

set没有key,只有value(key = value)

let s = new Set(["red","green","blue"])

for(let item of s.keys){
    console.log(item)//"red","green","blue"
}
for(let item of s.values){//这里的values可以省略
    console.log(item)//"red","green","blue"
}
for(let item of s){//这里的values可以省略
    console.log(item)//"red","green","blue"
}

//entries 是key和value的集合
for(let item of s.entries){
    console.log(item)//{"red","red"},
    {"green","green"},{"blue","blue"}
}

s.forEach((v,k) => console.log(k,v))//red red
green green  blue blue

2、Map的基本使用
Map与对象类似,也是键值对的组合
Map 键的范围不限于字符串,可以是各种数据类型

let m = new Map()
let o = {a: "hello"}
m.set( o,"content" )
console.log(m)  //key:{a: "hello"} value:"content"

console.log(m.get(o))// 获取value值   输出content

Map与二维数组的转换

let map = new Map([["name","tom"],["age",18]])
console.log("map",map)
//{"name":"tom"},{"age":18}

Set与Map的转换

let set = new Set([["foo",1],["bar",2])
let m = new Map(set)
console.log("m",m)

Map本身作为key,结果与他自己相同

let m = new Map([["baz",3]])
let m1 = new Map(m)
console,log(m1) // baz:3
let map = new Map()
map.set("foo",true)
map.set("bar",false)

console.log(map.size) //2
console.log(map.get("foo")) //true
console.log(map.has("foo")) //true,这个key存在

console.log(map.delete("foo")) //  true  删除也会返回true或者false

map.clear()
console.log(map.size) // 0
let m = new Map([["foo","no"],["bar","yes"]])

for(let key of m.keys()){
    console.log(key) //foo bar
}
for(let value of m.values()){
    console.log(value) //no yes
}
for(let item of m.entries()){
    console.log(item) //["foo","no"],["bar","yes"]
}
//解构赋值
for(let [key,value] of m.entries()){
    console.log(key,value) //foo no bar yes
}

//map->array
console.log([...m.keys()]) //返回所有的key
console.log([...m.values()]) //["no","yes"]
console.log([...m.entries()]) // [["foo","no"],["bar","yes"]]

console.log(...m) //等同于[...m.entries()]

class类和对象

1、Class基本语法

let p = new Object() //无法判断类型

//自定义构造函数
function Person(name,age){
    this.name = name
    this.age = age
    this.printInfo = function(){
        console.log(this.naem)
        console.log(this.age)
    }
}
let p = new Person("tom",18)
p.printInfo() // tom 18

console.log(p) 

可以发现 name,age,printInfo()全部挂载在了p上面,当对象变多时,每个对象上都有printInfo(),非常占内存

JS中通过原型链解决这个问题(具体看《前端基础知识》)

  • 将属性定义在构造函数中,将函数定义在原型上,这样可以减少内存

ES5解决方法如下

function Person(name,age){
    this.name = name
    this.age = age
}
Person.prototype.printInfo = function(){
        console.log(this.naem)
        console.log(this.age)
}
let p = new Person("tom",18)
p.printInfo() // tom 18

在ES6中提供了类,可以看作是一个语法糖

class Person {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    //在class内定义的方法,其实都是定义在其原型对象上的
    printInfo(){
        console.log(this.name)
        console.log(this.age)
    }
}
let p = new Person("tom",18)
console.log(p) //可以看到printInfo也是在_proto_里面的,与上面的原型方法相同

console.log(typeof Person ) //function
console.log(Person.prototype.constructor == Person) // true

2、constructor构造函数

  • 通过new 构造对象时,会自动调用构造函数

  • 通常在构造函数中,进行一些数据的初始化

  • 在一个类中必须要有一个构造函数,如果没有被显式定义,默认会添加一个空的constructo

class Person{
    
}
let p1 = new Person()
  • this指向当前创建的实例对象
class Person {
    constructor(){
        console.log(this) //Person() 
    }
}
let p = new Person()
  • 与Java不同,js中在一个class中只能有一个构造函数 以下代码会报错
class Person {
    constructor(){
        console.log(this) //Person() 
    }
        constructor(name,age){
        this.name = name
        this.age = age
    }
}
let p = new Person()
let p2 = new Person("tom",13) //会报错,因为有两个构造函数
class Person {
    constructor(){
        console.log(this) //Person() 
    }
}
let p = new Person()
let p2 = new Person("tom",13) // 不会报错,仍然会调用constructor,只不过参数没有被接受
class Person {
    constructor(...params){
        console.log(this) //Person() 
        console.log(params)
    }
}
let p = new Person() //params为空
let p2 = new Person("tom",13) // params为 tom,13

3、属性表达式
类的属性也可以用表达式方法

let methodName = "print"
class Person {
    [methosName]() {
        console.log(methosName + "is called")
    }
}
let p = new Person()
console.log(p)
p[methosName]() // 因为是变量,所以要用[]来调用

4、Class表达式

let Myclass = class Me{
    getClassName(){
        return Me.name
    }
}

let myClass = new Myclass()
console.log(myClass.getClassName()) //Me
console.log(Me.getClassName()) //报错

立即执行class,在定义class的同时创建出实例对象

//一般在只该类使用一次的情况下使用
let person = new class{
    constructor(name,age){
        this.name = name
        this.age = age
    }
    printInfo(){
        console.log(this.name)
        console.log(this.age)
    }
}("tom",18)

5、static静态方法

在类中定义的方法前加上static关键字,该方法则为静态方法

class Foo {
    //实例方法,定义在实例的原型对象上
    methodA() {
        console.log("methodA is called")
    }
    //静态方法
    static methodB(){
        console.log("methodB is called ")
    }
}
let foo = new Foo()
console.log(foo) //methodA在实例原型上,但methodB不在

//静态方法直接通过类访问,和实例对象无关
Foo.methosB()
class Foo 
    //实例方法,定义在实例的原型对象上
    methodA() {
        console.log("methodA is called")
    }
    static methodB(){
        console.log("methodB is called ")
    }
    static bar (){
        console.log(this) //this->类,而不是实例
        this.baz()
    }
    static baz(){
        console.log("baz is called")
    }
}
let foo = new Foo()
foo.methodA()
foo.methodB()
Foo.bar() // baz is called

6、class的继承

继承实际上就是将父类放到了子类的原型链中

class Person{
    constructor(name,age){
        this.name = name
        this.age = age
    }
    printInfo(){
        console.log(this.name)
        console.log(this.age)
    }
}

class Student extends Person{
    constructor(name,age,major){
        super(name,age) //里面的this指向子类
        this.major = major
    }
    study(){
        console.log("好好学习")
    }
    printInfo(){
        console.log(this.name)
        console.log(this.age)
        console.log(this.major)
    }
}
let s1 = new Student("tom",18,"IT")
console.log(s1)
s1.printInfo() // tom  18 IT ,调用子类的函数
s1.study() //调用子类的函数

promise

blog.csdn.net/weixin_4181…

async和await

segmentfault.com/a/119000000…

补充

1、setTimeout(function(){},0)

www.cnblogs.com/xyy2019/p/1…
setTimeout是异步的注册事件,它有两个参数,第一个参数是函数,第二参数是时间值。调用setTimeout时,把函数参数,放到事件队列中。等主程序运行完,再调用。 就像我们给按钮绑定事件一样。

2、正则表达式与test函数

www.runoob.com/regexp/rege…
通过正则表达式与test函数可以判断字符是否为字母或数字

正则表达式中的^的意思

只要是”^”这个字符是在中括号“[]”中被使用的话就是表示字符类的否定,如果不是的话就是表示限定开头,即/^A/会匹配"An e"中的A,但是不会匹配"ab A"中的A,所以如果使用正则表达式判断一个字符串中是否包含字母时,不能用^,而应该直接写为/[A-Za-z]/
blog.csdn.net/sufubo/arti…