JavaScript系列-4-函数进阶

本文详细探讨了JavaScript中的函数相关概念,包括作用域安全的构造函数、Function和Object的区别、浅拷贝与深拷贝实现、函数调用与this的丢失、以及闭包的使用。通过实例讲解了函数的创建、原型链、作用域链、变量提升等核心知识点,帮助开发者深入理解JavaScript的函数机制和作用域规则。
摘要由CSDN通过智能技术生成

本文作者:钟昕灵,叩丁狼高级讲师。原创文章,转载请注明出处。

作用域安全的构造函数

构造函数的调用方式存在下面两种:

​ 直接调用:普通函数

​ 使用new一起调用:创建对象

function Person(name, age) {
   
    this.name = name;
    this.age = age;
}
console.log(Person("zs", 10));//undefined
console.log(new Person("ls", 12));//初始化了name和age的Person对象

如果我们直接调用Person函数,因为函数默认的返回值为undefined,所以得到undefined结果。

如果使用new关键字来调用Person函数,此时在函数中会默认创建一个对象,并将该对象设置给this,然后将name和age封装到该对象中,最后返回该对象。所以得到的是一个封装好数据的对象。

所以,如果我们想要创建对象,这里必须使用new来调用。但是,在实际来发中,我们有可能会忘记使用new,而是直接调用该构造函数,此时会造成什么问题呢?

  1. 得不到想要的对象,这是最容易想到的

  2. 会存在作用域安全的问题,这里需要解释一下

    直接调用该函数,那么在函数内部中的this指向window

    如果我们在函数中需要修改当前创建对象(this)中的属性时,有可能会不知不觉的将window作用域下的某些变量给修改掉,导致数据错乱的问题。

var name = "今天天气不错";
function Person(name, age) {
   
    this.name = name;
    this.age = age;
}
Person("zs", 10);
console.log(name);//zs

此时,在Person函数中的this是指向window的,所以this.name访问到的是函数外面(全局作用域)中的name,并为其赋值为zs,所以,最终得到的name值为zs。

既然存在这样的问题,我们就得解决,那么思路应该是怎样的呢?

首先,造成上面问题的根本原因是程序员在使用的过程中可能会忘记new关键字,而导致作用域不安全的问题

所以,而当没有使用new关键字的时候,构造函数中的this关键字是指向window的

反过来,如果构造函数中的this指向window,说明没有使用new关键字,此时就有了下面的代码:

function Person(name, age) {
   
    if(this == window){
   
        throw "调用构造器需要使用new关键字";
    }else{
   
        this.name = name;
        this.age = age;
    }
}

在构造函数中判断this的指向即可解决忘记new关键字的问题。

但是在ES6中,这种方式存在一定的问题,此时的this不一定是指向window,原因我们后面再说。此时我们换种思路来解决。

如果使用new调用该构造函数,那么this指向的是什么呢?对,是当前构造函数创建的对象,所以根据类型判断也是可以的。

if(!(this instanceof Person)){
   
    throw "调用构造器需要使用new关键字";
}else{
   
    this.name = name;
    this.age = age;
}

上面这种方式是完全OK的,下面我们再给出一种方式,大家可以了解一下。

在ES6中,为new引入了一个target属性,如果没有使用new调用构造函数,那么在该构造函数中new.target为undefined,反之为当前的构造函数。

if(!new.target){
   
    throw "调用构造器需要使用new关键字";
}else{
   
    this.name = name;
    this.age = age;
}

以上解决了我们在使用构造函数创建对象的过程中可能存在的问题。如果出现了,我们也能够快速的解决。

Function和Object

前面我们学习了Function和Object,而且也学习了instanceof关键字的使用,下面来看几个例子,检验一下大家对前面所学知识点的掌握情况。

function Person() {
   
}
var p = new Person();
console.log(p instanceof Person);//①
Person.prototype = {
   };//修改Person的原型对象
console.log(p instanceof Person);//②

①处的打印结果相信大家都非常清楚,因为p对象是由Person构造函数创建出来的,所以Person构造函数的原型对象在p对象的原型链上,所以使用instanceof判断的结果为true。

