前端与移动开发----JS高级----函数全解,深拷贝浅拷贝,正则表达式

JS高级03

回顾

  1. 构造函数

    大写开头的函数, 需要配合new使用, 一般用作模板

  2. new的作用

    • new触发构造函数执行
    • 改变构造函数内this的指向为实例对象
    • 向实例对象身上绑定属性和值
    • 自动返回实例对象到new调用处
  3. 实例成员就是绑定在this实例对象上的属性和方法

  4. 静态成员就是绑定在构造函数身上的属性和方法(谁绑定谁调用)

  5. prototype是构造函数特有的, 为了让实例对象共享方法, 节省内存

  6. __proto__是对象特有的, 为了调度更多的方法(口诀: 对象.__proto__ 指向了 构造函数.prototype)

  7. constructor是构造函数.prototype原型对象上的一个属性, 指回构造函数本身, 用于检测一个原型对象属于哪个构造函数的

  8. 原型链: 通过对象.__proto__逐级向上查找的过程叫原型链

  9. typeof和instanceof都能判断某个对象的类型, instanceof判断更准确(原理是对象.__proto__ 指向 构造函数.prototype)

  10. 属性继承 - 子构造函数内, 父构造函数.call(this) - 如果有参数继续在后面传递

  11. 方法继承 - 子构造函数.prototype = Object.create(父构造函数.prototype)

    并且子构造函数.prototype.constructor = 子构造函数

  12. class类的本质就是ES5的构造函数+prototype原型

9. 函数全解

9.0 普通函数

  • 函数声明

语法: function 函数名() {}

fn();
function fn(){
     
    console.log("我是函数");
    console.log(this); // window
}
  • 函数表达式

语法: var 变量名 = function(){}

var fn2 = function(){
     
    console.log("我是函数2");
    console.log(this); // window
}
fn2();
// 注意函数表达式 没有变量提升

9.1 对象里函数

语法: key对应的value值是一个函数体

var obj = {
     
    fn: function(){
     
        console.log("对象里的属性-函数");
        console.log(this); // obj
    }
}
obj.fn();

9.2 事件处理函数

语法: 给事件绑定函数

document.onclick = function(){
     
    console.log("dom绑定事件 - 处理函数");
    console.log(this); // document
}
document.addEventListener("click", function(){
     
    console.log("事件监听 - 处理函数");
    console.log(this); // document
})

9.3 定时器/计时器函数

语法: 传入定时器/计时器中的一个函数体

setInterval(function(){
     
    console.log("计时器执行");
    console.log(this); // window
}, 1000);
setTimeout(function(){
     
    console.log("定时器执行");
    console.log(this); // window
}, 1000);

9.4 构造函数

语法: function 大写名开头的函数名(){}

function Person(){
     
    console.log("构造函数 调用");
    console.log(this); // this指向实例对象
}
new Person();

9.5 匿名函数

语法: function(){} - 没有名字的函数

常用: 对象里的value或者参数使用 - 也叫函数体

// 我是匿名函数 
var a = function(){
     
    console.log("我是匿名函数");
    console.log(this); // window
}

setTimeout(a, 1000);
// 这里都是匿名函数的情况

// 常用作, 当做参数传递给别人 (函数体)

9.6 立即执行函数 (函数自调用)

语法

  • (function(){console.log(1)})();
  • (function(){console.log(2)}());
  • !function(){console.log(4)}();

解析规则: ()() 加上()是调用前面的函数体执行, 如果不及时加分号, JS解析会报错

重要: 写立即执行函数, 最后必须加分号

// 立即执行函数
(function(){
     
    console.log("我是1");
	console.log(this); // window
})();
(function(){
     
    console.log("我是2");
}());
!function(){
     
    console.log("我是3");
}();

// 立即执行函数传参
(function(num){
     
    console.log("我是1");
    console.log(num);
})(100);

(function(num){
     
    console.log("我是2");
    console.log(num);
}(200));

!function(num){
     
    console.log("我是3");
    console.log(num);
}(300);

