js知识梳理

497 阅读16分钟

typeof 与 instanceof

typeof:

可以区别: 数值,字符串,布尔值,undefined,function

不可以区别: null,对象,数组 打印出来都是Object

instanceof:

专门用来判断对象数据的类型:Object,Array,Fuction

还可以用来判断undefined,null

null和undefined的区别

undefined 代表未定义 没有赋值

null 代表赋值了,只是赋的值为null

console.log(null == undefined) 返回true

原因: null的布尔值为0 undefined的布尔值也为0

this指向

<script>
    console.log(this) //this 指向window
    function fn(){
        console.log(this)
    }
    fn()   //这边this指向window
    //相当于执行 window.fn()
    new fn() //this 指向实例化对象
    
    var obj1 = {
        fun:function(){
            console.log(this)
        }
    }
    
    obj1.fun() //this指向obj1
    
    var obj = {name:'jenny'}
    fn.call(obj) //强行将this指向obj
    fn.apply(obj) //强行将this指向obj
</script>

this的理解:

  1. 关键字,不能将其定义成变量

  2. this本身是一个内置的变量,该变量用于指向一个对象

  3. this分为两种:

    全局this 指向window

    局部(函数)this 指向调用的对象,构造函数this 指向构造函数的实例对象

  4. 函数的this不是在定义的时候决定的,实在调用的时候决定的

回掉函数(DOM事件函数,定时器函数)

定义:1.自己定义 2.不是自己手动调用 3.最终还是执行了(满足一定条件之后执行)

立即执行函数(IIFE)

特点:1.只执行一次,2.在代码执行到函数位置执行3.内部的数据是私有的4.一般为匿名函数,当需要调用匿名函数可以用argumens.callee 去调用5.()代表执行符号,只有表达式才能被执行符号执行

(function ()()) //w3c推荐
或者
(function ())()

原型对象与原型链

  1. 每个函数都有一个属性prototype(显式原型对象)
  2. 每个实例化对象上面都有一个属性__poto__,该属性指向当前实例化对象的原型对象(隐式原型对象)
  3. 构造函数的显式原型对象 === 当前构造函数实例化对象的隐式原型对象

为什么要设计出原型??

function Person(name,age){
    this.name = name,
    this.age = age,
    this.eat = function (){
        console.log("i like eat meet")
    }
}

var person1 = new Person('blossom',22)
var person2 = new Person('smith',22)

当需要定义公共的方法时,可以将该方法定义在原型对象上供所有的实例对象使用,从而减小内存开销

显式原型 vs 隐式原型

显示原型可被程序员直接操作,隐式原型不能被程序员直接操作(ES6允许操作隐式原型)

值传递和引用传递

值传递:通常针对基本数据类型,传值的内容就是值本身

引用传递:通常针对的是引用数据类型(对象),传递的是数据在内存中的地址值

执行上下文

定义: js引擎在js代码正式执行之前会先创建执行环境,在执行环境中做预处理工作

工作内容:

1.创建空对象 2.该空对象用来收集变量,函数,函数的参数(找var 和 function) 3.创建作用域链 4.确认this的指向

变量提升(预处理)

  1. 找var function 关键字
  2. 找到var之后,将var 之后的变量提前声明 但是不进行复制,相当于var a;
  3. 找到function之后定义该函数

==注意:当有同名的变量和函数时,预处理之后 typeof得到的一定是 function==

函数有个默认的属性 name === 函数名

执行上下文栈

作用:用来保存 执行上下文对象

压栈特点:先进后出,后进先出

作用域

  1. 定义:代码执行区域
  2. 作用域什么时候产生:代码定义的时候产生
  3. 作用域分类:全局作用域,局部作用域(函数作用域)
  4. 作用域什么时候销毁: 局部:函数执行完成 全局:关闭浏览器
  5. 当初为什么要设计作用域:防止变量污染,隔离变量
  6. 作用域链:查找(使用)变量时,会先在当前作用域找,如果当前作用域没有,向外层作用域去找,直到找到全局作用域,如果还是没有,会报错,xxx is not defined
  7. 在作用域中查找变量去哪找:取当前作用域下的执行上下文对象中去找

==注意:obj.test() 对象是在作用域上找,方法是在原型链上找==

作义域 vs 执行上下文

作用域是在代码定义的时候产生(只有一个)

执行上下文是在代码执行前产生(可以有多个)