②处的结果会受到上面修改Person原型对象的影响,修改之后Person的原型对象不在p的原型链中,所以结果返回false。

var f = new Function();
console.log(f instanceof Function);//①
console.log(f instanceof Object);//②
console.log(Function instanceof Object);//③
console.log(Object instanceof Function);//④

①:返回true

②:返回true

上面两个比较简单,这里就不再做说明了。

③:这个也比较简单,Object的原型对象是所有对象的原型链的终点,所以只要最后是Object,都应该返回true。

④:这个判断稍微有点难度,但是如果大家对于前面画过的原型链的图还熟悉的话,应该能够得到正确答案。

因为Function.prototype是Object这个函数对象的原型对象,所以这句话可以这样说了,Function的原型对象在Object的原型链上,所以该判断理应返回true。

总结:如果大家能比较快的得到上面每个练习的答案的话,说明大家对于instanceof和对象的原型链还是认识的比较透彻了,恭喜大家!

浅拷贝和深拷贝的实现

在开发中,我们会有这样的需求,就是将A对象中的属性或者是方法拷贝到B对象中,而这里的拷贝我们按照拷贝的深度分为浅拷贝和深拷贝,下面我们来分析一下:

var p1 = {
   
    name:"zs",
    age:10,
    favs:["H5","Java","C"],
    wife:{
   
        name:"lily",
        age:8
    }
}
var p2 = {
   };
for(var key in p1){
   
    p2[key] = p1[key];
}
console.log(p2);

上面的代码中,我们将p1对象中的属性拷贝给了p2对象,这种拷贝方式我们称之为浅拷贝,为什么呢?我们来画图说明。

叩丁狼教育.png

上面是p1对象的内存结构图,通过上面的拷贝操作得到的p2是什么结构呢?

叩丁狼教育.png

我们得到和0x11一模一样的一份数据,而p2就指向该内存区域的数据,然后在0x44中的favs和wife这两个属性仍然指向0x22和0x33这两块内存区域的数据,所以此时的拷贝只拷贝了对象中的第一层属性,称之为浅拷贝。

浅拷贝在使用的过程中存在数据共享的问题(如果修改p1中的favs或者wife中的数据,p2中的这两个属性也会跟着被修改),因为他们引用的是同一块内存区域的数据。这个问题的我们可以使用深拷贝来实现。

所谓深拷贝,就是将对象引用的对象,或者对象引用的对象的引用的对象,一次往下推,全部都拷贝,大家不共享任何数据。

所以要实现深拷贝,当我们发现属性对应的值是一个对象的时候,应该将该对象拷贝一份,然后赋值给当前属性。

function deepCopy(source,target) {
   
    for(var key in source){
   
        if(source.hasOwnProperty(key)){
   //只拷贝当前对象的属性
            if(typeof source[key] == "object"){
   //如果属性是引用类型的对象
                // //根据原属性的类型决定是数组还是普通对象
                target[key] = Array.isArray(source[key]) ? [] : {
   };
                deepCopy(source[key],target[key]);//递归调用,完成所有层次的拷贝
            }else{
   
                target[key] = source[key];
            }
        }
    }
}
deepCopy(p1,p2);

通过上面的深度拷贝得到的p2对象是和p1完全不同的两份数据,此时不再存在数据共享的问题。

叩丁狼教育.png

函数的调用和this的丢失

调用函数大家都非常熟悉了,这里再统一的复习总结一下,需要强调的是,使用不同的方式调用函数,函数内部的this指向存在不同

  1. 普通调用 fun() this指向调用函数的对象—window
  2. 对象调用 obj.fun() this指向调用函数的对象—obj
  3. 使用new关键字调用 new Fun() this指向函数内部创建的新对象
  4. call或者apply调用 this指向call或者apply方法的第一个参数

所以我们在调用函数的过程中需要时刻关注我们调用方式的不同对this的影响,如下面的案例中就发生了this的丢失问题。

<div 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值