9.7 递归函数

语法: 在函数内, 调用当前函数

// 定义函数, 调用时, 在函数内再次调用当前函数, 产生递归
// function myFn(){
     
//     console.log("函数调用");
//     myFn();
// }
// myFn();

// 问题: 递归死循环了, 出不来了..
// 解决: 注意留好出口, 看下面例子
// 例如: 用递归函数 - 输出5到1

function sum(m){
     
    if (m <= 0) {
     
        return; // 出口 - 停止递归(防止代码往下执行)
    }
    console.log(m);
    console.log(this);
    sum(--m);
}
sum(5);

this指向调用者 -> window

9.8 高阶函数

高阶函数本身是一个函数, 它可接受/返回函数体, 是对其他函数进行操作

把函数作为参数,或者把函数作为返回值的的函数,称为高阶函数

// 语法学习:

// 1. 函数作为形参:
// 定义高阶函数myFn, 形式参数fn的值是函数体
function myFn(fn){
     
    fn();
}

// 调用高阶函数myFn - 传入参数是一个函数体
myFn(function(){
     
    console.log("myFn调用执行 - 传入一个函数体给fn, 然后fn()调用下面函数体执行了 - 这里打印");
});

// 2. 函数作为返回值 - (了解)
function getHobby(){
     
    return function(){
     
        console.log("返回了一个函数体");
    }
}
var theFn = getHobby(); // 得到高阶函数内返回的function
theFn(); // 导致内部return后的函数执行


// 自己使用时, 多出现在回调函数情况

this指向调用者 - window

9.9 回调函数

在高阶函数中, 其实你就已经使用了回调函数

// 语法: my是高阶函数, fn参数的值是function(){}函数体
function my(fn){
     
    fn(); // 这里调用外部传入进来的函数 - 回调外部的函数体执行 - 这个动作就叫回调函数
}

my(function(){
     
    console.log("函数执行");
})

// 执行顺序:
// 声明函数function my
// 调用my()执行 -> 传入函数体 -> fn变量
// my()函数执行 -> 调用fn() -> 回调了外面函数体执行


// 使用场景: 例如数组的所有方法(filter, map, forEach, every, some等), 还有addEventListener, 还有计时器, 定时器 (高阶函数都是使用的回调函数概念)

9.10 闭包

变量作用域

全局变量, 局部变量(函数内var的), 当函数内部执行完毕, 会销毁函数内所有声明的变量

语法: 内层函数, 引用外层函数上的变量, 形成闭包

// 内层函数引用外层函数变量, 形成闭包
var liList = document.querySelectorAll("#myUL>li");

for (var i = 0; i < liList.length; i++) {
     
    liList[i].onclick = function(){
     
        console.log(i);
    }
} // 这里没有闭包 - 所以i的值是全局的i, 点击谁都是liList的长度


for (var j = 0; j < liList.length; j++) {
     
    function myFn(ind){
     
        el.onclick = function () {
     
        	console.log(ind); // 内层函数 - 引用外层函数上声明的变量ind - 导致myFn函数执行完, ind不会被回收 - 这里形成闭包
    	}
    }
    myFn(j);
}

浏览器调试闭包

  • 浏览器运行 - 右键检查 - Sources - 选择网页文件 - 在内层函数点击左侧数字(打断点) - 触发这行代码暂停网页执行 - 查看右侧Scope作用域 (如果有Closure代表有闭包产生)

前端与移动开发----JS高级----函数全解,深拷贝浅拷贝,正则表达式_第1张图片

闭包会造成外层函数(执行10次), 函数不会被回收, 因为它的ind的值被引用了

解决方式:

  1. 使用自定义属性 (函数内可能还有其他的代码和变量, 而属性一般只保存一个值)
  2. 不要滥用闭包 - 尽量不要嵌套函数

9.11 call / apply / bind 使用

  • call方法
  • apply方法
  • bind方法

这3个方法都用函数.调用

