js回顾

一.一些常见小点点

  1. 判断的三种方式:
    typeof:不能区别null与object;object和array
    instanceof:判断对象的具体类型
    ===:null/undefined

    了解== 和===的区别:

    NaN与任何数运算得到的结果都是NaN
  • 在做==比较时。不同类型的数据会先转换成一致后在做比较,===中如果类型不一致就直接返回false,一致的才会比较
  1. setTimeout(fn,100);100毫秒是如何权衡的

setTimeout()函数只是将事件插入了任务列表,必须等到当前代码执行完,主线程才会去执行它指定的回调函数,有可能要等很久,所以没有办法保证回调函数一定会在setTimeout指定的时间内执行,100毫秒是插入队列的时间+等待的时间

  1. 回调函数
    你定义的,你没有调,但最终执行了
    常见的回调函数:
    ①dom事件的回调函数–》发生事件的dom元素
    ②定时器回调函数—-》window

  2. ES6新特性
    重要特性:

  • 块级作用域

  • rest参数
    用于获取函数的多余参数,这样就不需要使用arguments对象了,

  • promise
    一种异步编程的解决方案,比传统的解决方案回调函数和事件更合理强大

  • 模块化
    其模块功能主要有两个命令构成,export和import,export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能

  • Symbol
    独一无二
    不能进行运算
    不能用for…in循环遍历,但是可以用Reflect.ownKeys来获取对象所有键名
    注意:创建对象的时候不用new,let s = Symbol()

    js数据类型
    USONB
    you are so niubility
    // u undefined
    //s string
    //o object
    //n null number
    //b boolean

    • ES11引入大整型

      BigInt 标识为结尾有n

    • 常用于大整数的运算

      • 不能与非BigInt类型的数值相运算
      1
      2
      3
      4
      5
      6
      7
      8
      let n = 521n
      console.log(n,typeof(n))
      //521n bigint

      //将小的转化为大整型
      let small = 123
      console.log(BigInt(small))
      //123n
  • 迭代器
    工作原理:

    - 创建一个指针对象,指向当前数据结构的起始位置
    - 第一次调用对象的next方法,指针自动指向数据结构的第一个成员
    - 接下来不断调用next方法,指针一直后移直到指向最后一个成员
    - 每调用next方法返回一个包含value和done属性的对象

    :需要自定义遍历数据的时候,要想到迭代器

    1
    2
    3
    4
    5
    6
    7
    //声明一个数组
    const xiyou = ['唐僧','孙悟空','猪八戒','沙僧']

    //使用for...of遍历数组
    for(let v of xiyou){
    console.log(v)
    }

    手写迭代器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    const banji = {
    name: "中级一班",
    stus: [
    'xiaoming',
    'xiaohong',
    'xiaoxiao'
    ],
    [Symbol.iterator](){
    //定义索引
    let index = 0
    let _this = this
    return {
    next: function(){
    if(index < _this.stus.length){
    const result = {value: _this.stus[index],done:false}
    index++
    return result
    }else{
    const result = {valueundefined,done:true}
    return result
    }

    }
    }
    }
    }
    for (let v of banji){
    console.log(v)
    }
  • 生成器
    ES6提供的一种异步编程(之前用纯回调函数)
    函数名前要写一个星号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
         //yeild语句可以把函数分块,比如3个可以分为4块
            function * laohu({
                console.log(111)
                yield "一只没有耳朵"
                console.log(222)
                yield "一只没有尾巴"
                console.log(333)
                yield "真奇怪"
                console.log(444)
            }
            //每次一个next执行一块
            let iterator = laohu()
            iterator.next() //111
            iterator.next() //222
            iterator.next() //333
            iterator.next() //444

    生成器传参

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    //yeild语句可以把函数分块,比如3个可以分为4块
             function * laohu(arg{
                console.log(arg)   //A
                let one = yield "一只没有耳朵"
                console.log(one)    //ONE
                let two = yield "一只没有尾巴"
                console.log(two)    //TWO
                let three = yield "真奇怪"
                console.log(three)  //THREE
            }
            //每次一个next执行一块
            //next的参数将作为yeild语句输出的返回值
    //第二次调用next,它的参数将作为第一个yeild的返回结果
            let iterator = laohu('A')
            console.log(iterator.next()) //{value:'一只没有耳朵',done:false}
            console.log(iterator.next('ONE')) //{value:'一只没有尾巴',done:false}
            console.log(iterator.next('TWO') )  //{value:'真奇怪',done:false}
            console.log(iterator.next('THREE') )    ////{value:undefined,done:true}
  • 新的数据结构:Set,Map

    Set

    ES6提供的新的数据结构,类似集合,可以使用iterator接口和扩展运算符,for..of
    自动去重

    • size 返回集合的元素个数
    • add 增加一个新元素,返回当前集合
    • delete 删除元素,返回boolean
    • has 检查是否含有,返回布尔
    • clear 清空,返回undefined

    set+扩展运算符操作数组,太强了

    Map

    ES6提供类似于对象,是键值对的集合
    但是“键”的范围不限于字符串,各种类型的值都可以当作键
    Map也可以iterator接口,用扩展运算符和for…of

    - size 返回Map的元素个数
    - set 增加一个新元素,返回当前的Map  
    - get 返回键名对象的键值
    - has 检测Map中是否包含某个元素,返回布尔
    - clear 清空集合,返回undefined
  • 数值扩展与对象方法扩展

    • Number.EPSILON 无限接近于0

    • Number.isFinite 检测一个数值是否为有限数

    • Number.isNAN 检测一个数值是否为NAN

    • Number.parseInt , Number.parseFloat字符串转整数

    • Number.isInteger 判断一个数是否为整数

    • Math.trunc 将数字的小数部分抹掉

    • Math.sign 判断一个数是正(返回1),0(返回0),负数(返回-1)

      对象方法扩展

    • Object.is 判断两个值是否完全相等

      与===区别:

      1
      2
      3
      4
      5
      6
      7

      console.log(NaN === NaN)    //false
      console.log(NaN === undefined)  //false
      console.log(+0 === -0)  //true
      console.log(Object.is(NaN,NaN)) //true
      console.log(Object.is(NaN,undefined))   //false
      console.log(Object.is(+0,-0))   //false
    • Object.assign(对象1,对象2) 合并,对于相同属性2会覆盖1

    • Object.setPrototypeOf 设置原型对象,Object.getPrototypeOf 获取原型对象

    • Object.keys()获取对象所有的键

    • Object.values()获取对象所有的值

    • str.trimStart() //清除左边的空格

    • str.trimEnd //清除右边的空格

    • arr.flat() //数组拍平,参数为数组的层数

  1. ES6 - 箭头函数
    相比普通函数更简洁的语法
    解构赋值
    没有this
    不能使用new
    不绑定arguments,用rest参数…解决
    使用call()和apply()调用捕获其所在上下文的 this 值,作为自己的 this 值
    箭头函数没有原型属性
    不能简单返回对象字面量
    箭头函数不能当做Generator函数,不能使用yield关键字
    箭头函数不能换行

  2. JS的垃圾回收机制
    Js具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的执行。

标记清除

工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。

工作流程:

  1. 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
  2. 去掉环境中的变量以及被环境中的变量引用的变量的标记
  3. 再被加上标记的会被视为准备删除的变量。
  4. 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。

引用计数

工作原理:跟踪记录每个值被引用的次数。

工作流程:

  1. 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是1。
  2. 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1.
  3. 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减1.
  4. 当引用次数变成0时,说明没办法访问这个值了。
  5. 当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。

但是循环引用的时候就会释放不掉内存。循环引用就是对象A中包含另一个指向对象B的指针,B中也包含一个指向A的引用。因为IE中的BOM、DOM的实现使用了COM,而COM对象使用的垃圾收集机制是引用计数策略。所以会存在循环引用的问题。
解决:手工断开js对象和DOM之间的链接。赋值为null。IE9把DOM和BOM转换成真正的JS对象了,所以避免了这个问题。

  1. 防抖和节流

节流:
在一段时间内不管触发了多少次都只认为触发了一次,等计时结束进行响应(假设设置的时间为2000ms,再触发了事件的2000ms之内,你在多少触发该事件,都不会有任何作用,它只为第一个事件等待2000ms。时间一到,它就会执行了。 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

//节流
    //时间戳方式
    function throttle(fn,delay){
        let pre = Date.now();
        return function({
            let context = this;
            let args = arguments;
            let now = Date.now();
            if(now - pre > delay){
                fn.apply(context,args);
                pre = Date.now();
            }
        }
    }
    //定时器方式  
    function throttle(fn,delay){
        let timer = null;
        return function(){
            let context = this;
            let args = arguments;
            if(!timer){
                timer = setTimeout(function({
                    fn.apply(context,args);
                    timer = null;
                },delay)
            }
        }
    }

防抖:
在某段时间内,不管你触发了多少次事件,都只认最后一次(假设设置时间为2000ms,如果触发事件的2000ms之内,你再次触发该事件,就会给新的事件添加新的计时器,之前的事件统统作废。只执行最后一次触发的事件。)

1
2
3
4
5
6
7
8
9
10
11
12
13
//防抖  
    function fangdou(fn,delay){
        let timer = null;
        return function(){
            let context = this;
            let args = arguments;
            clearTimeout(timer);
            timer = setTimeout(function(){
                fn.apply(context,args);
            },delay)
        }
        
    }
  1. 深拷贝 与 浅拷贝

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

  • 浅拷贝:arr.slice() arr.concat() Object.assign({},obj)
  • 深拷贝:JSON.parse(JSON.stringify()) 函数库lodash的cloneDeep方法
    1
    2
    3
    4
    var _ = require('lodash');
    var obj1 = {
    }
    var obj2 = _.cloneDeep(obj1);

二.犄角旮旯

1.作用域

函数可以在定义之前调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun();  //我是一个fun函数
fun2(); //报错

//函数声明,会被提前创建
function fun(){
console.log("我是一个fun函数");
}

//函数表达式不会被提前创建
var fun2 = function(){
console.log("我是fun2函数");
}

//示例 1
alert(a); //undefined
var a = 123;

//示例 2
alert(a); //报错
a = 123;

函数可以访问全局,但是全局不能访问函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//  第一种  
var c = 33;
function fun1(){
console.log("c = "+c);
}
fun1() //33

// 第二种
var c = 33;
function fun2(){
console.log("c = "+c);
var c = 10;
}
fun2() //undefined

//第三种
var c = 33;
function fun3(){
console.log("c = "+c);
c = 10;
}
fun3() //33
  • 在函数中,不使用var声明的变量都会成为全局变量
  • 定义形参就相当于在函数作用域中声明了变量

    例题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    //1.
    var a = 123;
    function fun(){
    alert(a);
    a = 456; //没有加var,所以不会提前声明,先找全局的123;之后的给a赋值456,默认全局,所以最后输出456
    }
    fun(); //123
    alert(a); //456

    //2.
    var a = 123;
        function fun(a){
            //定义形参就相当于在函数作用域中声明了变量,var a
            alert(a);   
            a = 456; //因为有形参,已经var过了,所以不会对全局产生影响
        }
        fun(); //undefined
        alert(a); //123

    //3.
    var a = 123;
        function fun(a){
            alert(a);   
            a = 456; //相当于对函数里的a重复赋值
        }
        fun(123); //123
        alert(a); //123

    //变量提升与函数提升
    console.log(b)  //undefined 变量提升
        fn2()   //函数提升 fn2()
        var b = 3
        function fn2({
            console.log('fn2()')
        }

    //执行上下文 1
        var a = 10
        var bar = function(x{
            var b = 5
            foo(x + b)
        }
        var foo = function(y{
            var c = 5
            console.log(a+c+y)
        }
        bar(10//30
        //执行上下文 2  
        console.log('gb:'+i)
        var i = 1
        foo(1)
        function foo(i{
            if(i == 4) {
                return
            }
            console.log('fb:'+i)
            foo(i+1)
            console.log('fe:'+i)
        }
        console.log('ge:'+i)
        //gb: undefined
        //fb:1 fb:2 fb:3
        //fe:3 fe:2 fe:1
        //ge:1

var与let

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function varTest() {
var x = 31;
if (true) {
var x = 71; // same variable!
console.log(x); // 71
}
console.log(x); // 71
}

function letTest() {
let x = 31;
if (true) {
let x = 71; // different variable
console.log(x); // 71
}
console.log(x); // 31
}
  • let是块级作用域,var只有全局作用域和函数作用域
  • var存在变量提升,let,const不存在。let,const在变量声明之前访问变量的话,会提示ReferenceError。var的话默认undefined
  • ES6明确规定,如果区块中存在let命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。所以在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
  • var:变量可以多次声明,而let不允许在相同作用域内,重复声明同一个变量。
  • 使用 var 和 function 声明的全局变量依旧作为全局对象的属性,使用 let, const 命令声明的全局变量不属于全局对象的属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script>
    var a = 10;
    console.log(window.a); //10
    console.log(this.a) //10

    let b = 20;
    console.log(window.b); // undefined
    console.log(this.b) // undefined
    </script>
  • 不同的是 const 变量一旦被赋值,就不能再改变了,但是这并不意味着使用 const 声明的变量本身不可变,只是说它不可被再次赋值了,而且const 声明的变量必须经过初始化。

    2.this

    每次调用函数时,浏览器都会传递两个隐含的参数(this,arguments)
    没有直接指定就是window
  • 以函数的形式调用时,this永远都是window
  • 以方法的形式调用时,this就是调用方法的哪个对象
  • 以构造函数形式调用,this就是创建的对象
  • 使用call和apply调用时,this就是指定的那个对象

修改this

  • call()方法可以将实参在对象之后依次传递
  • apply()方法需要将实参封装到一个数组中
  • bind 除了返回是函数以外,它的参数和 call 一样。
    当然,三者的参数不限定是 string 类型,允许是各种类型,包括函数 、 object 等等

ES11引入globalThis,作用是始终指向全局对象

arguments封装实参的对象,是一个类数组对象,可以通过索引来操作数据,也可以获取长度

arguments.callee 这个属性就是正在指向的函数的对象

使用工厂创建的对象,无法区分类型,都是Object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

function Person(color{
        console.log(this)
        this.color = color
        this.getColor = function({
            console.log(this)
            return this.color
        }
        this.setColor = function(color{
            this.color = color
            console.log(this)
        }
    }
    Person("red")   //this是window  
    var p = new Person("yellow")    //this是p
    p.getColor()    //this是p
    var obj = {}
    p.setColor.call(obj,"black")    //this是obj
    var test = p.setColor;
    test(); //this是window  
    function fun1(){
        function fun2(){
            console.log(this);
        }
        fun2(); //this是window
    }
    fun1();

var name = "The window";
   var object = {
       name: "My Object",
       getNameFunc: function(){
           return function(){
               return this.name;
           };
       }
   }
   alert(object.getNameFunc()())    //The window
   var name2 = "The window";
   var object = {
       name: "My Object",
       getNameFunc: function(){
           var that = this
           return function(){
               return that.name;
           };
       }
   }
   alert(object.getNameFunc()())    //My object

不加分号会有问题的两种情况

  • 小括号开头的前一行要加
  • 中括号开头的前一行要加

3.构造函数与普通函数以及IIFE

1
2
3
4
5
6
7
8
9

function Person(){
    }
    // 普通函数的调用方式
    var per = Person(); 
    console.log(per);   //undefined
    //构造函数的调用方式  
    var pe = new Person();
    console.log(pe);    //Person{}

构造函数的执行流程:
1.立刻创建一个新的对象
2.将新建对象设置为函数中的this
3.逐行执行函数中的代码
4.将新建的对象作为返回值返回

  • 两个构造函数,如何让A继承B

    1
    2
    3
    4
    function A(...) {}  A.prototype...
    function B(...) {} B.prototype...
    //在A的构造函数里new B
    A.prototype = Object.create(B.prototype)
  • IIFE
    立即调用函数
    匿名函数自调用
    作用:隐藏实现;不会污染全局命名空间;编写js模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    (function ({  //匿名函数自调用
        var a = 3
        console.log(a + 3)  //6
    })()
    var a = 4
    console.log(a)  //4
    ;(function ({
        var a = 1
        function test({
            console.log(++a)    //会报错,给函数开头加分号
        }
        window.$ = function ({    //向外暴露一个全局函数
            return {
                test: test
            }
        }
    })()
    $().test()  //2
  • new操作符原理

  1. 创建一个类的实例:创建一个空对象obj,然后把这个空对象的proto设置为构造函数的prototype。
  2. 初始化实例:构造函数被传入参数并调用,关键字this被设定指向该实例obj。
  3. 返回实例obj。

Math.random()生成一个[0,1)的随机数
生成一个x-y之间的随机数
Math.round(Math.random()*(y-x)+x)

正则
加号:至少出现一次
星号:0次及以上
问号:0个或1个
^中括号里小三角表示“非”;//里的小三角表示开头
\w : 任意数字,字母,下滑线
\W: 除了字母数字下滑线
\d: [0-9]
\D:[^0-9]
\s:空格
\S: 除了空格
\b:单词边界 (表示独立的一个单词)
\B:除了单词边界

1
str.replace(/^\s*|\s*$/g,"")    //去除首尾的空格

取消a的链接 设href=”javascript: ;”

4.原型prototype

因为怕污染全局作用域,所以出现。

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

  • 作为普通函数调用prototype没有任何作用
  • 以构造函数创建,都会有一个隐含属性(__proto__)指向该构造函数的原型对象
  • 当所有实例对象都有这个方法或者属性的时候,可以通过Class.prototype.属性名/方法 =的方式添加到原型中

2个检查属性的方法

  • "name" in mc

    对象mc里没有name属性,原型中有也会返回true

  • mc.hasOwnProperty("name")
    只有当对象自身含有属性时,才会返回true
    mc.__proto__.__proto__里,即mc的原型的原型,即Object(祖先)

    • 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
    • 所有函数都是Function的实例(包含Function)
    • Object的原型对象是原型链的尽头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
console.log(Fn.prototype instanceof Object//true
console.log(Object.prototype instanceof Object//false
console.log(Function.prototype instanceof Object)   //true

console.log(Function.__proto__===Function.prototype)    //true

console.log(Object.prototype.__proto__) //null
console.log(Object.prototype.prototype) //undefined


//测试 
   function A(){
   }
   A.prototype.n = 1
   var b = new A()
   A.prototype = {
       n: 2,
       m: 3
   }
   var c = new A()
   console.log(b.n,b.m,c.n,c.m) //1 undefined 2 3  

function A(x){
        this.x = x
    }
    A.prototype.x = 1
    function B(x){
        this.x = x
    }
    B.prototype = new A()
    var a = new A(2)
    var b = new B(3)
    delete b.x
    console.log(a.x)    //2
    console.log(b.x)    //undefined

var F = function(){};
Object.prototype.a = function(){
console.log('123');
};
Function.prototype.b = function(){
console.log('456');
}
var f = new F();
F.a(); //123
F.b(); //456
f.a(); //123
f.b(); //报错:Uncaught TypeError: f.b is not a function

f 并不是 Function 的实例,因为它本来就不是构造函数,调用的是 Function 原型链上的相关属性和方法了,只能访问到 Object 原型链。

分析查找路径
1> F.a 的查找路径:F 自身:没有 —> F.proto(Function.prototype):没有—> F.proto.proto(Object.prototype):找到了输出 123

2> F.b 的查找路径:F 自身:没有 —> F.prototype(Function.prototype):456

3> f.a 的查找路径:f 自身:没有 —> f.proto(Object.prototype):输出 123

4> f.b 的查找路径:f 自身:没有 —> f.proto(Object.prototype):没有 —> f.proto.proto(Object.prototype.proto:null):找不到,报错

原文链接:https://blog.csdn.net/MFWSCQ/article/details/106502796

5.数组

通过[arr.length] = 值;总会向最后一个添加值

  • js的push()与concat()

concat的作用是在原数组后追加,参数既可以是数组也可以是参数列表,参数若是数组 会自动展开(但只会展开一层)返回一个新的数组,对原数组没有影响

push也是追加,不会自动展开,会改变原数组

[].concat.apply() 可以自动展开多层

遍历

forEach()方法

  • ie9以及以上

  • 需要一个函数作为参数

  • 回调函数,由我们创建,不由我们调用

  • 数组中有几个元素,函数就会执行几次

  • 浏览器会在回调函数中传递三个参数

    • 第一个参数,正在遍历的元素
    • 第二个参数,当前正在遍历元素的索引
    • 第三个参数,正在遍历的数组

1.数组去重

  • 遍历数组 indexOf()
    1
    2
    3
    4
    5
    6
    7
    8
    1  var arr = [2, 8, 5, 0, 5, 2, 6, 7, 2]
    2 var newArr = []
    3 for (var i = 0; i < arr.length; i++) {
    4 if (newArr.indexOf(arr[i]) === -1) {
    5 newArr.push(arr[i])
    6 }
    7 }
    8 console.log(newArr) // 结果:[2, 8, 5, 0, 6, 7]
  • 排序后相邻去除法 sort()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    1 var arr = [2, 8, 5, 0, 5, 2, 6, 7, 2]
    2 arr.sort()
    3 var newArr = [arr[0]]
    4 for (var i = 1; i < arr.length; i++) {
    5 if (arr[i] !== newArr[newArr.length - 1]) {
    6 newArr.push(arr[i])
    7 }
    8 }
    9 console.log(newArr) // 结果:[0, 2, 5, 6, 7, 8]
    • map
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      var arr = [285052672]
          var newArr = []
          var map = new Map()  
          for(var i=0; i < arr.length; i++){
              if(!map.has(arr[i])) {
                  newArr.push(arr[i])  
                  map.set(arr[i],true)
              }
          }
          console.log(newArr)
  • set
    1
    2
    3
    4
    5
    6
    7
    var arr = [285052672]  
        var newArr = []  
        var set = new Set(arr)  
        set.forEach(function (item) {
            newArr.push(item)
        })  
        console.log(newArr)

2.js数组拍平

  • reduce

    1
    2
    3
    4
    5
    6
    7
    8

    var arr = [1,2,[3,4,5,[6,7,8],9],10,[11,12]];

    function flatten(arr) {
    return arr.reduce((a,b)=>{
    return a.concat(Array.isArray(b)?flatten(b):b)
    },[])
    console.log(faltten(arr))
  • some

    1
    2
    3
    4
    5
    6
    7
    8
    var arr = [1,2,[3,4,5,[6,7,8],9],10,[11,12]];
    function flatten(arr) {
    while(arr.some(item => Array.isArray(item))){
    arr = [].concat.apply([],arr);
    }
    return arr;
    }
    console.log(flatten(arr));
  • let 新数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arr = [1,2,[3,4,5,[6,7,8],9],10,[11,12]];

function flatten(arr) {
let newArr = [];
arr.forEach((val)=>{
if(val instanceof Array){
newArr = newArr.concat(fn(val));
}else{
newArr.push(val)
}
})
return newArr;
}
console.log(fn(arr));

3.set优雅处理数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//1.去重  
//先用set去重,再用扩展运算符转为数组
let arr = [2,4,7,8,2,5,4]
let result = [...new Set(arr)]
console.log(result)
//2.交集  
//1个数组都进行去重处理,然后用filter遍历第一个数组,判断第二个数组中是否含有第一个数组
let arr2 = [2,8,4,2]
let jiao = result.filter(item => new Set(arr2).has(item))
console.log(jiao)
//3.并集 
//先将2个数组进行拼接,然后用set去重,最后转化为数组 
let union = [...new Set([...arr,...arr2])]
console.log(union)
//4.差集  
let cha = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)))
console.log(cha)

6.事件

  • 捕获阶段
    从外向里,默认不会触发事件
  • 目标阶段
    已经捕获到目标
  • 冒泡阶段
    从目标向外找祖先冒泡触发

事件对象:当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为参数传给回调函数,在事件对象中封装了当前事件相关的一切信息。比如:鼠标的坐标,键盘的哪个按键被按下,鼠标滚轮的方向
事件被触发时,会默认给事件处理程序传入一个参数e,通过e,我们可以得到其中包含的与事件有关的信息。只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完毕,event对象就被销毁。

1
2
3
4
//举例使用  
el.onmousemove = function(event){
alert(event.clientX) //得到鼠标的横坐标
}

事件冒泡: 从低向上传导,当后代被触发,上面也会被触发(要求:同一个事件
body-box1-span
给body,box1,span分别绑定单击响应函数,点击span,会触发span,box1,body的每个响应函数

如何取消事件冒泡?
找到事件对象el.cancelBubble = true

事件委派: 指将事件统一绑定给元素的共同祖先元素,当后代元素上的事件触发时,会一直冒泡到祖先元素
最适合采用事件委托的事件:click,mouseup,mousedown,keyup,keydown,keypress

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

给元素的共同祖先元素绑定一次事件,应用到多个元素上
比如:给ul下的每一个li里的a绑定单击响应函数
el.target是触发事件的目标节点

事件绑定:
方式一:
使用对象.事件 = 函数的形式绑定
只能同时为一个元素绑定一个箱形函数,绑定多个,后面会覆盖前面

方式二:
el.addEventListener(事件名不带on,回调函数,false)
false表示不冒泡
可以绑定多个,按顺序

  • ie8不支持,8用el.attachEvent(事件名要on,回调函数),执行顺序从后往前

Event Loop

由于js是单线程的所以js所有任务分为两种:同步任务和异步任务。

同步任务在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

事件循环就是同步任务进入主线程,异步任务加入到任务队列中。等主线程的任务执行完就去执行任务队列中的任务,这个过程会不断重复。

宏任务和微任务

任务队列又分为宏任务和微任务
同步任务>微任务>宏任务

宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering;

微任务:process.nextTick, Promise, Object.observer, MutationObserver;

例如:setTimeout和Promise的执行顺序

执行顺序为:同步执行的代码-》promise.then->setTimeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(resolve, reject) {
console.log(2)
for (var i = 0; i < 10000; i++) {
if(i === 10) {console.log(10)}
i == 9999 && resolve();
}
console.log(3)
}).then(function() {
console.log(4)
})
console.log(5);
//输出答案为2 10 3 5 4 1

文件加载顺序
for循环会在页面加载完成之后立即执行,而响应函数会在超链接被点击时才执行,当响应函数执行时,for循环早已执行完毕

js代码放在前面的话,会阻塞页面的加载,故在前面的时候要加window.onload = function(){}

7.BOM

window 对象

  • blur()
  • confirm()
  • prompt(msg,defaultText)
    prompt()方法用于显示可提示用户进行输入的对话框。

navigator
对象属性:

  • cookieEnabled
    返回指明浏览器中是否启用 cookie 的布尔值

  • userAgent
    返回由客户机发送服务器的user-agent 头部的值
    Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0

history
对象方法:

  • back() 前一个
  • forward() 下一个
  • go() 具体页
    window.history.go(-2)

location
对象属性
host —–>返回一个URL的主机名和端口
href —–>返回一个完整的URL
pathname—–>返回一个URL的路径名
port—–>返回一个URL服务器使用的端口
search—–>返回一个URL的查询部分
protocol—–>返回一个URL的协议

window,navigtor,location,history,screen
navigtor:由于历史原因,只有userAgent可以区别

1
2
3
4
5
//判断浏览器  
var ua = navigator.userAgent
if(/firefox/i.test(ua)){
alert("是火狐")
}

点击修改样式,每一条修改就需要多次渲染,不好,采用改class一次渲染box.className += " b2",这种写法不会修改原样式,新加的样式也会正常显示

8.JSON

json就是一个特殊格式的字符串,这个字符串可以被任意语言所识别,并且可以转换为任意语言中的对象,JSON在开发中主要用来数据交互
json和js对象的格式一样,只不过JSON字符串的属性名必须加双引号

1
2
//创建一个对象  
var obj = '{"name": "孙悟空","age": 18,"gender": "男"}'

JSON分类:

  • 对象{}
  • 数组[]

JSON允许的值:

  • 字符串
  • 数值
  • 布尔值
  • null
  • 对象
  • 数组

前后端数据传输

1
2
3
4
5
6
7
8
9
10
11
//一个工具类JSON可以将JSON与js对象相互转化
//JSON字符串转js对象 JSON.parse()
//js对象转JSON字符串 JSON.stringify()

var json = '{"name": "孙悟空","age": 18,"gender": "男"}' //后端传来json
var js = JSON.parse(json) //将JSON字符串转化为js对象 处理
console.log(js.gender) //男 前端得到了

var js = {"name": "孙悟空""age": 18"gender": "男"} //前端传来js对象
var str = JSON.stringify(js) //将js对象转化为JSON字符串
console.log(str) //后端得到了

JSON不能在ie7使用
ie7用eval()
eval()
用来执行一段字符串形式的JS代码块,并将执行结果返回
如果被执行的字符串中含{},会将{}当成代码块,出错——》在字符串前后各加一个()

1
2
3
var obj = '{"name": "孙悟空","age": 18,"gender": "男"}'
var o = eval("("+obj+")")
console.log(o)

不建议使用,性能差,太强大了存在安全隐患

真要ie7用json,可以引入外部js文件

9.闭包

  • 产生:当一个子函数引用了父函数时,就产生了闭包
  • 概念:嵌套的内部函数
  • 闭包存在于嵌套的内部函数中
  • 引用了几次外部函数,就产生了几次闭包

作用:
使函数内部的变量在函数执行完之后,仍然存在内存中(用完赋值null)
让函数外部可以操作到函数内部的数据

内存泄漏

  • 什么情况会引起内存泄漏?

虽然有垃圾回收机制但是我们编写代码操作不当还是会造成内存泄漏。

  1. 意外的全局变量引起的内存泄漏。
    原因:全局变量,不会被回收。
    解决:使用严格模式避免。

  2. 闭包引起的内存泄漏
    原因:闭包可以维持函数内局部变量,使其得不到释放。
    解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。

  3. 没有清理的DOM元素引用
    原因:虽然别的地方删除了,但是对象中还存在对dom的引用
    解决:手动删除。

  4. 被遗忘的定时器或者回调
    原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。
    解决:手动删除定时器和dom。

  1. 子元素存在引用引起的内存泄漏
    原因:div中的ul li 得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。
    解决:手动删除清空。

内存溢出

  • 一种程序运行出错的错误
  • 当程序运行需要的内存超过了剩余内存

10.继承

原型链继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

<!--
方式1: 原型链继承
  1. 套路
    1. 定义父类型构造函数
    2. 给父类型的原型添加方法
    3. 定义子类型的构造函数
    4. 创建父类型的对象赋值给子类型的原型
    5. 将子类型原型的构造属性设置为子类型
    6. 给子类型原型添加方法
    7. 创建子类型的对象: 可以调用父类型的方法
  2. 关键
    1. 子类型的原型为父类型的一个实例对象
-->
<script type="text/javascript">
  //父类型
  function Supper() {
    this.supProp = 'Supper property'
  }
  Supper.prototype.showSupperProp = function () {
    console.log(this.supProp)
  }
  //子类型
  function Sub() {
    this.subProp = 'Sub property'
  }
  // 子类型的原型为父类型的一个实例对象
  Sub.prototype = new Supper()
  // 让子类型的原型的constructor指向子类型,如果没有这句 Sub的构造函数将指向Supper(Sub.__proto__.proto__)
 Sub.prototype.constructor = Sub
  Sub.prototype.showSubProp = function () {
    console.log(this.subProp)
  }
  var sub = new Sub()
  sub.showSupperProp()
  // sub.toString() 可以找到,因为toString在object.prototype上
  sub.showSubProp()
</script>

构造函数继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

<!--
方式2: 借用构造函数继承(假的)
1. 套路:
  1. 定义父类型构造函数
  2. 定义子类型构造函数
  3. 在子类型构造函数中调用父类型构造
2. 关键:
  1. 在子类型构造函数中通用call()调用父类型构造函数
-->
<script type="text/javascript">
  function Person(name, age) {
    this.name = name
    this.age = age
  }
  function Student(name, age, price) {
    Person.call(this, name, age)  // 相当于: this.Person(name, age)
    /*this.name = name
    this.age = age*/
    this.price = price
  }
  var s = new Student('Tom'2014000)
  console.log(s.name, s.age, s.price)
</script>

组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--
方式3: 原型链+借用构造函数的组合继承
1. 利用原型链实现对父类型对象的方法继承
2. 利用super()借用父类型构建函数初始化相同属性
-->
<script type="text/javascript">
  function Person(name, age) {
    this.name = name
    this.age = age
  }
  Person.prototype.setName = function (name) {
    this.name = name
  }
  function Student(name, age, price) {
    Person.call(this, name, age)  // 为了得到属性
    this.price = price
  }
  Student.prototype = new Person() // 为了能看到父类型的方法
  Student.prototype.constructor = Student //修正constructor属性
  Student.prototype.setPrice = function (price) {
    this.price = price
  }
  var s = new Student('Tom'2415000)
  s.setName('Bob')
  s.setPrice(16000)
  console.log(s.name, s.age, s.price)
</script>

11.Web Worker

  • H5提供了js分线程的实现

  • 相关API

    • Worker:构造函数,加载分线程执行的js文件
    • Worker.prototype.onmessage: 用于接收另一个线程的回调函数
    • Worker.prototype.postMessage:向另一个线程发消息
  • 不足

    • worker内代码不能操作DOM(更新UI)
    • 不能跨域加载JS
    • 不是每个浏览器都支持这个新特性