JS

正则表达式全集

字符描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。串行“\\”匹配“\”而“\(”则匹配“(”。
^ 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
* 匹配前面的子表达式零次或多次。例如,zo*能匹配“z”以及“zoo”。*等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
? 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”。?等价于{0,1}。
{n} n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
{n,} n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
{n,m} m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
. 匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“(.|\n)”的模式。
(pattern) 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\(”或“\)”。
(?:pattern) 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
(?=pattern) 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
(?<=pattern) 反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。
(?<!pattern) 反向否定预查,与正向否定预查类拟,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。
x|y 匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。
[xyz] 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d 匹配一个数字字符。等价于[0-9]。
\D 匹配一个非数字字符。等价于[^0-9]。
\f 匹配一个换页符。等价于\x0c和\cL。
\n 匹配一个换行符。等价于\x0a和\cJ。
\r 匹配一个回车符。等价于\x0d和\cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于\x09和\cI。
\v 匹配一个垂直制表符。等价于\x0b和\cK。
\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\xn 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。.
\num 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
\nm 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。
\nml 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\un 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。
任务:匹配邮箱与手机号码
/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/

手机号:

/^1\d{10}$/

校验身份证

var checkCode = function (val) {
    var p = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
    var factor = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ];
    var parity = [ 1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2 ];
    var code = val.substring(17);
    if(p.test(val)) {
        var sum = 0;
        for(var i=0;i<17;i++) {
            sum += val[i]*factor[i];
        }
        if(parity[sum % 11] == code.toUpperCase()) {
            return true;
        }
    }
    return false;
}
// 输出 true, 校验码相符
console.log(checkCode("11010519491231002X"));
// 输出 false, 校验码不符
console.log(checkCode("110105194912310021"));

区分理解call,apply,bind

作用: call,apply,bind 都是改变this指向的

语法:

 func.call(thisagr,param1,param1,...)
 func.apply(thisagr,[param1,param1,...])
 func.bind(thisagr,param1,param1,...)

1.call:

  1.1 call第一个参数,就是要变成的this的对象

 var obj = { name: 'psg' };
     function fn(num1, num2) {
       console.log(num1 + num2);
       console.log("this指向:" + this, "num1:" + num1, "num2:" + num2)
   }
   fn(100, 200);//this指向window, num1=100, num2=200
   fn.call(100, 200);////this指向100, num1=200, num2=undefined
   fn.call(obj, 100, 200);//this指向obj,  num1=100, num2=200

  1.2严格模式,非严格模式下的this指向

在非严格模式下:this为null,undefined时,this指向window 在严格模式下:传谁this就是谁,不传this就是undefined

  // 严格模式
  fn.call(); //this指向undefined
  fn.call(null); //this指向null
  fn.call(undefined); //this指向undefined

2.apply:

apply 于call 类似,只是不用于的语法

call传参数是逗号分割,一个一个传入:fn.call(obj,arg1,agr2)

apply传参数是用一个数组传:fn.apply(obj,[agr1,agr2])

var obj1 = { name: 'psg' };
    function fn1(num1, num2) {
      console.log(num1 + num2);
      console.log("this指向:" + this, "num1:" + num1, "num2:" + num2)
   }
   
    fn1.call(obj1, 100, 200);
    fn1.apply(obj1, [100, 200]);

3.bind:

bind 于call类似,语法一致,但是bind体现了js的预处理

bind 不会执行函数,会有一个返回值(返回值是函数的拷贝)

预处理:实现把fn的this改变成我们想要的结果,并且把对象的参数也准备了,要用的时候,直接执行就行了

var obj1 = { name: 'psg' };
    function fn1(num1, num2) {
      console.log(num1 + num2);
      console.log("this指向:" + this, "num1:" + num1, "num2:" + num2)
    }
    fn1.call(obj1, 100, 200);
    fn1.bind(obj1, 100, 200);
    
    //bind只是改变了fn中的this为obj,并且给fn传递了两个参数值,但是此时并没有给fn这个函数执行。
   // 但是,执行bind会有一个返回值,这个返回值myFn就是我们把fn的this改变后的那个结果!!!


    var myFn = fn1.bind(obj1, 100, 200);
    myFn();  //执行函数

4.小结区别:

call于apply区别:

语法不同,传给函数的参数写法不同

call于bind区别:

1.执行:

call,apply改变了this执行,立马执行函数

bind 返回了this指向后的函数,没有执行函数

 浏览器下载文件

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <script src="http://111.229.14.189/file/axios.min.js"></script>
    </head>
    <body>
        <button id="downloadCustom">下载自定义内容</button>
        <button id="downloadExcel">下载excel</button>
        <button id="downloadPic">下载图片</button>
        <script>
            //提供一个链接用户下载内容
            function download(link, name) {
                if (!name) {
                    //如果没有提供名字,从给的Link中截取最后一坨
                    name = link.slice(link.lastIndexOf('/') + 1)
                }
                let eleLink = document.createElement('a')
                eleLink.download = name
                eleLink.style.display = 'none'
                eleLink.href = link
                document.body.appendChild(eleLink)
                eleLink.click()
                document.body.removeChild(eleLink)
            }
            function downloadFile(name, content) {
                if (typeof name == 'undefined') {
                    throw new Error('The first parameter name is a must')
                }
                if (typeof content == 'undefined') {
                    throw new Error('The second parameter content is a must')
                }
                if (!(content instanceof Blob)) {
                    content = new Blob([content])
                }
                const link = URL.createObjectURL(content)
                download(link, name)
            }

            function downloadByLink(link, fileName) {
                axios
                    .request({
                        url: link,
                        responseType: 'blob'
                    })
                    .then((res) => {
                        const link = URL.createObjectURL(res.data)
                        download(link, fileName)
                    })
            }

            // 下载自定义的字符串
            function downloadCustom() {
                // 可以用来下载JSONString html等内容,只要是字符串都可以下载
                const json = JSON.stringify({ name: 'xiaoming', age: 11 })
                downloadFile('1.json', json)
            }
            //下载浏览器不会默认预览的文件,例如excel,word,ppt
            function downloadExcel() {
                download('http://111.229.14.189/file/1.xlsx')
            }
            // 下载浏览器会默认执行预览的文件,例如
            function downloadPic() {
                // 需要转发才可以,否则会有同源策略
                downloadByLink('http://111.229.14.189/file/1.jpg')
            }

            ;(function () {
                document.getElementById(
                    'downloadCustom'
                ).onclick = downloadCustom
                document.getElementById('downloadExcel').onclick = downloadExcel
                document.getElementById('downloadPic').onclick = downloadPic
            })()
        </script>
    </body>
</html>

 防抖和节流

防抖(debounce)

所谓防抖,就是当事件触发了,在 n 秒内函数只能执行一次,如果在 n 秒内又触发了该事件,则重新计算函数时间。

// 防抖
function debounce(fn, delay) {
    let timer;
    return function (args) {
        let that = this;
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn.call(that, args);
        }, delay);
    }
}
// 这时候你在划 可以看到事件在停止滑动1s后才会输出  你不停滑动不会输出 (效果你的联想)
let num = 1;
document.body.onmousemove = debounce(function () {
    document.body.innerHTML = num ++ ;
}, 1000);