// 1. call方法
function Student(theName, theAge){
      // 构造方法
    this.name = theName;
    this.age = theAge;
}
var stu = {
     className: "114"};
Student.call(stu, "小花", 18); // 使用call方法, 执行Student方法, 传入stu对象, 蹭下来2个属性和传入的值
console.log(stu);

// 2. apply方法
function Student2(theName, theAge){
     
    this.name = theName;
    this.age = theAge;
}
var stu2 = {
     className: "114"};
Student2.apply(stu2, ["小花", 39]); // 使用apply方法, 执行Student2方法, 传入stu2对象, 蹭下来2个属性和传入的值 (注意传值, 必须使用数组格式)
console.log(stu2);

// 3. bind方法
function Student3(theName, theAge){
     
    this.name = theName;
    this.age = theAge;
}
var stu3 = {
     className: "114"};
var fn = Student3.bind(stu3, "小花", 10); // 使用bind不会马上调用函数执行, 而是返回一个this值指向stu3的函数
fn(); // 调用执行, 给传入的stu3对象, 绑定属性
console.log(stu3);


// 总结:
// call, apply, bind 都可以给对象绑定额外的属性和值
// call, apply, bind 一般配合构造函数使用
// call 和 apply 会马上调用函数执行, 但是apply传参必须是数组格式
// bind 不会马上调用函数执行, 会返回一个函数, 手动调用
  • this情况总结
方式 代码 this指向 备注
默认绑定 fn() / 定时器/ 计时器 window 前面默认有window.
隐式绑定 事件调用函数 / 函数方法调用 调用者 调用者.函数名()
new绑定 new 构造函数() 新对象 返回这个新对象
硬绑定 使用call / apply / bind 方法 传入的参数 call和apply马上执行函数, bind会返回一个函数

总结: 除了硬绑定和new, 其余的都看调用者是谁

9.12 练习

练习1: 闭包使用 -10个li绑定鼠标移入事件, 移入打印索引
		var liList = document.querySelectorAll("li");
        for (var i = 0; i < liList.length; i++) {
     
            function myFn(ind) {
     
                liList[ind].onclick = function () {
     
                    console.log(ind);
                }
            }
            myFn(i);
        }

练习2: 递归函数 - 用递归函数求5的阶乘 myFn(5)  (5 * 4 * 3 * 2 * 1)
        function sumFn(num){
     
            if (num == 0) {
     
                return num;
            }
            return num + sumFn(--num);
        }
        var result = sumFn(5);
        console.log(result);

10. 深拷贝浅拷贝

内存地址

代码是存储在硬盘上的 -> 浏览器上运行 -> 把代码被读取到内存中(参与CPU的执行) -> 显示结果到页面上

计算机内存中, 主要分为两大块

  • 栈内存 (基础类型: 字符串 / 数值 / 布尔)
  • 堆内存 (引用类型: 数组 / 对象)

声明(var)变量, 会在栈区开辟一个空间, 里面存储的要么是基础类型的值, 要么是数组/对象的堆地址

赋值动作

var a = 10;
var b = a; // 基础类型, 单纯值复制

var obj = {
      // obj保存的实际上是堆内存地址
    age: 18
};
var obj2 = obj; // obj变量的内存地址, 赋予给obj2变量

10.0 浅拷贝

把一个引用类型里的内容, 拷贝到另一个新的引用类型中

只拷贝第一层的内容 - 浅拷贝

var obj = {
     
    age: 18,
    grade: {
     
        math: 99,
        chinese: 80,
        english: 89
    }
};
var newObj = {
     }; // 一个新的对象
for (var prop in obj) {
      // 把obj里的key和value都拿过来
    newObj[prop] = obj[prop]; // 如果是基础类型就是单纯的值复制, 如果是引用类型保存的是堆内存地址(互相引用着同一个空间)
}

// 测试深层是否还是同一个内存地址互相影响
newObj.grade.math = 60;
console.log(obj); // {age: 18, grade: { math: 60, chinese: 80, english: 89}}

