实例对象与new命令
对象是什么
- 对象是单个实物的抽象。
- 对象是一个容器,封装了对象状态(属性)与行为(方法)
构造函数
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关键字后,函数内部执行流程
- 创建一个空对象,作为将要返回的对象实例;
- 将这个空对象的原型,指向构造函数的
propotype
属性;- 将这个空对象赋值给函数内部的
this
关键字;- 开始执行构造函数内部的代码。
构造函数使用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
,age
和sayHello
。首先,我们需要明白该对象是以***字典形式存放在内存的某个区域***。大概的数据形式如下:{ 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
的初始化过程包括下面四个步骤
- 构造一个空对象,并将对象作为构造函数返回的实例对象;
- 将该空对象的原型指向构造函数的的
propotyep
对象;- 将该空对象赋值给
this
;- 执行构造函数中的其他代码;
发现在第三个步骤中,会将返回的实例赋值给
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
数组的
map
和forEach
方法,允许提供一个函数作为参数。这个函数内部不应该使用this
.如果确实与需要可以参考以下两种解决方法
- 在
map
和forEach
方法外部用新变量替换this
;- 将
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>