节流(throttle)

所谓节流,就是随便触发事件,但是在 n 秒中只执行一次。

// 节流
function throttle(fn, delay) {
    let flag = true;
    return function (...args) {
        let that = this;
        if(!flag) return;

        flag = false;
        setTimeout(() => {
            fn.call(that, args);
            flag = true;
        }, delay);
    }
}
// 这时候你不停地划 可以看到事件每隔1s输出一次
let num = 1;
document.body.onmousemove = throttle(function () {
    document.body.innerHTML = num ++ ;
}, 1000)

 深拷贝

一行代码的深拷贝:

function cloneJSON(source) {
    return JSON.parse(JSON.stringify(source));
}

举个例子,假设有如下的数据结构

var a = {
    a1: 1,
    a2: {
        b1: 1,
        b2: {
            c1: 1
        }
    }
}

这不就是一个树吗,其实只要把数据横过来看就非常明显了。

    a
  /   \
 a1   a2        
 |    / \         
 1   b1 b2     
     |   |        
     1  c1
         |
         1       

用循环遍历一棵树,需要借助一个栈,当栈为空时就遍历完了,栈里面存储下一个需要拷贝的节点

首先我们往栈里放入种子数据,key用来存储放哪一个父元素的那一个子元素拷贝对象

然后遍历当前节点下的子元素,如果是对象就放到栈里,否则直接拷贝

function cloneLoop(x) {
    const root = {};

    //
    const loopList = [
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];

    while(loopList.length) {
        // 深度优先
        const node = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = {};
        }

        for(let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循环
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }

    return root;
}