// 总结:
// 浅拷贝, 只拷贝第一层的内存
// 如果第二层还是个引用类型, 拷贝的都是内存地址 (内部还是互相影响)

10.1 深拷贝

for…in 知识点铺垫

// 1. for...in知识点铺垫
// for (var k in obj){}
// for...in... 既能遍历对象, 也能遍历数组
// 对象遍历的是key, 数组遍历出来的是下角标索引
var o = {
     
    age: 18,
    sex: "男"
}
for (var prop in o) {
     
    console.log(prop);
}

var arr = [10, 55, 22];
for (var prop in arr) {
     
    console.log(prop);
}    

深拷贝实现

  • 不知道数据有多少层
  • 不知道数据的类型 - 需要判断
  • 递归 + 判断
// 2. 把obj对象 - 拷贝到一个新的对象里
var oldObj = {
     
    age: 18,
    color: ["yellow", "blue"]
};

var newObj = {
     };
function deepClone(newObj, oldObj) {
      // {}, {age:18, color: ["yellow", "blue"]}
    for (var k in oldObj) {
     
        var value = oldObj[k];
        newObj[k] = value;
    }
}
deepClone(newObj, oldObj);
newObj.color[0] = "green";
console.log(oldObj); // 发现修改的是newObj, 但是oldObj的第二层数组被影响了 (我们想要的是互不影响的2个对象)

// 3. 深拷贝 - 如果是数组类型
var oldObj = {
     
    age: 18,
    color: ["yellow", "blue"]
};

var newObj = {
     };
function deepClone(newObj, oldObj) {
      // {}, {age:18, color: ["yellow"]}
    for (var k in oldObj) {
     
        var value = oldObj[k];
        if (value instanceof Array) {
      // 如果当前的value值是数组类型, 不能直接赋予到newObj对应的key上, 而应该给一个新的数组(保证和原来的数组不是同一个)
            newObj[k] = [];
            // (难点-核心)但是发现value的值是["yellow", "blue"] - 我们可以再次调用deepClone在遍历这个数组
            deepClone(newObj[k], value);
        } else {
     
            newObj[k] = value;
        }
    }
}
deepClone(newObj, oldObj);
console.log(newObj);
newObj.color[0] = "green";
console.log(oldObj); // 数组没问题了

// 4. 深拷贝 - 如果内层又是对象类型
var oldObj = {
     
    age: 18,
    color: ["yellow", "blue"],
    grade: {
     
        math: 99,
        chinese: 80,
        english: 89
    }
};
var newObj = {
     };
function deepClone(newObj, oldObj){
     
    for (var k in oldObj) {
     
        var value = oldObj[k];
        if (value instanceof Array) {
     
            newObj[k] = [];
            deepClone(newObj[k], value);
        } else if (value instanceof Object) {
      // 如果当前的值又是个对象-把这个对象看成像oldObj一样, 是不是要调用deepClone啊
            // 那再给当前的key, 一个新的对象(相当于newObj一样), 再传入进deepClone里就ok了
            newObj[k] = {
     };
            deepClone(newObj[k], value);
        }
        else {
     
            newObj[k] = value;
        }
    }
}
deepClone(newObj, oldObj);
console.log(newObj);
newObj.grade.math = 1000;
console.log(oldObj); // 原来的对象并未受到影响

// 总结:
// 1. 递归的使用可以用在不确定层次的情况下来使用
// 2. 每次递归函数都是独立执行的, 与上次函数执行无关
// 3. 递归无论有没有return, 执行完了都要回到上次调用的地方

11. 正则表达式

什么是正则表达式

var str = "chuanzhi_12345678-heihei"; 
// 判断这个字符串是否符合既有数字,字母,下划线,中划线以及长度在0-16位的代码
// 能写出来但是很麻烦, 而正则表达式只需要写几个字符就能作为规则来进行匹配
  • 匹配字符串中字符组合的规则, 表单验证
  • 检索关键字,过滤敏感字符,替换字符

