Web入门----Javascript对象学习

实例对象与new命令

对象是什么
  1. 对象是单个实物的抽象。
  2. 对象是一个容器,封装了对象状态(属性)与行为(方法)

构造函数

javascript语言体系不是基于,而是基于构造函数(constructor)与原型链(prototype)的。

javascript语言使用构造函数作为对象模板。所谓“构造函数”,就是专门用来生成实例对象的函数

。他就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。


构造函数特征

​ 构造函数依然属于函数的范畴,但为了与普通函数区别,构造函数的函数名字第一个名字用大写。

// 第一种写法
var Animal = function(){
    
}

// 第二种写法
function Animal(){
    
}

构造函数的特点
  • 函数体内部使用了this关键字,代表了所要生成的实例对象;
  • 调用构造函数生成实例对象使用new关键字。

New命令
`new`命令的作用,就是调用构造函数,并且返回一个实例对象。
构造函数不使用new调用
如果构造函数不被`new`关键字调用,将会作为普通函数执行。最终的结果是不会返回实例对象,并且构造函数内部的`this`指向了全局对象。
    <script>
      // 这是一个构造函数;
      function Animal(name) {
        this.name = name;
        /**
         * 通过new关键字构造的this为普通对象;
         * 没有通过new关键字调用的this指定了window对象;
         */
        console.log("Constructor:" + this);
      }

      // 通过new关键字调用构造函数;
      var dog = new Animal("dog");
      // 没有通过new关键字调用构造函数;
      var cat = Animal("cat");

      // 通过构造函数创建的对象属性是正常的;
      console.log("dog.name=" + dog.name);
      // 没有通过new关键字调用构造函数,不会返回对象,所以这里会报异常;
      console.log("cat.name=" + cat.name);
      // 通过
    </script>

在这里插入图片描述


避免构造函数没有被new调用
如果构造函数不是被`new`  关键字调用,则会出现很多问题,所有,在程序中应该尽量避免该情况的发生。则可以使用的方法有如下几种

1. 使用严格模式,在严格模式下,如果构造函数不是被`new`关键字调用,会出发错误;
2. 在构造函数内部通过`instanceof`判断`this`的类型,如果不是构造函数类型,手动调用构造函数。
3. 在构造函数内部通过'new.target'属性是否为空判断是否为构造函数类型,如果不是,则手动调用构造函数。

    <script>
      // 方法1:
      function Animal(name) {
        // 在严格模式下,如果构造函数没有被new关键字调用,会出发错误。
        "use strict";
        this.name = name;
        console.log("Constructor:" + this);
      }
      // 方法2:
       function Animal(name){
           //如果this的类型为Animal,则代表是通过new调用的构造函数;
       	 if(!this instanceof Animal){
         	   return new Animal(name);
     	   }
    	   this.name = name;
       }

       //  方法3:
       function Animal(name){
           //  如果构造函数不是由new关键字调用,则new.target属性为空
           if(!new.target){
                return new Animal(name);
           }
           this.name;
       }
      var dog = new Animal("dog");
      var cat = Animal("cat");
    </script>



基本用法
function Animal(name){
   if(!new.target){
       return new Animal(name);
   }
   this.name;
}

// 这里不加new关键字,也可以调用构造函数,是因为我们在构造函数手动通过new关键字调用构造函数。
Animal('dog').name;
(new Animal('dog')).name;


命令原理
使用new关键字后,函数内部执行流程
  1. 创建一个空对象,作为将要返回的对象实例;
  2. 将这个空对象的原型,指向构造函数的propotype属性;
  3. 将这个空对象赋值给函数内部的this关键字;
  4. 开始执行构造函数内部的代码。

构造函数使用return语句
在默认情况下,构造函数会返回一个模板实例,也就是`this`对象。但是,如果在构造函数内部使用`return`语句,并且`return`后面跟着一个对象,则使用`new`关键字调用构造函数会返回该特定的对象;如果内部的`return`后面跟着非对象,则构造函数依然后返回`this`对象;

​ 正常情况下,调用情况

    <script>
      function Animal(name) {
        this.name = name;
        console.log("this is :" + this);
      }

      var dog = new Animal("dog");
      var cat = Animal("cat");

      //  通过new关键字调用构造函数,会返回实例对象;
      console.log("dog:" + dog);
      //  没有通过new关键字返回undefined;
      console.log("cat:" + cat);
    </script>

    <script>
      function Animal(name) {
        this.name = name;

        /**
         * 构造函数使用return语句,并且返回一个对象
         * 则返回的对象不是this,而是return后面的对象,
         * 如果return后面返回的不是对象,则依然返回函数内部的this;
         */
        return { name: "animal" };
      }

      // 这里返回的对象是{name:'animal';}
      var dog = new Animal("dog");
      console.log("dog.nanme:" + dog.name);
    </script>