存在的问题:假如一个对象a,a下面的两个键值都引用同一个对象b,经过深拷贝后,a的两个键值会丢失引用关系,从而变成两个不同的对象。

var b = {};
var a = {a1: b, a2: b};

a.a1 === a.a2 // true

var c = clone(a);
c.a1 === c.a2 // false

 

如果我们发现个新对象就把这个对象和他的拷贝存下来,每次拷贝对象前,都先看一下这个对象是不是已经拷贝过了,如果拷贝过了,就不需要拷贝了,直接用原来的,这样我们就能够保留引用关系了,✧(≖ ◡ ≖✿)嘿嘿

但是代码怎么写呢,o(╯□╰)o,别急往下看,其实和循环的代码大体一样,不一样的地方我用// ==========标注出来了

引入一个数组uniqueList用来存储已经拷贝的数组,每次循环遍历时,先判断对象是否在uniqueList中了,如果在的话就不执行拷贝逻辑了

find是抽象的一个函数,其实就是遍历uniqueList

// 保持引用关系
function cloneForce(x) {
    // =============
    const uniqueList = []; // 用来去重
    // =============

    let root = {};

    // 循环数组
    const loopList = [
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];

    while(loopList.length) {
        // 深度优先
        const node = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = {};
        }
        
        // =============
        // 数据已经存在
        let uniqueData = find(uniqueList, data);
        if (uniqueData) {
            parent[key] = uniqueData.target;
            continue; // 中断本次循环
        }

        // 数据不存在
        // 保存源数据,在拷贝数据中对应的引用
        uniqueList.push({
            source: data,
            target: res,
        });
        // =============
    
        for(let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循环
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }

    return root;
}

function find(arr, item) {
    for(let i = 0; i < arr.length; i++) {
        if (arr[i].source === item) {
            return arr[i];
        }
    }

    return null;
}

性能对比:

 

 JS高级

 

1、基本数据类型和引用类型

   1 分类
            基本类型
                String  Number boolean undefined null
            引用类型
                Object Array(一种特别的对象,数值下标,内部数据是有序的) Function(一种特别的对象,可以执行) 
        2 判断
            typeof:返回数据类型的字符串表达(可以判断:undefined/数值/字符串/布尔) typeof null='object'
                    不能判断null与object,object与array
            instanceof:判断对象的具体类型
            === :可以判断:undefined(var a; a === undefined);
            null(var a = null ;typeof a === "object"; a===null)
  var b1 = { // 对象类型
        b2: [1, 'abc', console.log],
        b3: function () {
            console.log('b3')
            return function () {
                return 'xfzhang'
            }
        }
    }
 
    console.log(b1 instanceof Object, b1 instanceof Array) // true false
    console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true
    console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true
 
    console.log(typeof b1.b3 === 'function') // true
 
    console.log(typeof b1.b2[2] === 'function') // true
    b1.b2[2](4) // 4
    console.log(b1.b3()()) // xfzhang

1. undefined与null的区别?

  undefined代表定义未赋值;null定义并赋值,只是值为null。

2. 什么时候给变量赋值为null呢?

初始赋值,表明将要赋值为对象;结束前,让对象成为垃圾对象(被垃圾回收器回收)。初始化赋值:将要作为引用变量使用, 但对象还没有确定。结束时:将变量指向的对象成为垃圾对象。

var a = null // a将指向一个对象,但对象此时还没有确定
a = null // 让a指向的对象成为垃圾对象

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

js的变量本身是没有类型的,变量的类型实际上是变量内存中数据的类型(js是弱类型的语言)。var a; 判断变量类型,实际上 是判断值的类型。

数据的类型(数据对象):
* 基本类型
* 对象类型

变量的类型(变量内存值的类型):
* 基本类型:保存基本类型的数据(保存基本类型数据的变量)。
* 引用类型:保存对象地址值(保存对象地址值的变量)。

数据 变量 类型

什么是数据:

  存储在内存中代表特定信息,本质上是0101。。。

  特点:可传递,可运算(一切皆数据,内存中所有操作的目标:数据)

什么是内存:

  内存条通电以后产生的可存储数据的空间(临时的)

  内存产生和死亡:内存条(电路板)==》通电==》产生内存==》存储数据==》处理数据==》断电==》内存空间和数据都消失

  一块小内存的2个数据(内部存储的数据+地址值)