其他语言也会使用正则表达式,本节主要是利用JavaScript 正则表达式完成验证

11.0 正则 - JS创建正则对象

// 方式1: 通过RegExp() 构造函数创建
var reg1 = new RegExp(/xyz/);
console.log(reg1);

// 方式2: 通过字面量(语法糖) - 创建正则对象
var reg2 = /xyz/;
console.log(reg2);

// 总结:
// 常用字面量方式来创建, 正则对象

11.1 正则 - 测试字符串

连续出现过目标字符串

var reg = /xyz/;
var str = "xzsdafxy0zsdyzxsdfxy5zsdxyzfsadfsdxy4zfy";

// test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串
console.log(reg.test(str));

var reg2 = /123/;
var str2 = "1sssss2ss3";
console.log(reg2.test(str2));
// 必须出现过123连续的

11.2 正则 - 边界符

用户描述规则出现在的位置 (开头还是末尾)

开头是否为连续的目标字符串

结尾是否为连续的目标字符串

// 1. 先要判断开头是否是xyz
var reg = /^xyz/;
var str = "xyzsdfsdfsdfsdfsdfsdxyzsdfasdfsadf";
console.log(reg.test(str));

// 2. 判断结尾是否是xyz
var reg2 = /xyz$/;
var str2 = "xxxxhahhaxyz";
console.log(reg2.test(str2));

// 3. 这种写法是, 精确匹配(中间没有任何匹配符号)
var reg3 = /^xyz$/;
var str3 = "xyzxxxhahhaxyz";
console.log(reg3.test(str3));

11.3 正则 - |

这个是或者的意思

// 以xyz开头, 或者以xyz结尾
var reg = /^xyz|xyz$/;
var str = "xyzxxxhahhaxyz";
console.log(reg.test(str));

// 只要匹配其中一项就返回true
var reg2 = /傻|笨蛋|可爱/;
var str = "这里有一个非常可爱的小姑娘";
console.log(reg2.test(str));

11.4 正则 - 中括号

// 表示有一系列字符可供选择,只要匹配其中一个就可以了[多选1] - [abcdefg]
var reg1 = /xyz/;
var str1 = "weweewrx0234afa";
console.log(reg1.test(str1)); // false

var reg1 = /[xyz]/;
console.log(reg1.test(str1)); // true

// 例1: 
var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
console.log(rg.test('andy'));//true
console.log(rg.test('baby'));//true
console.log(rg.test('color'));//true
console.log(rg.test('red'));//false

var rg1 = /^[abc]/; // 只要以a/b/c开头的, 都为true
console.log(rg1.test('ab')); // true
console.log(rg1.test('a')); // true
console.log(rg1.test('bfff'));//true
console.log(rg1.test('csdfsdf'));//true
console.log(rg1.test('eabcf'));//false

var reg1 = /^[abc]$/; // 完整匹配其中的一个
console.log(reg1.test("abc")); // false
console.log(reg1.test("a")); // true
console.log(reg1.test("b")); // true
console.log(reg1.test("c")); // true
console.log(reg1.test("abc123abc")); // false

11.5 正则 - 量词符 - 基础使用

词符用来设定某个模式出现的次数

量词 解释
* 重复0次或更多次【>=0次】
+ 重复1次或更多次【>=1次】
? 至少重复0次或1次
{n} 连续重复至少n次
{n,m} 连续重复至少n到m次 (注意不能有空格中间)
// a出现0-多次
console.log(/a*/.test("hello"));

// b出现1-多次
console.log(/b+/.test("good bye"));

// c出现0或者1次
console.log(/c?/.test("chinese"));

// d出现0-2次
console.log(/d{0,2}/.test("brother"));

11.6 正则 - 量词符 - 配合其他使用

// 必须是数字的组合
var reg = /^[0123456789]+$/;
console.log(reg.test("1203123123"));

// 使用范围量词 - 改写
var reg = /^[0-9]+$/;
console.log(reg.test("1203123123"));