闭包

(在全局中,定义一个函数,未调用,全局执行上下文中还是会有这个函数,在局部作用域中,定义一个函数,未调用,局部执行上下文中,不会有这个函数)

定义:

  1. 闭包是一个闭合容器
  2. 我们可以认为闭包就是一个对象:{key:value}
    function fun(){
        var num = 1,
        return function fun2(){
            console.log(num)
        }
    }
    var fun2  = fun()

闭包的形成条件:

  1. 函数嵌套
  2. 内部函数引用外部函数变量
  3. 外部函数一定被调用

作用:

  1. 延长外部函数的局部变量的生命周期
  2. 从外部访问函数内部的局部变量

闭包的缺点:

  1. 占内存
  2. 不及时清除闭包容易导致内存溢出

使用闭包时,怎么避免闭包带来的缺点

  1. 能不用就不用
  2. 及时清除闭包

闭包的使用场景

  1. 解决循环遍历加监听的问题
  2. 将内部的函数返回出来
  3. 将函数作为实参传递给另一个函数

js是单线程还是多线程?

js引擎是单线程,但是它可以通过轮转时间片实现多线程操作

垃圾回收机制(循环机制)

计数清零: 看内存的地址身上有几个指针指向,当一块内存地址身上指针个数为零,说明这块地址马上要被回收

标记清除: 进入代码执行环境后,检测到需要使用的变量之后就在其身上加一个进场标记,,在代码执行完成的时候,就会在之前加标记的变量身上再添加一个出场标记,当出场标记和进场标记同时存在时,该变量就会被销毁

同步 vs 异步

  1. 同步:同步会阻塞后续代码执行,同步没有回调
  2. 异步:异步时非阻塞的,异步一定有对应的回掉函数

对象的创建方式

  1. Object构造函数方式 缺点:语句太多,流程太啰嗦
    var person = new Object()
    person.name = 'kate'
    person.age = '13'
  1. 对象的字面量创建方式,优点:书写简单,方直观 缺点:有太多重复的代码
    var person1 = {
        name:'kate',
        age:'18'
    }
    
    var person2 = {
        name:'bob',
        age:'15'
    }
  1. 工厂模式:优点:避免重复代码,可以批量生产对象
  • 缺点:不能明确区分是哪一类
    function Person(name,age){
        return {
            name:name,
            age:age
        }
    }
    
    var person1 = Person('kate','12')
    var person2 = Person('bob','13')
  1. 自定义构造函数模式
  • 优点:可以生产多个实例对象
  • 缺点:如果定义的方法直接给实例对象,会消耗大量内存
    function Person(name.age){
        this.name = name,
        this.age = age,
        this.text = function (){
            console.log('hhh')
        }
    }
    
    function Student(name,age,sex){
        this.name = name,
        this.age = age
        this.sex = sex
    }
    
    var man = new Person('kate','18')
    var bob = new Student('bob','13')

继承

  1. 原型继承:子类的原型对象 == 父类的构造函数
    Child.prototype = new Parent()

==注意点:上述操作,会导致子类原型上的构造器属性缺失,所以需要手动的添加构造器属性==

    Child.prototype.constructor = Child
  1. 借用构造函数继承(不是真正意义上的继承)

==注意点:父类构造器上的this指向问题==

解决:通过call/apply 方法强制修改this指向

function Parent(name,age){
    this.name = name
    this.age = age
}

function Student(name,age,sex){
    // this.name  = name
    // this.age = age
    //可以改变构造函数的this指向,从而减少代码量
    Parent.call(this,name,age)
    this.sex = sex
}

事件循环机制

  1. js是单线程
  2. 所有的js代码都会在主线程执行
  3. 同步任务加载及执行
  4. 异步任务不会立即执行,而是会交给对应的管理模块
  5. 管理模块一直被监视异步任务是否满足条件,如果满足条件会将对应的回调放入callback queue(回调队列)
  6. 主线程上的同步任务执行完一i后会通过event loop(事件轮询机制)询问callback queue,查看事件是否有可执行的回调函数,如果有将回调勾到主线程上执行,如果没有,待会再来询问

ES6语法

严格模式 (use strict)

语法和行为的改变

  1. 必须用var声明变量,不然会报错
  2. 禁止自定义函数的this指向window
    function fn() {
        console.log(this)
    }
    fn()
    //this为undefined
  1. 创建eval作用域
    var a = 123
    eval('var a = 1; alert(a)')
    console.log(a)
    //alert的值为1
    //console的值为123
    //使用严格模式 不会将变量污染全局
  1. 对象不能又重名属性