问题:var a = XXX,a内存中到底保存的是什么?
    XXX是基本数据,保存的就是这个数据;
    XXX是对象,保存的是对象的地址值
  XXX是变量,保存的是XXX的内存内容(可能是基本数据,也可能是地址值)

  内存的分类:

    栈:全局变量/局部变量

    堆:对象

什么是变量:

  可以变化的量,由变量名和变量值组成

  每个变量都对应一个小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据

内存,数据和变量的区别:

   内存:用来存储数据的空间,变量是内存的标识

 

 引用变量赋值的问题:

  2个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到的是修改后的数据

  2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个对象

var a = {age:12}
var b = a
a = {name:'BOB',age:18}
console.log(b.age,a.name,a.age) //12, BOB,18

function fn(obj){
    obj = {age:19}
}
fn(a)  // 1、将a的内容赋值给obj  2、函数体里面给obj重新赋值,obj指向了新的对象,此时不影响a的内存值
console.log(a.age)  //18

js调用函数时,是值传递还是引用传递:

   理解1:都是值(基本/地址值)传递

  理解2:可能是值传递,也可能是引用传递(地址值)

var a = 1
function fn(a){
    a = a+1
}
fn(a)  //拿到1,赋值给fn里面的参数a
console.log(a) //1

JS引擎如何管理内存:

1、内存生命周期

  分配小空间,得到他的使用权

   存储数据,可以反复进行操作

  释放小内存空间

2、释放内存

   局部变量:函数执行完自动释放

  对象:成为垃圾对象==》垃圾回收器回收

function fn(){
    obj = {}
}
fn()  //obj是自动释放,obj所指向的对象是在后面的某个时刻由垃圾回收器回收

对象

什么是对象:多个数据的封装体;用来保存多个数据的容器;一个对象代表现实事件中的一个事物

为什么要用对象:管理多个数据

对象的组成:1、属性:属性名(字符串)和属性值组成;2、方法:一种特殊的属性(属性值是函数)

如何访问对象内部的数据:.属性名 or ["属性名"]

  什么时候必须使用["属性名"]:属性名包含了特殊字符:-  空格;属性名不确定

函数

函数的调用:

  直接调用  fn()

  通过对象调用  obj.fn()

  new 调用:new fn()

  fn.call/apply(obj):临时让fn成为obj的方法进行调用

var obj = {}
function fn(){
    this.xxx = "nielifang"
}    
fn.call(obj)  // 可以让一个函数成为指定任意对象的方法进行调用
console.log(obj.xxx)  // nielifang

回调函数

什么是回调函数:定义了,没有调用,但是最终执行了

函数的this

  任何函数本质上都是通过某个对象来调用的(如果没有指定就是window),所有函数内部都有一个变量this,他的值是调用函数的当前对象

补充:js语句的后面加不加分号:

  是否加分号是编码风格的问题,没有应不应该,只有喜不喜欢

  但是下面2种情况不加分号会有问题:小括号开头的前一条语句;中方括号开头的前一天语句

 

函数高级:

原型与原型链

 1、函数的prototype属性:  

  每个函数都有一个prototype属性,他默认指向一个object的空对象

  原型对象中有一个属性constructor,他指向函数对象

 2、给原型对象添加属性

  作用:函数的所有实例对象自动拥有原型中的属性(方法)

显式原型与隐式原型

 1、每个函数function都有一个prototype,即显式原型

 2、每个实例对象都有一个__proto__,可称为隐式原型

 3、对象的隐式原型的值为其对应构造函数的显式原型的值 (var fn = new Fn()   Fn.prototype === fn.__proto__)

总结:

  函数的prototype属性:在定义函数时自动添加,默认值是一个空的object对象

  对象的__proto__属性:创建对象时自动添加的,默认值是构造函数的prototype属性值

  可以直接操作显式原型,但不能直接操作隐式原型(es6之前)

 

原型链

 

 

 访问一个对象的属性时,先在自身属性中找,找到返回;如果没有,沿着__proto__这条链往上找,找到返回;如果最终没有找到,返回undefined。

别名:隐式原型链

作用:查找对象的属性

补充:

  函数的显示原型指向的对象:默认是空的object实例对象()

   所有函数都是Function的实例,包括Function本身(Function.__proto__ === Function.prototype)

  Object的原型对象是原型链的尽头(Object.prototype.__proto__ === null)

原型链的属性

  读取对象的属性值时,会自动到原型链中查找

  设置对象的属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值

  方法一般定义在原型中,属性一般通过构造函数定义在对象本身上