// 必须是纯英文(大小写都可以)
var reg = /^[a-zA-Z]+$/;
console.log(reg.test("asdfsadfsadafsdaf"));

// 判断身份证号 - 精准匹配
var reg = /^[0-9Xx]{15,18}$/;
console.log(reg.test("211002197702011"));

11.7 正则 - 预定义规则

// 预定义:正则预先定义好的一些字母规则

// 1. \d ----> [0-9]
var reg = /^\d*$/
console.log(reg.test("123")); // true

// 2. \D ----> [0-9]以外所有字符
var reg = /^\D*$/
console.log(reg.test("abc")); // true


// 3. \w ----> [a-zA-Z0-9_] (匹配包括下划线的任何单词字符, 不包括-字符)
var reg = /^\w+$/
console.log(reg.test("home_33hh")); // true

// 4. \W ----> [a-zA-Z0-9_]以外的字符
var reg = /^\W+$/
console.log(reg.test("你好")); // true


// 5. \s ----> 特殊字符:空格, 回车符, 换行符, 制表符(tab键) 
var reg = /^\s*$/;
console.log(reg.test(` 

`)); // true  (注意这里故意打的回车)


// 6. \S ----> 除了以上字符以外, 都可以
var reg = /^\S*$/;
console.log(reg.test("你好,我在北京顺义;"));

11.8 正则

// 其他的预定义规则:
// 1. 中文: 
// \u4e00 是 一
// \u9fa5 是 龥
// \xxxxx 是unicode编码,正好是中文编码的开始和结束的两个字
console.log(/^[\u4e00-\u9fa5]{2,8}$/.test("会飞的企鹅"));

// 2. 密码, 必须是6到16位数字或大小写字母或中划线或下划线组成
var reg = /^[a-zA-Z0-9_-]{6,16}$/;

// 3. 手机号
var reg = /^1[345678]\d{9}$/;
// ^ $ 从头到尾精确模式
// 必须1开头
// 第二部分 [345678] :这6个数字中选出一个!
// 剩余部分:\d{9}  出现9次
console.log(reg.test("13411112222"));

// 4. QQ验证 - 开头是1到9, 最少5位
var reg = /^[1-9]\d{4}$/;
console.log(reg.test(45671));

11.9 正则 - 与replace

// 字符串的replace方法中, 可以传入正则表达式
var str = "老师讲课真有激情, 学习千万不能学成书呆子, 这个人有点gay, GaY";
console.log(/(激情|呆子|gay)+/.test(str));
// 屏蔽傻, 激情等词汇
console.log(str.replace(/(激情|呆子|gay)+/, "*"));

// 引出ig的使用
// 正则后, 加g 全局替换(都替换), 加i 忽略大小写
console.log(str.replace(/(激情|呆子|gay)+/gi, "*"));

正则 - 特点

  1. 灵活性、逻辑性和功能性非常的强

  2. 可以迅速地用极简单的方式达到字符串的复杂控制

  3. 不太好理解规则, 犹如火星文一样

  4. 实际开发,一般都是直接复制写好的正则表达式. 但是要求会使用正则表达式并且根据实际情况修改正则表达式

11.10 - 案例 - 验证码发送

// 需求: 输入手机号, 监测是否符合正则判断表达式, 符合后才可以点击发送验证码, 然后做倒计时效果
// 思路: 监测手机号输入框的改变, 影响发送验证码按钮的状态, 给发送验证码按钮绑定点击事件, 做验证码倒计时效果

var thePhone = document.getElementById("phone");
var getCodeBtn = document.getElementById("getCode");

thePhone.oninput = function(){
     
    if(/^1[345678]\d{9}$/.test(this.value)){
     
        getCodeBtn.disabled = false;
    } else {
     
        getCodeBtn.disabled = true;
    }
}