new target
函数内部可以通过`new.target`属性来判断是否通过`new`关键字调用构造函数。如果是通过`new`关键字调用,则`new.target`属性不会空且为函数实例。

// 构造函数
function Animal(name){
    // 非new关键字调用构造函数,new.target为空
    if(!new.target || new.target !==Animal){
        return new Animal(name);
    }
    this.name = name;
}


Objec.create()创建实例对象
`javascript`是以构造函数与原型为模板的对象体系,通过构造函数可以创建对象实例。如果无法获取构造函数,则也可以通过对象实例构造同一模板的另外对象实例。通过对象的创建的新的对象实例,其模板是用于生成新的对象实例的实例对象,所以新的对象实例的属性与方法跟模板实例是一样的。
    <script>
      function Animal(name, age) {
        this.name = name;
        this.age = age;
        function eat() {}
      }

      // 这里返回的对象是{name:'animal';}
      var dog = new Animal("dog", 1);
      var cat = Object.create(dog);
      console.log("cat:" + name + ":" + age);
    </script>


this 关键字

含义

this返回一个对象;

this就是属性或者方法当前所在的对象;

this表示当前方法或者属性持有的对象。但是由于对象的属性可以赋值给另一个对象。所以,属性所持有的当前对象是可变的。所以this的指向是可变的。


    <script>
        // 定义了对象A
        var a = {
            name: "A",
            age: 12,
            sayName: function () {
                // 该方法会输出实例中的name属性;
                console.log("my name is " + this.name);
            }
        }

        // 定义对象B
        var b = {
            name: "B",
        }

        // 将a的sayName属性复制给b的sayName属性;
        b.sayName = a.sayName;

        a.sayName(); // my name is A;
        b.sayName(); // my name is B;
    </script>

javascript语言之中,一切皆对象,运行环境也是对象,所以函数都是爱某个对象之中运行,this就是函数运行时所在的对象(环境)。javascript支持运行环境的动态切换,所以this的指向是动态的。


实质

this关键字的出现跟javascript的设计有很大关系。

javascrpt允许在函数体内部引用当前环境的其他变量,而this关键字的出现初衷就是用于表示函数所在的执行环境,也就是上下文。

​ 需要注意的是,在js语言中,函数跟对象的耦合性是非常低的,甚至于函数是可以脱离对象存在的。这点从js的数据存储上表现出来

/**
* let obj=0x00123456;
* obj 变量存储的是{}对象的内存地址。真正的对象数据是以字典形式存放的。
*/
let obj  = {
    name:'javascript',
    age:22,
    sayHello:function(){
        console.log("My name is "+this.name+",Hello everyone!");
    }
}

上面的代码定义了一个对象,该对象存在三个属性name,agesayHello。首先,我们需要明白该对象是以***字典形式存放在内存的某个区域***。大概的数据形式如下:

{
    name:{
      [[value]]:'javascript',
      [[writable]]:true,
      [[enumerable]]:true,
      [[cofigurable]]:true
    },
    age:{
      [[value]]:22,
      [[writable]]:true,
      [[enumerable]]:true,
      [[cofigurable]]:true 
    },
    sayHello{
      [[value]]:0x111111,
      [[writable]]:true,
      [[enumerable]]:true,
      [[cofigurable]]:true 
    }   
}

​ 从上面可以看出每个属性对应描述一个属性描述对象。一般原子类型属性的value值都是属性值。但是,引用类型如函数,他们的value存放的的是内存地址,在上面的对象中sayHello属性的value值对应的是一个内存地址。换句话说,sayHello作为一个函数,他被独立于该对象存储了。sayHello在函数内部引用上下文环境变量时,this指代对象自然会随着调用环境的变化做动态切换了。

总而言之,我们需要明白的一点就是***函数内部使用this指代的是函数的执行环境,函数的上下文***,牢牢把握住这一点。



使用场合

全局环境

全局环境使用的this,它指代的是全局环境变量,顶级上下文***window***对象;

// true
this === window

// This is the [object Window]
(function(){
    console.log("This is the:"+this);
})();

构造函数