instanceof

  A(实例对象) instanceof B(构造函数)

  如果B函数的显示原型对象在A对象的原型链上,返回true  (A.__proto__ === B.prototype)

  Function通过new 自己 来产生的实例

 

 

测试题

 

执行上下文与执行上下文栈

变量提升与函数提升

  1、变量声明提升:通过var声明的变量,在定义语句之前就可以调用,返回的值是undefined

  2、函数声明提升:通过function声明的函数,在之前就可以调用,值是函数定义(对象)

  3、问题:变量提升和函数提升是如何产生的?  

执行上下文

 代码分类:全局代码和函数代码(位置)

 全局执行上下文:

  1、在执行全局代码前将window确定为全局执行上下文

  2、对全局数据进行预处理

    1、对var声明的全局变量 ==》undefined,添加为window属性

    2、function声明的全局函数==》赋值为fun,添加为window的方法

    3、this赋值为window

  3、开始执行全局代码

 函数执行上下文:

  1、在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中);

  2、对局部数据进行预处理

    形参变量==》赋值(实参)==》添加为执行上下文的属性

    arguments==》赋值(实参列表),添加为执行上下文的属性

    对var定义的局部变量 ==》undefined,添加为执行上下文的属性

    funciton声明的函数==》赋值为fun,添加为执行上下文的方法

    this==》赋值(调用函数的对象)

  3、开始执行函数体代码

执行上下文栈(后进先出)

1、 在全局代码执行前,JS引擎会创建一个栈来管理所有的执行上下文对象;

2、在全局执行上下文(window)确定后,将其添加到栈中(压栈);

3、在函数执行上下文创建后,将其添加到栈中(压栈);

4、在当前函数执行完后,将栈顶的对象移除(出栈);

5、当所有的代码执行完后,栈中只剩下window

面试题:

执行结果:

 整个过程产生了几个执行上下文:

  5个( f(4) \ f(3) \ f(2) \ f(1) \ window)

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)

测试1:先执行变量提升,再执行函数提升

function a(){}
var a
console.log(typeof(a))   // "function"

  变形:

function a(){}
var a = 10
console.log(typeof(a))  //"number"

测试2:b in window 返回true

if(! (b in window)){
    var b = 1
}
console.log(b)  // undefined

测试3:

var c = 1
function c(c){
    console.log(c)
}
c(2) // c is not a function

作用域与作用域链

   理解:就是一块“地盘”,一个代码段所在的区域;是静态的,在编写代码的时候就确定了,后面不会变

  分类:全局作用域、函数作用域、块作用域

  作用:隔离变量,不同作用域下同名变量不会有冲突

作用域链:多个上下级关系的作用域形成的链,他的方向是从内到外的;查找变量时就是沿着作用域链来查找的

测试题:

var x = 10;
function fn(){
  console.log(x)  
}
function show(f){
  var x = 20;
   f()    
}
show(fn)  // 10

 闭包

 产生:当一个嵌套的内部函数引用了嵌套的外部函数的变量时,就产生了闭包

  闭包存在与嵌套的内部函数中

闭包产生的条件:

  1、函数嵌套

  2、内部函数引用了外部函数的数据

function fn1(){
 var a = 2
 function fn2(){
   console.log(a)
  }
}
fn1()

常见的闭包:

  1、将函数作为另一个函数的返回值

function fn1(){
//此时闭包已经产生(因为函数提升,内部函数对象已经创建了)
var a = 2 function fn2(){ a++ console.log(a) } return fn2 } var f = fn1() f() //3 f() //4
//总共产生了一次闭包,调用了2次内部函数;产生几次闭包主要看调用了几次外部函数
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)

  2、将函数作为实参传递给另一个函数调用

function showDelay(msg,time){
  setTimeout(function(){
    alert(msg)  
    },time)  
}
showDelay("nielifang",2000)

闭包的作用:

  1、使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)

  2、让函数外部可以操作(读写)函数内部的数据

问题:函数执行结束之后,函数内部声明的局部变量是否还存在? 

  答:一般是不存在的,存在与闭包中的变量才可能存在

  在函数外部能直接访问函数内部的局部变量吗?

  答:不能。但我们可以通过闭包让外部操作他

闭包的生命周期:

  产生:在嵌套内部函数定义执行完时就产生了(不是调用时产生)

  死亡:当嵌套内部函数成为垃圾对象