getCodeBtn.onclick = function () {
     
    getCodeBtn.disabled = true;
    thePhone.disabled = true;
    var i = 3;
    getCodeBtn.value = '验证码已发送' + i;

    var timer = window.setInterval(function () {
     
        i--;
        getCodeBtn.value = '验证码已发送' + i;
        if (i === 0) {
     
            window.clearInterval(timer);
            getCodeBtn.value = '获取验证码';
            thePhone.disabled = false;
            getCodeBtn.disabled = false;
        }
    }, 1000);
}

实际开发时 - 可以百度找到正则表达式 - 直接使用 (但是要学会读懂和修改)

面试题

什么是闭包?(必会)

  • 问题: 什么是闭包

  • 答案: 内层函数引用外层函数中声明的变量就会形成闭包

  • code:

    function fn(){
           
        var a = 100;
        function closure(){
           
            console.log(a); // 引用了外层声明的函数
        }
        closure();
    }
    fn();
    
  • 图示:

    前端与移动开发----JS高级----函数全解,深拷贝浅拷贝,正则表达式_第2张图片

什么是内存泄漏(必会)

  • 问题: 什么是内存泄漏

  • 答案: 内存泄漏指任何对象在您不再拥有或需要它之后仍然存在

  • code:

    var seconds = 5;
    var t = setInterval(function(){
           
        if (--seconds == 0) {
           
            div.innerHTML = "http://www.itheima.com";
        }
    }, 1000);
    // 虽然内容改了, 但是计时器还在内存中运行, 就会造成内存泄露
    

哪些操作会造成内存泄漏?(必会)

  • 问题: 哪些操作会造成内存泄漏

  • 答案:

    第一句: 垃圾回收器对变量和对象引用计数, 为0时回收此变量内存

    第二句: 意外的全局变量, 会导致内存泄露

    第三局: 定时器或计时器或事件监听, 不销毁会导致内存泄露

    第四局: 闭包, 控制台打印, 互相引用(在两个对象彼此引用且彼此保留时)

  • code:

    var t = setInterval(function () {
           
        var f = setInterval(function(){
           
            console.log("你好");
        }, 1000);
    }, 1000);
    // 每隔1秒都创建了1个计时器 - 而且内部f计时器从未销毁过
    
  • 图示: (蓝色表示分配的内存)

    前端与移动开发----JS高级----函数全解,深拷贝浅拷贝,正则表达式_第3张图片

Call和apply,bind的区别

  • 问题: call和apply,bind的区别

  • 答案:

    第一句: call, apply, bind目的修改函数this的值

    第二句: call支持若干参数列表, apply方法接受包含多个参数的数组

    第三句: call和apply会马上调用函数执行, 而bind创建一个新的函数并返回, 需要手动调用执行

  • code

    // 1. call方法
    function Person(tName, tAge){
           
        this.name = tName;
        this.age = tAge;
    }
    Person.prototype.say = function(){
           console.log("人类会说话");};
    
    var per1 = {
           };
    Person.call(per1, "小黑", 20);
    console.log(per1);
    
    // 2. apply方法
    function Person(tName, tAge){
           
        this.name = tName;
        this.age = tAge;
    }
    Person.prototype.say = function(){
           console.log("人类会说话");};
    
    var per2 = {
           };
    Person.apply(per2, ["小黑", 20]);
    console.log(per2); 
    
    // 3. bind方法
    function Person(tName, tAge){
           
        this.name = tName;
        this.age = tAge;
    }
    Person.prototype.say = function(){
           console.log("人类会说话");};
    
    var per3 = {
           };
    var newThisFn = Person.bind(per3, "小黑", 20);
    newThisFn();
    console.log(per3);
    

笔试题

// 回答下面打印的结果是多少
function test(){
     
    var arr = [];
    for (var i = 0; i < 10; i++) {
     
        arr[i] = function(){
     
            console.log(i);
        }
    }
    return arr;
}
var myArr = test();
for (var j = 0; j < 10; j++) {
     
    myArr[j]();
}
// 提示: i变量虽然释放不掉, 但是i可以被修改 (注意执行的时机和顺序)
// 解释: test只执行一次, var i 是test作用域下的一个变量 (然后一直被for修改), 最终i的值是10, 所以下面调用arr[i]后面函数体执行时, 根据作用域链向test作用域下找到i访问, 所以都是10