构造函数中的this指代的是实例对象。new的初始化过程包括下面四个步骤

  1. 构造一个空对象,并将对象作为构造函数返回的实例对象;
  2. 将该空对象的原型指向构造函数的的propotyep对象;
  3. 将该空对象赋值给this;
  4. 执行构造函数中的其他代码;

​ 发现在第三个步骤中,会将返回的实例赋值给this。所以,构造函数中的this自然指向了实例对象。

function Taxpayer(name){
    this.name = name;
}
var tp =new Taxpayer('cosin');

// cosin;
console.log(tp.name);

对象方法

如果对象中的方法里面包含`this`,`this`的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变`this`的指向。
    <script>
        // 定义对象,并且在eat实例方法中输入对象的name属性;
        var Person = {
            name: "Susan",
            eat: function () {
                console.log("My name is " + this.name + ",The French Onion soup is my favorite!");
            }
        }
        // 在window上下文中定义name属性;
        var name = "window";

        // 将Person的eat属性赋值给windowObj变量;
        var windowObj = Person.eat;
        // My name is window,The French Onion soup is my favorite!
        windowObj();
    </script>

涉及到嵌套类情况下,this只能指向当前所在对象对象,不能继承更上层

   <script>
        var student = {
            name: "aaa",
            age: 12,
            sayHello: function () {
                console.log("My name is " + name);
            },
            habit: {
                listHabit: function () {
                    console.log("My name is " + this.name + ", i am the" + this.age + " year old");
                }
            }
        }
        // My name is undefined, i am theundefined year old
        student.habit.listHabit();
    </script>

将对象的方法赋值给一个变量,this会指向全局环境

    <script>
        var People = function (name, age) {
            this.name = name;
            this.age = age;
            this.introduceSelf = function () {
                console.log("name:" + this.name + ",age:" + this.age);
            }
        }
        var name = "WindowContext";
        var age = "forever";
        // 赋值
        var f = new People("part", "short").introduceSelf;
        // windowContext ,forever;
        (f)();
    </script>

下面几种情况会将this指向提升为全局环境变量

// No1:
(obj.function =obj.function)();

// No2:
(false || obj.function)();

// No3:
(1,obj.function)();

​ 上面的三种情况,都会将对象实例中的属性从对象中提调出来,脱离对象,单独运行。

// No1:
(obj.function = function(){
    console.log(this);
})();
// No1 相当于
(function(){
    console.log(this);
})();

// No2:
(false || function(){
    console.log(this);
})();

// No3:
(1,function(){
    console.log(this);
})();



使用注意点
避免多层this

由于this的指向是不确定的,所以切勿在函数中包涵多层的this.如果确实需要嵌套多层的this可以在用一个变量指向上层的环境变量;

    <script>
        var obj = {
            f1: function () {
                console.log("f1:" + this);
                // 这里定义并执行了一个函数,并将函数结果赋值给f2;
                var f2 = (function () {
                    console.log("f2:" + this);
                })();
            }

        }
        // f1:[object:object];
        // f2:[object:window]
        obj.f1();
    </script>

以上代码相当于

<script>
  var temp = function(){
      console.log(this);
  }
  var obj={
      f1:function(){
          console.log(this);
          var f2 = temp();
      }
  }  
</script>

​ 解决办法

<script>
    var obj = {
        f1:function(){
            console.log(this);
            var that = this;
            var f2=(function(){
                consolog.log(that);
            })();
        }
    }
	// [object:object];
	// [object:object];
	obj.f1();
    
</script>

避免数组处理方法中的this

数组的mapforEach方法,允许提供一个函数作为参数。这个函数内部不应该使用this.如果确实与需要可以参考以下两种解决方法

  1. mapforEach方法外部用新变量替换this;
  2. this作为参数传入map,forEach方法中,固定上下文;

<script>
    var obj = {
        name: 'array',
        content: ['a1', 'a2', 'a3'],
        f1: function () {
            // 方法1:用一个变量固定替换this;
            // 方法2:在forEach()方法中将this作为参数传入,固定上下文;
            var that = this;
            this.content.forEach(function (item, index) {
                console.log("index:" + index + ",value:" + item + ",name:" + this.name);
            }, this);
        }
    }
    obj.f1();
</script>

避免回调函数中的this

回调函数中的this往往会改变指向,所以最好避免使用。

<body>
    <label for='name'>Name<input id="name" placeholder="请输入注册邮箱或账号" name="name" /></label>
