JavaScript高级(一)

261 阅读6分钟

深拷贝和浅拷贝

浅拷贝是将复制的内容指向内存地址,如果内存地址改变,浅拷贝出来的对象也会改变。

深拷贝是在计算机里面开辟了一块内存区域来存放复制对象。

浅拷贝只是一个引用,深拷贝才是真正的自己创建出了一个复制品

浅拷贝只是拷贝一层,深拷贝会将原型上面每一级的数据都会拷贝

值类型都是深拷贝,引用类型都是浅拷贝

 let arrA = [1, 2, 3, 4, 5];
let arrB = arrA;
arrB.push(6);
console.log(arrA);
// [1, 2, 3, 4, 5, 6]  A 同时发生了变化 

** 方法一:**...使用展开运算符 简单的深拷贝 用展开运算符...就可以,但嵌套的不行

let objA = { name: "zhangsan", age: 30, course: { chinese: 100 } };
let objB = {...objA};  // 不好用了吧
objB.name = "lisi";
objB.course.chinese = 89;

// {"name":"zhangsan","age":30,"course":{"chinese":89}}

 **方法二:**第二种应对嵌套的要深拷贝

JSON.parse(JSON.stringify(objA))

let objA = { name: "zhangsan", age: 30, course: { chinese: 100 } };
let objB = JSON.parse(JSON.stringify(objA));

objB.name = "lisi";
objB.course.chinese = 89;

// {"name":"zhangsan","age":30,"course":{"chinese":100}}

JSON.parse(JSON.stringify(objA)  但不够优雅比较暴力

**方法三:**手写深拷贝

function deepCopy(obj){
    //判断是否是简单数据类型,
    if(typeof obj == "object"){
        //复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for(let i in obj){
            result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : 

obj[i];
        }
    }else {
        //简单数据类型 直接 == 赋值
        var result = obj;
    }
    return result;
} 

闭包

闭包是指有权访问另一个函数作用域中的变量的函数

简单的闭包

function f1(){
        var n = 123;
        function f2(){    //f2是一个闭包
            alert(n)
        }    
        return f2;
    }

js中函数内部可以读取全局变量,函数外部不能读取函数内部的局部变量。

闭包不会被垃圾回收

f1是f2的父函数,f2被赋给了一个全局变量,f2始终存在内存中,f2的存在依赖 f1,因此f1也始终存在内存中,不会在调用结束后,被垃圾回收机制回收。

闭包的作用:当缓存用,封装变量

减少全局变量的使用,保证了内部变量的安全。同时外部函数也可以访问内部函数的变量。

闭包的缺点:

被引用的内部变量不能被销毁,增大了内存的消耗,使用不当易造成内存泄漏,解决办法可以在内部变量不使用时,吧外部的引用设置为null。

闭包就是函数间跨作用域访问,会导致性能损失。

原型和原型链

原型对象主要用来继承。

在JavaScript中,每当定义一个对象(函数)时候,对象中都会包含一些预定义的属性,其中函数对象的一个属性就是原型对象,prototype,

注意:普通对象没有prototype,但有__proto__属性。

 Object.prototype对象也有__proto__属性,但它比较特殊,为null

1.原型和原型链是JS实现继承的一种模型。

 2.原型链的形成是真正是靠__proto__ 而非prototype

a.prototype 是实例原型

var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

person的__proto__       Person是构造函数          函数才有.prototype 这就是实例原型  

每个原型都有一个 constructor 属性指向关联的构造函数 实例原型指向构造函 数

Object.getPrototypeOf(person) //这个方法获得对象的原型

Object.prototype.__proto__ === null // true 这是原型链的顶端

hasOwnProperty是判断一个对象是否包含自定义属性而不是原型链上的属性,是 JS中唯一一个查找属性,但不查找原型链的函数。

new的过程

理解一

1、创建新对象

 2、将构造函数的作用域赋给新对象,this指向这个新对象

3、执行构造函数中的代码 就是添加对象属性

 4、返回新对象

理解二

创建一个空对象,将它的引用赋给 this,继承函数的原型。

 通过 this 将属性和方法添加至这个对象 

最后返回 this 指向的新对象,也就是实例(如果没有手动返回其他的对象) 

理解三

 创建空对象 

将this指向新对象,构造函数的作用域也给新对象,继承函数的原型 

通过this 将属性和方法添加到这个对象上, 返回新对象。

事件冒泡和事件委托

事件冒泡

冒泡,当事件发生后,事件就要开始从里往外传播

事件源本身无法处理事件就往上传播交给外层来处理

比如点击 button ,按钮本事无法处理点击事件,就交给绑定在按钮上面的 click事件来处理。

例如,父子div,都加了点击事件,点击div子 父也会被点击。

传递的只是引起触发,并没有把子的事件函数传给父,所以父没有事件,就没什 么效果。

阻止事件冒泡的方法:

e.stopPropagation();
event.stopPropagation();
ev.cancelBubble=true; 

扩展:取消浏览器默认行为

JavaScript:;

事件委托

比如每个子元素都要执行同一个事件 ,就可以把事件绑定在它的父元素上,这 样就减少内存消耗了,这就用了事件委托。

或者使用ajax动态增加子标签,需要给每个子标签绑定事件时,用事件委托,新 增的也会自动增加事件。

作用:减少内存消耗

this问题

this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this 到底指向谁,实际上this的最终指向的是那个调用它的对象。

**1. 全局作用域或者普通函数中 this 指向全局对象 window。 **

**2. 方法调用中谁调用 this 指向谁 **

**3. 在构造函数或者构造函数原型对象中 this 指向构造函数的实例 **

4. 箭头函数中指向外层作用域的 this

this永远指向的是最后调用它的对象

new关键字可以改变this的指向

如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是 一个对象那么this还是指向函数的实例。

还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因 为null比较特殊。

箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对 象,导致内部的this就是外层代码块的this。箭头函数的this是继承过来的

 箭头函数指向定义它的对象,因为他是继承过来的

call、apply、bind用法及区别

call、apply、bind都可以改变this的指向

call传参数,apply传数组,bind都可以,不过传数组,一个数组都只会被当成 一个参数。

bind 不会立即执行,要加()才会执行。

bind 除了返回是函数以外,它 的参数和 call 一样。

类型转换

 通过String(),Number(),Boolean()函数强制转换。

隐式转换通常发生在运算符加减乘除,等于,还有小于,大于等 条件判断。

null和undefined的区别

Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量 的默认值为undefined。

Null类型也只有一个值,即null。null用来表示尚未存在的对象,常用来表示函 数企图返回一个不存在的对象。

null是一个对象 typeof返回的是一个对象,但undefined返回的是一个值。