知识点表格

函数全集

函数类型 语法 例子 特点 this指向
函数声明 function 函数名() {} function fn() {} 有变量提升 调用者(window)
函数表达式 var 变量名 = function(){} var fn = function(){} 没有变量提升 调用者(window)
对象里函数 {键: function() {}} {fn: function(){}) 调用者(对象)
事件处理函数 1. 事件源.事件类型 = function(){}
2. 事件源.addEventListener(“事件 类型”, function(){})
1. btn.onclilck= function(){}
2. btn.addEventListener(“click”, function(){})
调用者(事件源)
定时器 setTimeout(function(){}) setTimeout(function(){}) 调用者(window)
计时器 setInterval(function(){}) setInterval(function(){}) 调用者(window)
构造函数 function 大写开头名() {} function Person(){} 配合new使用 new的新对象
匿名函数 function (){} function (){} 一般配合别人使用 调用者(得具体看在哪里用)
立即执行函数 (function(){}) (); (function(){}) (); 结尾必须写分号 调用者(window)
递归函数 function 函数名(){ 函数名() } 函数名() function myFn() { myFn() } myFn() 留好出口 调用者(window)
高阶函数 1. function 函数名(形参A) {}
2. function 函数名() { return 函数体}
参数/返回值是函数体的 - 就是高阶函数 调用者(window)
回调函数 function a (b) { b() }
a(function(){});
1. 调用函数A, 传入函数体B
2. 在A中会回调函数体B执行
调用者(window)

正则全集

元字符 示例 解释 备注
具体字符 /abc/ 匹配连续出现的abc 至少出现过abc
^ /^abc/ 以具体连续字符开头 至少开头是abc
$ /def$/ 以具体连续字符结尾 至少结尾是def
/^xxx$/ 精准匹配 所有字符都需要满足xxx位置条件
| /我|你/ 或者关系 至少出现过其中一个一次
[] /[xyz]/ 一组或者关系 至少匹配所包含的任意一个字符
* /a*/ 0次 - 多次 可有可无
+ /a+/ 1次 - 多次 至少出现1次
? /a?/ 0次 - 1次 至少0-1次 相当于{0,1} 只要连续出现过0或1次
{n,m} /e{2,3}/ 前面表达式至少2-3次 连续出现2-3次
- /0-9/ 连字符(前提连续的) 匹配这个范围内的任意字符
\d /\d/ 匹配数字, 相当于[0-9] 匹配数字
\w /\w/ 匹配字符, 相当于[a-zA-Z0-9_] 匹配字符
\s /\s/ 匹配特殊字符[\t \r \n \v \f] 匹配特殊字符
[\u4e00-\u9fa5] [\u4e00-\u9fa5] 这是第一个文字和最后一个文字的Unicode编码 匹配所有中文任意一个
g //g 全局匹配 全文匹配
i //i 忽略大小写 大小写都可以匹配
身份证匹配: (1518位的)
var idCardReg = /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx]$)/
QQ号码:  (5-10)
var qqReg = /^[1-9][0-9]{4,9}$/
手机号码: 
var phoneReg = /^((134\d{4})|((13[0-3|5-9]|14[1|5-9]|15[0-9]|16[2|5|6|7]|17[0-8]|18[0-9]|19[0-2|5-9])\d{8}))$/

深拷贝浅拷贝

  • 拷贝: 新建个数组 / 对象, 把东西拷贝到一个新对象/数组里
  • 浅拷贝: 只是变量之间的复制 - 复制内存地址 (2个变量指向的是同一个数组/对象) - 互相 影响 (只拷贝第一层)

如有不足,请多指教,
未完待续,持续更新!
大家一起进步!

你可能感兴趣的:(前端与移动开发学习笔记,JS高级,正则表达式,js,regex,javascript,前端)