</body>
<script>
    function showName() {
        console.log(this === Window);
    }
    var ele = document.getElementById('name');
    ele.onchange = showName;
</script>

​ 在上面例子中原本showName方法中的this指向window对象,之后该方法作为input元素的onchanged回调后,方法中的this则指向了input元素。


绑定this的方法
Function.prototype.call()

函数实例的call方法,可以指定函数内部this的指向,也就是函数执行时所在的作用域,然后在该作用域中调用该函数。

function.prototype.call()方法中的第一个参数是指代上下文对象,如果传入为空值(不传),null,undefined,则默认为window对象。

function.prototyp.call()从第二个参数开始为方法参数,如果缺少则按照undefined类型处理。

function.prototype.call()方法中的第一个参数如果为原子类型,则自动装箱。

<script>
    function fun(p1, p2, p3) {
        return this;
    }
    // ture
    console.log(fun(1, 2, 3) === window);

    var obj = {}
    // window
    console.log(fun.call());
    // Object;
    console.log(fun.call(obj, 1, 2, 3, 4));
    // Number;
    console.log(fun.call(1.2, 3, 4, 5));
    // window;
    console.log(fun.call(undefined, 1, 2, 3, 4, 5, 6));
    // window;
    console.log(fun.call(null, 1, 2, 3, 4, 5, 6));
</script>

可以通过call方法来调用原生方法。

<script>
    var obj = {

    };
    // false;
    console.log(obj.hasOwnProperty("toString"));

    obj.hasOwnProperty = function () {
        return true;
    }

    // false;
    console.log(obj.hasOwnProperty("toString"));

    // 判断是否原生方法,false;
    console.log(Object.hasOwnProperty(obj, "toString"));

</script>

Function.prototype.aply()

apply方法的作用跟call基本一样,唯一的不同是apply方法是通过数组来传递参数调用参数。

找出数组中最大数

<script>
    var array = [23, 2, 122, 2, 34, -1, 12, 222, 0, 112];
    var maxNum = Math.max.apply(null, array);
    console.log("max:" + maxNum);
</script>

将数组空元素置为undefine

<script>
   var array = [1, , 2, , 3, , 3, , 4, 5];
   // 正常的foreach会直接跳过空元素
   array.forEach(function (value, index) {
       console.log(index + ":" + value);
   })

   // 所有的空元素输出为undefined;
   var newArray = Array.apply(null, array);
   newArray.forEach(function (value, index) {
       console.log(index + ":" + value);
   });
</script>

转换类似数组对象

利用Array.slice方法可以将类似数组元素(如arguments)转换为数组;

<script>
    function print(value, index) {
        console.log(index + ":" + value);
    }
    let argument = { 0: "one", 1: "one", length: 3 };
    let trancelateArray = Array.prototype.slice.apply(argument);
    let newArray = Array.apply(null, trancelateArray);
    newArray.forEach(print);
</script>

绑定回调函数的对象

<script>
   // 定义函数
   function inputChanged() {
       // this === window;
       console.log(this);
   }
   // 定义另一个函数,该函数绑定了上个函数的上下文范围;
   function applyWindow() {
       inputChanged.apply(window);
   }
   // 将绑定函数作为input的回调函数;
   document.getElementById('name').onchange = applyWindow;
</script>


Function.propotype.bind()

bind方法将函数体内的this对象绑定到某一个对象,然后返回新函数。

<script>
    var Person = function () {
        this.name = "Chiness";
        this.sayName = function () {
            console.log("My name is " + this.name + ",nice to meet you !");
        };
    }

    let p = new Person();
    // name = chiness;
    p.sayName();


    let sayHello = p.sayName;
    // name = "";
    sayHello();
</script>

​ 例子中执行sayHello函数,name='',其原因是函数内部引用了this.name属性,但是此时的window上下文中不存在name属性。如果此时将sayName 函数与p对象绑定,则可以正常输出this.name属性;

<script>
    var Person = function () {
        this.name = "Chiness";
        this.sayName = function () {
            console.log("My name is " + this.name + ",nice to meet you !");
        };
    }

    let p = new Person();
    // name = chiness;
    p.sayName();


    let sayHello = p.sayName;
    // name = "";
    sayHello();

    // 方法1
    // My name is Chiness,nice to meet you !
    p.sayName.call(p);
    // 方法2
    // My name is Chiness,nice to meet you !
    p.sayName.apply(p);
    // 方法3
    // My name is Chiness,nice to meet you !
    sayHello = p.sayName.bind(p);
    sayHello();

</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值