Object 扩展

  1. Object.create(prototype, [descriptors])
  • 作用:可以指定对象为原型创建新的对象

  • 为新的对象指定新的属性,并对属性进行描述

  • value :指定值

  • writable :标识当前属性是否可以修改,默认值为false

  • configurable :标识当前属性是否可以删除,默认值为false

  • enumerable :标识当前属性是否能用for in 枚举,默认值为false

    var obj = {
        name: 'kate',
        age: 14,
        setName: function(){
            this.name  = 'bob'
        }
    }
    
    var obj2 = {}
    obj2 = Object.create(obj,{
        sex:{   //配置对象
            value:'男', //给obj2上新增属性
            writable: true,  //当前属性可修改
            configurable: true,  //当前属性可删除
            enumerable: true //当前属性可被枚举
        }
    })
    
    obj2.sex = '女' //修改属性值
    delete obj2.sex //删除属性
  1. Object.defineProperties(Object,descriptors)
  • 作用:为指定对象定义扩展多个属性
  • get:用来获取当前属性值的回调函数
  • set:用来修改当前属性值的触发回调函数,并且实参即为修改后的值
  • 存储器选择:setter,getter一个用来存值一个用来取值
    var sex = '男'
    var obj = {
        name: 'kate',
        age: 18
    }
    
    Object.defineProperties(obj,{
        sex: {
            get: function (){ //设置值,获取对象属性值时会自动被调用
                return sex
            },
            set: function (value){   //当修改扩展的属性值时,该方法就自动被调用
                sex = value
            }
        }
    })
    
    obj.sex('女')

call() | apply() | bind() 对比

    var obj = {
        name:'kate',
        age:12
    }
    
    function fn(msg){
        console.log(this)
        console.log(msg)
    }
    
    fn()    //函数自调用时this指向window
    
    fn.call(obj, 'this is call passing in arguments')
    fn.apply(obj, ['this is apply passing in arguments'])
    fn.bind(obj, 'this is bind passing in arguments')()

call与bind传入参数得方式一样

call与apply绑定完this会立即调用函数,bind只会绑定this但是不会立即执行

bind的运用场景:当要为回调函数绑定this时,只能用bind

setTimeout(function (){
    console.log(this)
}.bind(obj),2000)

关键字

let

  1. 在块级作用域中有效
    
    <button>第一个按钮</button>
    <button>第二个按钮</button>
    <button>第三个按钮</button>
    
    var btns = document.getElementByTagName('button')
    
    for(var i = 0; i < btns.length; i++){
        var btn = btns[i]
        (function (i){
            btn.onclick = function(){
                alert(i)
            }
        })(i)
    }
    //之前要实现点击按钮跳出相应按钮的下标,需要通过闭包来实现
    
    for(let i = 0; i < btns.length; i++){
        let btn = btns[i]
        btn.onclick = function (){
            alert(i)
        }
    }
    //通过let会产生一个自己的块级作用域
    //例如上例,会产生三个不同的块级作用域,每个作用域的i都不相同
    //因此在点击button按钮时,会获得当前块级作用域中的i值
  1. 不能重复声明,重复声明之后就会报错

  2. 不会预处理,不存在变量提升

const

  1. 作用:定义一个常量
    const NUMBER = 123
  1. 特点:
  • 不能被修改
  • 在块级作用域中有效
  • 不能重复声明
  • 不会预处理,不存在变量提升

解构赋值

对象的解构赋值

let obj = {
    name:'kate',
    age:14
}

//原来获取obj中的name
let yname = obj.name
let yage = obj.age
cobsole.log(yname,yage) 
//es6 通过对象中的key值 来获取相对应的值
let {name,age} = obj
console.log(name,age)

数组的结构赋值

let arr = [1,3,4,2,5]
//es6 通过获取数字的index 来获取相对应的值
let [a,b,c] = arr
console.log(a,b,c)
//打印 1 3 4

function fun({name,age}){
    console.log(name,age)
}
fun(obj)

简化对象写法

let name = 'kate',
let age = 14,
let obj = {
    name : name,
    age : age
    setName : function (name){
        this.name = name
    }
}

//es6 简化之后