闭包的应用:自定义js模块

  1、具有特定功能的js模块

  2、将所有的数据和功能都封装在一个函数里(私有的)

  3、只向外暴露一个包含n个方法的对象或函数

  4、模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能

myMoudle.js文件

(functio (window){
      // 私有属性
      var msg = "nielifang"
      function doSomething(){
        console.log("dosomething()",msg.toUpperCase())
      },
      function doOtherthing(){
        console.log("doOtherthing()",msg.toLowerCase())
      }
    window.module =     
                     {doSomething:doSomething,
                      doOtherthing:doOtherthing}
    
//return {doSomething:doSomething,doOtherthing:doOtherthing} 
})(window)                 

 闭包的缺点:

  1、函数执行结束后,函数内的局部变量没有释放,占用内存时间会变长

  2、容易造成内存泄漏

解决:

  能不用闭包就不用;及时释放(f = null,让内部函数成为垃圾对象)

测试题:

function fun(n,o){
      console.log(o)
      return {
        fun:function(m){
          return fun(m,n)
        }
      }
    }
    var a = fun(0)
    a.fun(1)  //0
    a.fun(2)  //0
    a.fun(3)  //0
    var b = fun(0).fun(1).fun(2).fun(3)  //undefined,0,1,2
    var c = fun(0).fun(1)
    c.fun(2)
    c.fun(3)  //undefined,0,1,1

内存溢出与内存泄漏

内存溢出:

   一种程序运行出现的错误;当程序运行需要的内存超过了剩余的内存时,就抛出内存溢出的错误

内存泄漏:

  占用的内存没有及时释放;

  内存泄漏积累多了就容易导致内存溢出;

  常见的内存泄漏:

    意外的全局变量

    没有及时清理的计时器或回调函数

    闭包

测试题:

var name2 = "The Window"
    var Object2 = {
      name2:'My Object',
      getNameFunc:function(){
        return function(){
          return this.name2  //function中的this指的是window
        }
      }
    }
    alert(Object2.getNameFunc()())  // The Window
  var name1 = "The Window"
    var Object1 = {
      name1:'My Object',
      getNameFunc:function(){
        var that = this;  //this指的是调用者object1
        return function(){
          return that.name1
        }
      }
    }
    alert(Object1.getNameFunc()())  // My Object

对象创建模式

工厂模式(每次运行都能return出一个新的对象):

  套路:通过工厂函数动态创建对象并返回

  适用场景:需要创建多个对象

  问题:对象没有一个具体的类型,都是object类型

function createPerson(name,age){
  var obj = {
    name:name,
    age:age,
    setName:function(){
        this.name = name  
    }
 } 
  return obj
}
//创建2个人 var p1 = createPerson("TOM",12) var p2 = createPerson("BOB",13)

自定义构造函数模式

  套路:自定义构造函数,通过new创建对象

  适用场景:需要创建多个类型确定的对象

  问题:每个对象都有相同的数据,浪费内存

function Person(name,age){
     this.name = name
     this.age = age  
     this.setName = function(name){
        this.name = name  
    }
 } 
var p = new Person("TOM",12)
console.log(p instanceof Person)

function Student(name,price){
    this.name = name
    this.price = price
}
var s = new Student("Jack",13000)
console.log(s instanceof Student)

构造函数+原型的组合模式

套路:自定义构造函数,属性在函数中初始化,方法添加到原型上

适用场景:需要创建多个类型确定的对象

function Person(name,age){
    this.name = name
    this.age = age    
}
Person.prototype.setName = function(name){
    this.name = name
}
var p1 = new Person("TOM",12)
var p2 = new Person("JACK",13)

原型链继承

1、原型链继承

  套路:定义父类型构造函数;给父类型的原型添加方法;定义子类型的构造函数;创建父类型的对象赋值给子类型的原型;

     将子类型原型的构造属性设置为子类型;给子类型原型添加方法;创建子类型的对象:可以调用父类型的方法

  关键:子类型的原型为父类型的一个实例对象

  // 父类型
    function Supper(){
      this.supProp = 'Supper property'
    }
    Supper.prototype.showSupperProp = function(){
      console.log(this.supProp);
    }
    // 子类型
    function Sub(){
      this.subProp = "Sub property"
    }
     //子类型的原型为父类型的一个实例对象
    Sub.prototype = new Supper()
    Sub.prototype.showSubProp = function(){
      console.log(this.subProp);
    }
    var sub = new Sub()
    sub.showSupperProp()

 借用构造函数继承

  在子类型构造函数中通过call()调用父类型构造函数

function Person(name,age){
      this.name = name
      this.age  = age
    }
function Student(name,age,price){
      Person.call(this,name,age)
      this.price = price
    }
var s = new Student("TOM",12,13000)

 组合继承

  原型链+借用构造函数的组合继承:
    利用原型链实现对父类型对象的方法继承
    利用super()借用父类型构造函数初始化相同属性
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",12,13000)
s.setName("BOB")
s.setPrice("99999")

 进程与线程

进程:程序的一次执行,它占有一片独立的内存空间

线程:是进程内一个独立的执行单元;是程序执行的一个完整的流程;是CPU最小的调度单元

 

 

相关知识:

  应用程序必须运行在某个进程的某个线程上

  一个进程中至少有一个运行的线程:主线程,进程启动后自动创建

  一个进程中也可以同时运行多个线程,也就是说程序是多线程运行的

   一个进程内的数据可以供其中的多个线程直接共享

  多个进程之间的数据是不能直接共享的

  线程池:保存多个线程对象的容器,实现线程对象的反复利用

多线程:

  优点:提高CPU的利用率

  缺点:创建多线程开销

        线程间切换开销

                  死锁与状态同步问题

Js是单线程运行的,但使用H5中的web workers可以多线程运行

浏览器的运行是多线程的(有的是单进程,有的是多进程)

浏览器内核

  支撑浏览器运行的最核心的程序

  不同浏览器不一样

  内核由很多模块组成

 重载

同样的函数,不同的参数个数,执行不同的代码

比如:

function fn(name) {
  console.log(`我是${name}`)
}

function fn(name, age) {
  console.log(`我是${name},今年${age}岁`)
}

function fn(name, age, sport) {
  console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`)
}

/*
* 理想结果
*/
fn('nlf') // 我是nlf
fn('nlf', 18) // 我是nlf,今年18岁
fn('nlf', 18, '打篮球') // 我是nlf,今年18岁,喜欢运动是打篮球
//实际结果
我是nlf,今年undefined岁,喜欢运动是undefined
我是nlf,今年18岁,喜欢运动是undefined
我是nlf,今年18岁,喜欢运动是打篮球

最后一个fn的定义,把前面两个都给覆盖了,所以没有实现重载的效果

简单的做法:想要实现理想的重载效果,可以只写一个fn函数,并在这个函数中判断arguments类数组的长度,执行不同的代码,就可以完成重载的效果

function fn() {
  switch (arguments.length) {
    case 1:
      var [name] = arguments
      console.log(`我是${name}`)
      break;
    case 2:
      var [name, age] = arguments
      console.log(`我是${name},今年${age}岁`)
      break;
    case 3:
      var [name, age, sport] = arguments
      console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`)
      break;
  }
}

发现了一种比较高端的做法,可以利用闭包来实现重载的效果

function addMethod(object, name, fn) {
  var old = object[name]; //把前一次添加的方法存在一个临时变量old里面
  object[name] = function () { // 重写了object[name]的方法
    // 如果调用object[name]方法时,传入的参数个数跟预期的一致,则直接调用
    if (fn.length === arguments.length) {
      return fn.apply(this, arguments);
      // 否则,判断old是否是函数,如果是,就调用old
    } else if (typeof old === "function") {
      return old.apply(this, arguments);
    }
  }
}