let obj = {
    name,
    age,
    setName(name){
        this.name = name
    }
}

三点运算符(...)

function fun(name,age){
    console.log(arguments)
    //打印出关于参数的伪数组(不具备真数组的所有方法)
}
fun('kate',12)

//es6
function fn (...value){
    console.log(value)
    //打印出关于参数的真数组(具备数组的所有方法)
}
fn('kate',12)

function fn2(name,...value){
    console.log(value)
    //打印 [12,'男']
    //name会对应kate,其余的参数都将会被value搜集
    //...value 一定要放在参数最后一位,否则就会报错
}
fn2('kate',12,'男')

扩展运算符

let arr = [1,4,5,6]
let arr2 = [2,3]

arr = [1,...arr2,4,5,6]
console.log(arr)
//打印[1,2,3,4,5,6]
//...arr2 可以循环遍历数组里的值 ...arr2 => 2 3
//...obj 不可以循环遍历对象

形参默认值

//es6
funtion Point(x = 0,y = 0){
    this.x = x,
    this.y = y
}

let location = new Point()
console.log(location)
//es5中创建实例对象,如果不传值打印出来的属性值为undefined
//es6中允许形参赋值,如果创建实例对象没有传入值,那么默认打印出的是定义时赋予的值

Promise对象

  1. 作用:解决异步回掉
  2. 语法:
    • 1.创建promise实例对象 ---> 初始化promise对象的状态为pending
    • 2.执行异步任务:开启定时器任务/执行ajax请求
    • 3.根据异步任务执行的结果去修改promise实例对象的状态:resolve()成功,reject()失败
    • 4.promise实例对象默认有个then()方法,该方法需要两个参数,这两个参数是回调函数
    • 5.当promise对象的状态变为成功/失败的时候自动调用then方法中的回调函数

Symbol.iterator

  1. 概念:Symbol.iterator是一个接口机制(方法),为各种不同的数据结构提供统一的访问机制
  2. 作用:
    1. 为各种数据结构,提供一个统一的、便捷的访问接口
    2. 使得数据结构的成员能够按照某种次序排列
    3. ES6创造了一种新的遍历命令 for ... of循环,Iterator接口主要供 for ... of消费
  3. 工作原理
    1. 创建一个指针对象(遍历器对象),指向数据结构的起始位置
    2. 第一次调用next()方法,指针自动指向数据结构的第一个成员
    3. 每次调用next()方法返回的是一个包含value 和 done 的对象,{value:当前成员值,done:布尔值}
      • value表示当前成员的值,done对应的布尔值表示当前的数据结构是否遍历结束
      • 当遍历结束的时候返回的value值是undefined,done值为false
  4. 原生具备iterator接口的数据(可用for of 遍历)
    1. Array
    2. arguments
    3. set容器
    4. map容器
    5. String
 function iteratorUtil(){
        console.log(this)
        let that = this 
        let index = 0
        if(that instanceof Array){
            return {
                next(){
                    return index < that.length ? {value:that[index++],done:false}:{value:that[index++],done:true}
                }
            }
        }else{
            let keys = Object.keys(obj)
            let length = keys.length
            return {
                next(){
                    return index < length ? {value:that[keys[index++]],done:false}:{value:that[keys[index++]],done:true}
                }
            }
        }
        
    }

    let arr = [1,2,2,3,4,5]
    let obj = {
        name:'kete',
        age:13,
        sex:'男'
    }
    console.log(Object.keys(obj))
    // let iteratorObj = iteratorUtil(arr)
    Array.prototype[Symbol.iterator] = iteratorUtil
    Object.prototype[Symbol.iterator] = iteratorUtil

    for(let i of arr){
        console.log(i)
    }

    for(let i of obj){
        console.log(i)
    }

async函数

  1. 语法
    • 定义:async + function (){}
    • 函数里搭配使用 await 异步操作
    • await 后跟异步任务,当执行await会阻塞,当异步任务成功后续操作
  2. async + promise对象
    • 异步任务使用promise包装
    • await的异步任务通常返回一个promise对象
    • 通过修改promise实例的状态来通知await以及给await设置返回值(传递的数据)
  3. 特点
    • 语义化明确
    • 真正意义上解决异步回掉的问题

class

  1. 通过class定义类/实现类的继承
  2. 在类中通过constructor定义构造方法
  3. 通过new来创建类的实例
  4. 通过 extend 来实现类的继承
  5. 通过super()调用父类的构造方法