addMethod(window, 'fn', (name) => console.log(`我是${name}`))
addMethod(window, 'fn', (name, age) => console.log(`我是${name},今年${age}岁`))
addMethod(window, 'fn', (name, age, sport) => console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`))

/*
* 实现效果
*/

window.fn('nlf') // 我是nlf
window.fn('nlf', 18) // 我是nlf,今年18岁
window.fn('nlf', 18, '打篮球') // 我是nlf,今年18岁,喜欢运动是打篮球

 按位非  ~

js中按位非运算符是 ~ ,作用是将每位二进制取反

比如:十进制2的二进制表示为:0000,0010

每位都取反:1111,1101 ,这是内存中的保存形式。

我们读取的十进制是根据原码来读取,而在内存中,数值都是以二进制补码形式保存的。

正数的补码和原码一样,负数的原码转补码或者补码转原码的规则:
符号位不变,将剩余位取反,得到反码,在反码的基础上最后一位加一得到负数的补码。

1111,1101输出10进制的过程:

    1. 符号位不变,剩余位取反
        1000,0010
    2. 最后一位加1
        1000,0011
    3. 得的结果1000,0011就是-3的原码形式

记忆方法是~(A) = -(A+1)

~(1) = -2
~(0) = -1
~(-1) = 0

Proxy代理

  Proxy代理对象:使用代理对象,对目标对象的属性操作全部改为对代理对象相同属性的操作,代理对象提供了对属性获取 [[get]] 、修改 [[set]] 等操作的拦截,js将这种拦截称为trap(捕捉器)。

  通过捕捉器,我们就可以捕获到 代码中对属性的操作时机,让我们能够先执行我们自定义的业务逻辑代码。因为我们对目标对象的属性操作改为了对代理对象相同的属性操作,所以我们在最后需要通过Reflect执行目标对象的原始操作。
比如:

 var consume = 0
    var obj = {wallet:100}
    var handlers = {
      set(target,key,val){
        //target:目标对象,key:代理对象要修改的属性
        consume++
        Reflect.set(target,key,val)

      }
    }
    //代理对象
    var pObj = new Proxy(obj,handlers)
    //将目标对象obj的属性wallet的操作改为代理对象相同属性wallet的操作
    pObj.wallet = 97
     pObj.wallet = 94
    console.log(obj.wallet) //94
    console,log(consume) //2

  Reflect

  Reflect用来触发目标对象执行相应的操作

Reflect.get(target,key,context)  //等价于target[key]
Reflect.set(target,key,val)  //等价于 target[key] = val

async/await原理

 用处就是:用同步方式,执行异步操作

 async/await是一种语法糖语法糖,我个人理解就是,语法糖就是一个东西,这个东西你就算不用他,你用其他手段也能达到这个东西同样的效果,但是可能就没有这个东西这么方便了。

1、generator函数:

generator函数跟普通函数在写法上的区别就是:多了一个星号*,并且只有在generator函数中才能使用yield,什么是yield呢,他相当于generator函数执行的中途暂停点,比如下方有3个暂停点。而怎么才能暂停后继续走呢?那就得使用到next方法next方法执行后会返回一个对象,对象中有valuedone两个属性
function* gen() {
  yield 1
  yield 2
  yield 3
  return 4
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: 4, done: true }

2、yield后面接Promise,next函数传参

return new Promise(resolve => {
    setTimeout(() => {
      resolve(nums * 2)
    }, 1000)
  })
}
function* gen() {
  const num1 = yield fn(1)
  const num2 = yield fn(num1)
  const num3 = yield fn(num2)
  return num3
}
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
  console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
  console.log(res1) // 1秒后同时输出 2

  const next2 = g.next(res1) // 传入上次的res1
  next2.value.then(res2 => {
    console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false }
    console.log(res2) // 2秒后同时输出 4

    const next3 = g.next(res2) // 传入上次的res2
    next3.value.then(res3 => {
      console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
      console.log(res3) // 3秒后同时输出 8

       // 传入上次的res3
      console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
    })
  })
})

3、活代码(封装一个高阶函数)

上面的代码其实都是死代码,因为一个async函数中可能有2个await,3个await,5个await ,其实await的个数是不确定的。同样类比,generator函数中,也可能有2个yield,3个yield,5个yield,所以咱们得把代码写成活的才行
function generatorToAsync(generatorFn) {
  return function() {
    const gen = generatorFn.apply(this, arguments) // gen有可能传参

    // 返回一个Promise
    return new Promise((resolve, reject) => {

      function go(key, arg) {
        let res
        try {
          res = gen[key](arg) // 这里有可能会执行返回reject状态的Promise
        } catch (error) {
          return reject(error) // 报错的话会走catch,直接reject
        }

        // 解构获得value和done
        const { value, done } = res
        if (done) {
          // 如果done为true,说明走完了,进行resolve(value)
          return resolve(value)
        } else {
          // 如果done为false,说明没走完,还得继续走

          // value有可能是:常量,Promise,Promise有可能是成功或者失败
          return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
        }
      }

      go("next") // 第一次执行
    })
  }
}

function* gen() {
  const num1 = yield fn(1)
  const num2 = yield fn(num1)
  const num3 = yield fn(num2)
  return num3
}

const asyncFn = generatorToAsync(gen)

asyncFn().then(res => console.log(res))

 

 

 

 

 

 

 
posted @ 2021-08-19 10:28  聂丽芳  阅读(141)  评论(0)    收藏  举报