<script>
        //类
        class Person{
            //类的构造函数
            constructor(name,age){
                this.name = name
                this.age = age
            }

            //这个方法相当于构造函数Person2.prototype.showName = function () {}创建出来的方法
            // showName(),供实例对象使用
            showName(){
                console.log(this.name,this.age)
            }

            //这个是Person类自身的方法,实例对象不能使用
            static test(){
                console.log('hahahahhah')
            }
        }

        //构造函数Person2中的原型中也有一个constructor,这个constructor指向Person2自己
        function Person2(name,age) {
            this.name = name
            this.age = age
        }


        Person2.prototype.showName = function (){
            
        }

        Person2.test = function (){
            console.log('我是构造函数Person2的自身方法,该方法不能给实例对象使用')
        }

        class ChildPerson extends Person{
            constructor(name,age,sex){
                //super()调用父类的构造方法
                //相当于写了 this.name = name  this.age = age
                super(name,age)
                this.sex = sex
            }

            // 重写父类Person的showName()方法
            showName(){
                console.log(this.name,this.age,this.sex)
            }

        }

        let person = new Person('Bob',23)
        person.showName()

        let childPerson = new ChildPerson('jenny', 2, '女')
        childPerson.showName()
    </script>

浅度拷贝 和 深度拷贝

  1. 浅度拷贝:拷贝一个结构,当里面的数据类型为基本数据类型时,会原样拷贝,当遇到对象和数组时,拷贝的时对象的引用地址,所以新对象对象/数组,或者被拷贝的对象/数组,会导致数据发生共同的改变

  2. 深度拷贝:拷贝一个结构,当结构内部存在对象或者数组时,不仅会把对象和数组的引用地址拷贝下来,还会拷贝对象和数组的值,所以当改变新对象/数组或者被拷贝对象/数组时不会导致对方数据改变

  3. 深度拷贝的核心思想:克隆的数据不能有引用数据类型,如果有,继续遍历挖掘,直到每次拷贝的数据都是基本数据类型

//实现深度克隆
<script>
    //判断是数组还是对象
    function getType(target){
        // console.log(Object.prototype.toString.call(target).slice(8,-1))
        return Object.prototype.toString.call(target).slice(8,-1)
    }

    function cloneUtil(target){
        let result;

        // 判断数据类型
        if(getType(target) === 'Array'){
            result = []
        } else if(getType(target) === 'Object'){
            result = {}
        } else {
            return result
        }

        for (let i in target) {
            let item = target[i]
            
            //当遍历的数据中仍然有数组/对象时,重新按照之前的步骤 进行拷贝,直到所有拷贝数据的类型都是基本数据类型
            if(getType(item) === 'Object' || getType(item) === 'Array'){
                let cloneitem = cloneUtil(item)
                result[i] = cloneitem
            } else{
                result[i] = item
            }
        }

        return result
    }

    let obj = {
        name:'Bob',
        age:22,
        sex:{
            option1:'男',
            option2:'女'
        }
    }

    let cloneObj = cloneUtil(obj)
    cloneObj.sex.option1 = 'xxx'
    console.log(obj)
    console.log(cloneObj)
    
    
</script>

正则表达式

/./g       .  表示通配符
/3\.14/g   \  表示转义符  把通配符. 转化成一个普通的点
/\w/g      \w 表示匹配阿拉伯数字,英文字母,和一个下划线(0~9 a~z A~Z)
/\W/g      \W 表示匹配被\w排除在外的字符(\w的补集)
/\d/g      \d 表示仅仅匹配阿拉伯数字
/\D/g      \D 表示匹配除阿拉伯数字以外的字符 也匹配空格
/\s/g      \s 表示匹配空格、制表符(Tab)、断行等
/\S/g      \S 表示\s的补集
/[a-z]/g   [] 表示选择范围,匹配括号中的任意一个字符
/yo+/g     +  表示重复一次或者多次,例如oooooo可以写成o+,
/[yY][oO]+/g 表示Yoooo,yOOOOOO,YOOOOOO,yooooo 满足四种方式
/[yY][oO]+[!~\.]*/g 表示[!~\.]重复零个或多个
/p?/g      ?  表示出现零次或者一次
/yo{1,2}/g {a,b}表示重复a到b次

希望本文可以给你带了一些帮助,文中如有错误,欢迎指出

持续更新中...