在这里插入代码片
## 1. 构造函数和new命令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
构造函数:
1. 典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念。
所谓“类”就是对象的模板,对象就是“类”的实例。但是,JavaScript 语言的对象体系,
不是基于“类”的,```而是基于构造函数(constructor)和原型链(prototype)```。
2. ```JavaScript 语言使用构造函数(constructor)作为对象的模板。```
所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,
描述实例对象的基本结构。```一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。```
3. 构造函数就是一个普通的函数,但是有自己的特征和用法。
4. ```为了与普通函数区别,构造函数名字的第一个字母通常大写。```
5.构造函数的特点有两个:
(1) 函数体内部使用了this关键字,代表了所要生成的对象实例;
(2) 生成对象的时候,必须使用new命令;
6. new命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。
```下面两行代码是等价的,但是为了表示这里是函数调用,推荐使用括号。```
var v = new Vehicle(); // 推荐的写法
var v = new Vehicle; // 不推荐的写法
-->
</head>
<body>
<script>
/*
1. 如果忘了使用new命令,直接调用构造函数会发生什么事???
这种情况下,构造函数就变成了普通函数,并不会生成实例对象;
this这时代表全局对象,将造成一些意想不到的结果;
因此,应该非常小心,避免不使用new命令、直接调用构造函数;
2. 为了保证构造函数必须与new命令一起使用:
(1) 构造函数内部使用严格模式,即第一行加上use strict, 这样的话,一旦忘了使用new命令,直接调用构造函数就会报错;
(由于严格模式中,函数内部的this不能指向全局对象,默认等于undefined,导致不加new调用会报错;
JavaScript 不允许对undefined添加属性)
function test(foo, bar){
'use strict';
this._foo = foo;
this._bar = bar;
}
test(); // TypeError: Cannot set property '_foo' of undefined
(2) 另一个解决办法,构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象;
*/
var Vehicle = function () {
console.log(this);
if (!(this instanceof Vehicle)) {
return new Vehicle(); // new
}
this.price = 1000;
};
// var v = Vehicle(); // 执行2次log
new Vehicle(); // 执行1次log
</script>
</body>
</html>
2. new命令原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
使用new命令时,它后面的函数依次执行下面的步骤。
1. 创建一个空对象,作为将要返回的对象实例;
2. 将这个空对象的原型,指向构造函数的prototype属性;
3. 将这个空对象赋值给函数内部的this关键字;
4. 开始执行构造函数内部的代码;
也就是说,构造函数内部,this指的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。
构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。
-->
</head>
<body>
<script>
/*
1. 如果构造函数内部有return语句:
(1) return后面跟着一个对象,new命令会返回return语句指定的对象;
(2) 否则,就会不管return语句(将会被忽略),返回this对象;
2. 另一方面,如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象;
*/
// (1)构造函数Vehicle的return语句返回的是一个数值;
// 这时,new命令就会忽略这个return语句,返回“构造”后的this对象。
var Vehicle = function () {
this.price = 1999;
return 1000;
};
var obj = new Vehicle();
console.log((new Vehicle()) === 1000); // false
// (2)但是,如果return语句返回的是一个跟this无关的新对象,
// new命令会返回这个新对象,而不是this对象。这一点需要特别引起注意;
var Animal = function () {
this.animal = 'cat';
return {
name: 'Zhang'
};
}
var object = new Animal();
console.log(object); // {name: "Zhang"}
// (3) getMessage是一个普通函数,返回一个字符串;
// 对它使用new命令,会得到一个空对象。这是因为new命令总是返回一个对象,要么是实例对象,要么是return语句指定的对象;
// 本例中,return语句返回的是字符串,所以new命令将忽略该语句, 直接返回一个空对象;
function getMessage() {
return 'HelloWorld'; //将会被忽略
}
var msg = new getMessage();
console.log(typeof msg); //{} object
</script>
<script>
/*
3. 函数内部的new.target属性 :
(1) 函数内部可以使用new.target属性;
(2) 如果当前函数使用了new命令调用,new.target指向当前函数本身,否则为undefined;
(3) 使用这个属性,可以判断函数调用的时候,是否使用new命令;
*/
function test() {
console.log(new.target === test);
}
test(); // false
new test(); // true
function fun() {
if (!new.target) {
throw new Error('请使用new命令调用!');
}
}
fun();
</script>
<!-- Object.create() 创建实例对象 -->
<script>
/*
4. 构造函数作为模板,可以生成实例对象; 但是,有时拿不到构造函数,只能拿到一个现有的对象;
我们希望以这个现有的对象作为模板,生成新的实例对象; 这时就可以使用Object.create()方法;
该函数对于复杂类型(数组, 对象)实行浅拷贝;
*/
var person = {
name: 'ZhangSan',
age: 38,
sing: function () {
console.log('i like sing!');
},
books: {
firstBook: '三国演义',
secondeBook: '水壶传'
},
arr: [1, 2, 3, 4]
};
var _person = Object.create(person);
person.books.firstBook = '红楼梦';
person.arr = ['one', 'two', 'three', 'four'];
// ps: 对于复杂数据类型来说, 是浅拷贝
console.log(_person.books.firstBook); // 红楼梦
console.log(_person.arr); // ["one", "two", "three", "four"]
</script>
</body>
</html>
3. prototype-1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
构造函数中的 原型对象:
构造函数通过原型分配的函数时所有对象所共享的;
```JavaScript规定, 每一个构造函数都有一个prototype属性, 指向另一个对象;```
注意:prototype就是一个对象, 这个对象的所有属性和方法, 都会被构造函数所拥有;
```我们可以把那些不变的方法, 直接定义在prototype原型对象上面, 这样所有对象实例就都可以共享这些方法;```
prototype: 原型, 也称原型对象;
```原型对象上面存在着许许多多的方法;```
-->
<!--
```JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享; ```
```也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系; ```
```下面,先看怎么为对象指定原型。JavaScript 规定,每个函数都有一个prototype属性,指向一个对象; ```
-->
</head>
<body>
<script>
function Father(name, age) {
this.name = name;
this.age = age;
this.sing = function() {
console.log('sing a song!');
}
}
console.dir(Father);
var obj1 = new Father('Zhang', 13);
var obj2 = new Father('Li', 32);
console.log(obj1.name === obj2.name); // false
console.log(obj1.sing === obj2.sing); // false
// 在原型对象上添加方法
Father.prototype.say = function() {
console.log('I am father!');
}
console.log(obj1.say === obj2.say); // true
</script>
</body>
</html>
4. prototype-2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
大部分面向对象的编程语言,都是通过“类”(class)来实现对象的继承;
JavaScript 语言的继承则是通过“原型对象”(prototype);
-->
<!--
1. prototype: 每个函数都有一个prototype属性,指向一个对象;
a. 对于普通函数来说, 该属性基本无用;
b. 对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型;
c. ``````实例对象可以视作从原型对象(prototype)衍生出来的子对象``````;
-->
</head>
<body>
<script>
/*
1. 构造函数:
(1) JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板;
实例对象的属性和方法,可以定义在构造函数内部;
*/
function Cat(name, color) {
this.name = name;
this.color = color;
this.move = function () {
console.log('喵喵');
};
};
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
console.log(cat1.move === cat2.move); // false
/*
上面代码中,cat1和cat2是同一个构造函数的两个实例,它们都具有move方法;
由于move方法是生成在每个实例对象上面,所以两个实例就生成了两次;
也就是说,每新建一个实例,就会新建一个move方法;
这既没有必要,又浪费系统资源,因为所有move方法都是同样的行为,完全应该共享;
===> 这个问题的解决方法,就是 JavaScript 的原型对象(prototype);
*/
console.log('----------');
</script>
<script>
/*
2. prototype(原型对象) 属性的作用:
(1) JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享;
也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系;
(2) 下面,先看怎么为对象指定原型。JavaScript规定,每个函数都有一个prototype属性,指向一个对象;
function fun() {}
console.log(typeof fun.prototype); // 'object'
上面代码中,函数fun默认具有prototype属性,指向一个对象;
a. ```对于普通函数来说,该属性基本无用; ```
b. ```但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型; ```
*/
function Animal(name) {
this.name = name;
};
Animal.prototype.color = 'white';
Animal.prototype.player = 'ball';
Animal.prototype.show = function () {}
Animal.prototype.display = function () {}
var Cat1 = new Animal('大毛');
var Cat2 = new Animal('二毛');
console.log(Cat1.color); // 'white'
console.log(Cat2.color); // 'white'
console.dir(Animal);
console.dir(Cat1);
/*
上面代码中,构造函数Animal的prototype属性,就是实例对象cat1和cat2的原型对象;
原型对象上添加一个color属性,结果,实例对象都共享了该属性;
```原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上; ```
这是因为实例对象其实没有color属性,都是读取原型对象的color属性;
```也就是说,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处; ```
```当然如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法; ```
``````总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法; ``````
这也是它被称为原型对象的原因, ``````而实例对象可以视作从原型对象衍生出来的子对象``````;
*/
console.log('----------');
</script>
<script>
/*
3. constructor属性: prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数;
(1) function fun() {}
console.log(fun.prototype.constructor === fun); // true
(2) 作用:可以得知某个实例对象,到底是由哪个构造函数产生的;
*/
function Person() {}
var p1 = new Person();
console.log(p1);
console.dir(p1);
console.log(p1.constructor === Person); // true
console.log(p1.constructor === Person.prototype.constructor); // true
console.log(p1.hasOwnProperty(
'constructor')); // false (p1自身没有constructor属性, 该属性其实是读取原型链上面的Person.prototype.constructor属性;)
console.log(Person.prototype === p1.__proto__); // true
//
console.log('------------');
function callback() {};
var Call = new callback();
console.log(Call);
console.log(Call.constructor === callback); // true
console.log(Call.constructor === RegExp); // false
// 上面代码中,constructor属性确定了实例对象Call的构造函数是callback,而不是RegExp;
</script>
</body>
</html>
5. __proto__
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
1. 对象原型 __proto__:
对象都会有一个属性__proto__, 指向构造函数的原型对象(prototype), (首先明确: prototype是构造函数constructor的一个属性)
之所以我们对象可以使用构造函数prototype原型对象的属性和方法, 就是因为有对象原型__proto__的存在;
2. __proto__对象原型 和 prototype原型对象是等价的;
3. __proto__对象原型的意义就在于为对象的查找机制提供一个方向, 或者说一条路线, 但是它是一个非标准属性,
因此实际开发中, 不能使用这个属性, 它只是内部指向原型对象 prototype;
__proto__存在的目的就是为了让我们得到prototype对象; (个人)
__proto__用来标识实例对象所继承的原型;
4. 区别:
a. prototype: 是构造函数才有的属性; (存在于函数中)
b. __proto__:是每个对象都有的一个属性;
c. prototype 和 __proto__都是一个对象;
d. person.__proto__ === Person.prototype;
-->
</head>
<body>
<script>
function Father(name, age) {
this.name = name;
this.age = age;
this.sing = function () {
console.log('sing a song!');
}
}
Father.prototype.say = function () {
console.log('I am father!');
}
var father = new Father('Zhang', 21);
console.log(Father.prototype === father.__proto__); // true
// 打印一下三句话, 就可以看出了
console.log(father);
console.log(Father.prototype);
console.log(father.__proto__);
console.dir(Father);
// 为什么father1 可以访问say()函数呢 ???
// 方法的查找规则: 首先查看father对象自身有没有 say()方法, 如果有就调用;
// 如果没有say()方法, 那么因为__proto__的存在(father.__proto__ === Father.prototype), 就会去构造函数的原型对象prototype身上去查找say()方法; (console.dir(Father))
// 记住: __proto__ === prototype
father.say();
</script>
</body>
</html>
6. constructor属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
1. 原型对象(prototype) 和 对象原型(__proto__)里面都有一个constructor属性,
constructor 我们称为构造函数, 因为它指向构造函数本身;
2. constructor 主要用于记录该对象引用哪个构造函数, 它可以让原型对象重新指向原来的构造函数;
-->
</head>
<body>
<script>
function Father(name, age) {
this.name = name;
this.age = age;
this.sing = function () {
console.log('sing a song!');
}
}
/***********************************************************************************************/
/*
以下两句相当于是在原型对象身上添加了两个方法;
此时执行 ```打印```语句
前两句:里面存在constructor构造函数这一属性;
后两句:将会打印出构造函数Father;
*/
/*
Father.prototype.say = function () {
console.log('I am father!');
}
Father.prototype.dance = function () {
console.log('I am father-dance!');
}
*/
/***********************************************************************************************/
/*
以下话语相当于直接覆盖了原先的原型对象; (该话语相对于上面的话语更为清晰)
此时执行 ```打印```语句
前两句:里面将不存在constructor构造函数这一属性; (在不加constructor属性的情况之下)
后两句:将会打印出 ƒ Object() { [native code] };
```十分注意```: 如果我们是给原来的原型对象直接赋值对象了, 则此时必须手动的利用prototype.constructor属性指向原来的构造函数;
*/
Father.prototype = {
constructor: Father, // 手动指向原来的构造函数
say: function () {
console.log('I am father!');
},
dance: function () {
console.log('I am father-dance!');
}
}
/***********************************************************************************************/
// ```打印```
var father = new Father('Zhang', 21);
console.log(Father.prototype);
console.log(father.__proto__);
console.log(Father.prototype === father.__proto__); // true
console.log(Father.prototype.constructor);
console.log(father.__proto__.constructor);
console.log(Father.prototype.constructor === father.__proto__.constructor); // true
</script>
</body>
</html>
7. 原型链
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
1. JavaScript 规定,所有对象都有自己的原型对象(prototype);
一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型;
因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
2. 如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性;
也就是说,所有对象都继承了Object.prototype的属性;
这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的;
3. 那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是null;
null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null;
4. 读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找;
如果直到最顶层的Object.prototype还是找不到,则返回undefined;
如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding);
5. 注意,一级级向上,在整个原型链上寻找某个属性,对性能是有影响的;
所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链;
-->
</head>
<body>
<script>
function Star(name, age) {
this.name = name;
this.age = age;
}
Star.prototype.dance = function () {
console.log('dancing!');
}
var LDH = new Star('ldh', 24);
console.log(LDH);
//只要是对象,就有__proto__属性,指向原型对象
console.dir(Star); //构造函数
console.log(Star.prototype); //原型对象
console.log(Star.prototype.__proto__);
console.log(Star.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
</script>
</body>
</html>
8. JS成员查找机制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
1. 当访问一个对象的属性(包括方法)时, 首先查找这个对象自身有没有该属性;
2. 如果没有就查找它的原型对象(也就是student.__proto__指向的Student.prototype原型对象);
3. 如果还没有就查找原型对象的原型对象(Object的原型对象);
4. 以此类推一直找到Object为止(null);
5. ``````实例对象可以视作从原型对象(prototype)衍生出来的子对象``````;
/*
举例说明:
比如:person1.valueOf()
a. 浏览器首先检查,person1对象是否具有可用的valueOf()方法;
b. 如果没有,则浏览器检查person1对象的原型对象(即Person构造函数的prototype属性所指向的对象)是否具有可用的valueof()方法;
c. 如果也没有,则浏览器检查Person()构造函数的prototype属性所指向的对象的原型对象(即Object构造函数的prototype属性所指向的对象)是否具有可用的valueOf()方法;
d. 如果还是没有, 那么就是undefined的了;
*/
-->
</head>
<body>
<script>
function Father(name, age) {
this.name = name;
this.age = age;
}
var obj = new Father('XingWei', 23);
Object.prototype.sex = '女-Object';
Father.prototype.sex = '男-Father';
obj.sex = '中性-实例对象';
console.log(obj.sex);
console.log(obj.__proto__ === Father.prototype); // true
console.log(obj.__proto__.__proto__ === Object.prototype); // true
console.log(Father.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
console.log(obj);
console.log(Father.prototype.__proto__);
/*
解析:
0. 首先我们明确一点:我们是通过实例对象的__proto__属性去得到prototype的(两者等价);
1. 首先将在obj对象自身查找sex属性, 如果有就打印出来;
2. 否则查找obj对象的原型对象, 怎么查找?通过obj.__proto__属性,
```它指向(等价)Father.prototype```, 也就是在Father.prototype上查找sex, 如果有就打印出来;
3. 否则就查找原型(Father.prototype)的原型对象, 怎么查找?通过Father.prototype.__proto__属性(实际上是Object.prototype),
```它指向Object.prototype```, 此时就在Object.prototype上查找sex, 如果有就打印出来;
4. 否则继续查找原型(Object.prototype)的原型对象, 怎么查找?通过Object.prototype.__proto__属性(实际是null),
```实际上到第三步就停止了, 如果没有找到就是undefined了;
*/
/*
console.log(obj); // 打印obj实例对象
打印结果:
>Father {name: "XingWei", age: 23, sex: "中性-实例对象"}
name: 'XingWei'
age: 23
sex: 中性-实例对象
>__proto__ ```obj.__proto__ 实际上就是 obj对象的原型对象 Father.prototype ```
sex: '男-Father'
>constructor: f Father(name, age)
>__proto__ ```obj.__proto__.__proto__ 实际就是 Father.prototype.prototype ```
sex: '女-Object'
>constructor: f Object()
>valueOf: f valueOf()
>toString: f toString()
... ...
*/
</script>
</body>
</html>
9. 扩展内置对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--
可以通过原型对象,对原来的内置对象进行扩展自定义的方法,比如给数组增加自定义求偶数和的功能;
```定义在prototype原型对象上面的属性和方法, 可以被所有实例对象共享;```
-->
<script>
console.log(Array.prototype);
Array.prototype.sum = function () {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
var arr = [1, 2, 3];
console.log(arr.sum());
var Arr = new Array(11, 22, 33);
console.log(Arr.sum());
</script>
</body>
</html>
10. call方法的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
call():
1. 调用函数;
2. 修改函数运行时的this指向;
-->
</head>
<body>
<script>
function test() {
console.log('test');
}
function fun(x, y) {
console.log(this);
console.log(x+y);
}
//1. call()可以调用函数
test.call();
//2. 改变this指向
var obj = {
name: 'andy'
}
fun(1, 4); // this指向window
fun.call(obj, 1, 2); // this指向obj了
</script>
</body>
</html>
11. 构造函数的继承1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--
ES6之前并没有给我们提供extends继承机制,
但是我们可以通过 构造函数 + 原型对象 来模拟实现继承,称之为组合继承。
ES6, 2015.06发版。但是目前浏览器的JavaScript是ES5版本,大多数高版本的浏览器也支持ES6,
但是只是实现了ES6的部分特性和功能。
在ES6之前,对象不是基于类创建的,而是用一种称为构造函数的特殊函数来定义对象和它们的特征。
-->
<!--
在子类的构造函数中调用父类的构造函数; (call())```
-->
<script>
// 父构造函数
function Father(_name, _age) {
this.name = _name;
this.age = _age;
this.money = function () {
console.log('100$');
}
}
// 子构造函数
function Son(_name, _age, _score) {
Father.call(this, _name, _age); // 关键句 => 此时就可以调用父构造函数了
// 通过call, 修改了Father构造函数中的this指向, 使其指向son,
// 而后通过this.name, this.age, 也就相当于给Son添加了name, age, money属性了;
// 此时也就好比说实现了一种"继承", 子构造函数继承了父构造函数的系列属性
this.score = _score;
}
var son = new Son('XingWei', 23, 100);
console.log(son);
/*
console.log(son);
>Son
name: 'XingWei'
age: 23
money: f()
score: 100
>__proto__
constructor: f Son(_name, _age, _score)
__proto: ...
... ...
*/
</script>
</body>
</html>
12. 构造函数的继承2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
两步:
第1步: 在子类的构造函数中,调用父类的构造函数;
第2步: 让子类的原型指向父类的原型,这样子类就可以继承父类原型; (a 或 b都可以)
a. Son.prototype = new Father();
Son.prototype.constructor = Son;
b. Son.prototype = Object.Create(Father.prototype);
Son.prototype.constructor = Son;
c.
以上两种写法都是可以的, 但是子类会具有父类实例的方法。有时,这可能不是我们需要的,所以不推荐使用这种写法;
以上写法是子类是整体继承父类。有时只需要单个方法的继承,这时可以采用下面的写法;
Son.prototype.money = function() {
Father.prototype.money.call(this);
}
此种写法倒是更为简单易懂了, 就是在Son构造函数的原型之上添加了一个money方法, 然后就接下来就清晰了... ...;
-->
</head>
<body>
<script>
// 父构造函数
function Father(_name, _age) {
this.name = _name;
this.age = _age;
this.sing = function() {
console.log('father-sing');
}
}
Father.prototype.money = function () {
console.log('10000000元RMB');
}
// 子构造函数
function Son(_name, _age, _score) {
Father.call(this, _name, _age);
this.score = _score;
}
Son.prototype.exam = function () {
console.log('考试');
}
///
Son.prototype = new Father(); // son的原型对象指向father的实例对象, 此时就相当于继承了父亲的方法
Son.prototype.constructor = Son; // ***, 利用对象形式给原型对象赋值, 别忘了利用constructor指向原来的构造函数
var son = new Son('ldh', 13, 199);
son.sing(); // 调用父类的方法
son.money(); // 调用父亲的方法
console.dir(Son.prototype);
console.dir(Father.prototype);
console.log(son);
// console.log(Son.prototype.constructor); // 指向的是构造函数Father,需更改指向,见***
/*
形式一:
Son.prototype = new Father();
Son.prototype.constructor = Son;
=> 此时我们打印console.log(son);
>Son {name: "ldh", age: 13, score: 199}
name: 'ldh'
age: 13
sing: f()
score: 199
>__proto__
name: undefined
age: undefined
>constructor: f Son(_name, _age, _score)
>__proto__
>money: f()
constructor: f Father(_name, _age)
>__proto__
>constructor: f Object()
>toString: f toString()
>valueOf: f valueOf()
...
=> 可以明显的看出:实例对象son的原型son.__proto__是实例对象 new Father();
实例对象new Father()的__proto__是Father的原型Father.prototype;
接下来就是Object了;
*/
/*
形式二:
Son.prototype = Object.create(Father.prototype); // 返回的是一个基于Father.prototype创建的一个对象
Son.prototype.constructor = Son;
=> 此时我们再次打印console.log(son);
>Son {name: "ldh", age: 13, score: 199}
name: 'ldh'
age: 13
sing: f()
score: 199
>__proto__ ```__proto__就是Object.create(Father.prototype)返回的这个对象, 该对象的constructor属性被指定为Son, 既然是对象, 那么肯定有__proto__属性了```
>constructor: f Son(_name, _age, _score)
>__proto__ ```返回的这个对象的原型是Father.prototype (son.__proto__.__proto__ === Father.prototype `true`)```
>money: f()
>constructor: f Father(_name, _age)
>__proto__
>constructor: f Object()
>toString: f toString()
>valueOf: f valueOf()
*/
</script>
<script>
/*
constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错;
function Person(_name, _age) {
this.name = _name;
this.age = _age;
}
1. 坏的写法:
Person.prototype = {
method1: function() { }
// ...
};
2. 好的写法:
Person.prototype = {
constructor: Person,
method1: function() { }
// ...
};
3. 更好的写法:
Person.prototype.method1 = function() {
...
}
*/
</script>
</body>
</html>
13. 构造函数的继承3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
多重继承:
JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能;
下面代码中,子类S同时继承了父类M1和M2。这种模式又称为 Mixin(混入)
-->
</head>
<body>
<script>
// 父构造函数1
function Man() {
this.name1 = 'Zhang';
this.play = function() {
console.log('man like playing game!');
}
}
Man.prototype.run = function() {
console.log('man like running!');
}
// 父构造函数2
function Woman() {
this.name2 = 'Wang';
this.sing = function () {
console.log('woman like singing a song!');
}
}
Woman.prototype.dance = function() {
console.log('woman like dancing!');
}
// 子类
function Son() {
Man.call(this);
Woman.call(this);
}
// 继承Man
Son.prototype = Object.create(Man.prototype);
// 继承链上再加入Woman
Object.assign(Son.prototype, Woman.prototype); // 也即是将Woman.prototype对象的属性给拷贝到Son.prototype对象上
/*
Man.prototype:
>{run: ƒ, constructor: ƒ}
run: f()
constructor: f Man()
__proto__:
... ...
Object.assign(Man.prototype, Woman.prototype) 回车键
>{ fun: f, dance: f, constructor: f }
>run: f()
>dance: f()
>constructor: f Man()
>__proto__:
... ...
在控制台中直接调试打印即可;
*/
//
Son.prototype.constructor = Son;
var son = new Son();
son.play();
son.sing();
son.run();
son.dance();
console.log(son);
/*
console.log(son);
>Son {name1: "Zhang", name2: "Wang", play: ƒ, sing: ƒ}
name1: 'Zhang'
play: f()
name2: 'Wang'
sing: f()
>__proto__ ```son.__proto__是Son.prototype, 是Object.create(Man.prototype)返回的一个对象, 该对象上有dance(),constructor(),__proto__```
>dance: f()
>constructor: f Son()
>__proto__ ```son.__proto__.__proto__ === Man.prototype `true` ```
>run: f()
>constructor: f Man()
>__proto__
>constructor: f Object()
>toString: f toString()
>valueOf: f valueOf()
>isPrototypeOf: f isPrototypeOf()
... ...
*/
</script>
</body>
</html>
14. this指向
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--
总结一下,JavaScript语言之中,一切皆对象,运行环境也是对象;
所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境);
这本来并不会让用户糊涂,但是JavaScript支持运行环境动态切换;
也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象,这才是最让人感到困惑的地方;
-->
<!--
使用场合:
1.全局环境: 全局环境使用this,它指的就是顶层对象window;
2.构造函数: 构造函数中的this,指的是实例对象;
3.对象的方法: 如果对象的方法里面包含this,this的指向就是方法运行时所在的对象; 该方法赋值给另一个对象,就会改变this的指向;
-->
</head>
<body>
<script>
/*
1. 下面代码说明,不管是不是在函数内部,只要是在全局环境下运行,this就是指顶层对象window;
*/
console.log(this === window); // true
function fun() {
console.log(this === window); // true
}
fun();
console.log('----------');
</script>
<script>
/*
2. 如果this所在的方法不在对象的第一层,这时this只是指向当前一层的对象,而不会继承更上面的层;
(1) a.age.m方法在对象a的第二层,该方法内部的this不是指向对象a, 而是指向对象a.age, 而a.age并没有name属性, 那么自然是undefined了;
*/
var a = {
name: 'Hello',
age: {
m: function () {
console.log(this.name);
}
}
};
a.age.m(); // undefined
console.log('----------');
</script>
<script>
/*
3. 避免多层 this: 由于this的指向是不确定的,所以切勿在函数中包含多层的this;
*/
var ooo = {
name: 'Zhang',
f1: function () {
console.log(this); // this指向对象ooo
var f2 = function () {
console.log(this); // this指向顶层对象window
}(); // 注意,此处的f2指向的函数自动被调用了(多了一个"()")
}
};
ooo.f1();
/*
(1) 因此实际执行的是以下的代码:
var temp = function() {
console.log(this);
};
var ooo = {
f1: function() {
console.log(this);
var f2 = temp();
}
}
*/
/*
(2) 一个解决方法是在第二层改用一个指向外层this的变量;
事实上,使用一个变量固定this的值,然后内层函数调用这个变量,是非常常见的做法,请务必掌握;
*/
var oooo = {
name: 'Zhang',
f1: function () {
console.log(this);
var that = this;
var f2 = function () {
console.log(that);
}();
}
};
oooo.f1();
/*
(3) JavaScript 提供了严格模式,也可以硬性避免这种问题。严格模式下,如果函数内部的this指向顶层对象Window,就会报错;
*/
var counter = { count: 0 };
counter.inc = function () {
'use strict';
this.count++;
};
var ff = counter.inc;
ff(); // Uncaught TypeError: Cannot read property 'count' of undefined
</script>
<!-- test -->
<script>
var f = function () {
console.log('hello world');
}(); // 将立即调用该函数
</script>
</body>
</html>
15. Object对象的相关方法1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
/*
1. Object.getPrototypeOf(): 返回参数对象的原型;
2. 特殊对象的原型:
(1) 空对象的原型是 Object.prototype;
Object.getPrototypeOf({}) === Object.prototype //true
(2) Object.prototype 的原型是 null;
Object.getPrototypeOf(Object.prototype) === null //true
(3) 函数的原型是 Function.prototype;
Object.getPrototypeOf(function f() {}) === Function.prototype // true
*/
</script>
<script>
/*
3. Object.setPrototypeOf(): 为参数对象设置原型,返回该参数对象;
(1) 它接受两个参数,第一个是现有对象,第二个是原型对象;
(2) 下面代码中,Object.setPrototypeOf方法将对象a的原型设置为对象b,因此a可以共享b的属性;
*/
var a = {};
var b = {num: 101};
Object.setPrototypeOf(a, b); // 对象a的原型为对象b
Object.getPrototypeOf(a) === b; // true
console.log(a.num);
console.log('----------');
</script>
<script>
/*
4. Object.create(): 该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例对象完全继承原型对象的属性;
(1) 上面代码中,Object.create方法以A对象为原型,生成了B对象。B继承了A的所有属性和方法;
*/
// 原型对象
var A = {
print: function() {
console.log('hello');
}
};
// 实例对象
var B = Object.create(A);
console.log(Object.getPrototypeOf(B) === A); // true
B.print();
console.log(B.print === A.print); // true
console.log('----------');
</script>
<script>
/*
5.
(1) 如果想要生成一个不继承任何属性(比如没有toString和valueOf方法)的对象,可以将Object.create的参数设为null;
var Obj4 = Object.create(null);
console.log(Obj4);
(2) 使用Object.create方法的时候,必须提供对象原型,参数为空或参数不是对象时,会报错;
Object.create(); // ERROR
Object.create(123); // ERROR
(3) Object.create方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上;
var obj1 = {
name: 'Zhang'
};
var obj2 = Object.create(obj1);
obj1.sing = 'LaDing';
console.log(obj2);
*/
// 以下三种写法是等价的
var Obj1 = Object.create({});
var Obj2 = Object.create(Object.prototype);
var Obj3 = new Object();
console.log(Obj1);
console.log(Obj2);
console.log(Obj3);
console.log('----------');
</script>
<script>
/*
6. 除了对象的原型,Object.create方法还可以接受第二个参数;
该参数是一个属性描述对象,它所描述的对象属性,会添加到实例对象,作为该对象自身的属性;
*/
var Object = Object.create({}, {
name: {
value: 'Wang',
enumerable: true,
configurable: true,
writable: true
},
age: {
value: 88,
enumerable: true,
configurable: true,
writable: true
}
});
//以上等价于:
/* var Object = Object.create({});
Object.name = "Wang";
Object.age = 88; */
console.log(Object);
</script>
<script>
/*
7. Object.prototype.isPrototypeOf(): 用来判断该对象是否为参数对象的原型;
var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
o1.isPrototypeOf(o3) // true
o2.isPrototypeOf(o3) // true
Object.prototype.isPrototypeOf({}) // true
Object.prototype.isPrototypeOf([]) // true
Object.prototype.isPrototypeOf(/xyz/) // true
Object.prototype.isPrototypeOf(Object.create(null)) // false
上面代码中,由于Object.prototype处于原型链的最顶端,所以对各种实例都返回true,只有直接继承自null的对象除外;
*/
</script>
</body>
</html>
16. Object对象的相关方法2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
/*
1. Object.prototype.__proto__:
(1) 实例对象的__proto__属性(前后各两个下划线),返回该对象的原型。该属性可读写;
(2) 根据语言标准,__proto__属性只有浏览器才需要部署,其他环境可以没有这个属性;
它前后的两根下划线,表明它本质是一个内部属性,不应该对使用者暴露;
因此,应该尽量少用这个属性,而是用Object.getPrototypeof()和Object.setPrototypeOf(),进行原型对象的读写操作;
var obj = {};
var p = {};
obj.__proto__ = p;
Object.getPrototypeOf(obj) === p; // true
上面代码通过__proto__属性,将p对象设为obj对象的原型;
*/
var A = {
name: 'Zhang'
};
var B = {
name: 'Wang'
};
var proto = {
show: function() {
console.log(this.name);
}
};
A.__proto__ = proto;
B.__proto__ = proto;
A.show();
B.show();
console.log(A.show === B.show); // true
console.log(A.show === proto.show); // true
console.log(B.show === proto.show); // true
// 上面代码中,A对象和B对象的原型都是proto对象,它们都共享proto对象的print方法。
// 也就是说,A和B的print方法,都是在调用proto对象的print方法。
console.log('----------');
</script>
<script>
/*
3. 获取原型对象方法的比较:
(1) 如前所述,__proto__属性指向当前对象的原型对象,即构造函数的prototype属性;
var obj = new Object();
obj.__proto__ === Object.prototype; // true
obj.__proto__ === Object.constructor.prototype; // true
(2) 因此,获取实例对象obj的原型对象,有三种方法:
a. obj.__proto__;
b. obj.constructor.prototype;
c. Object.getPrototypeOf(obj);
*/
</script>
<script>
/*
4. Object.getOwnPropertyNames(): 返回一个数组,成员是参数对象本身的所有属性的键名(不管是否可以遍历),不包含继承的属性键名;
5. Object.prototype.hasOwnProperty():返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上;
*/
console.log(Object.getOwnPropertyNames(Date));
console.log(Date.hasOwnProperty('length')); // true, Date.length是Date自身的属性
console.log(Date.hasOwnProperty('toString')); // false, Date.toString是继承的属性
</script>
<script>
/*
6. in 运算符和 for…in 循环:
(1) in运算符返回一个布尔值,表示一个对象是否具有某个属性; 它不区分该属性是对象自身的属性,还是继承的属性;
console.log('length' in Date); // true
console.log('toString' in Date); // true
(2) 获得对象的所有可遍历属性(不管是自身的还是继承的),可以使用for...in循环;
下面代码中,对象oo2的age属性是自身的,name属性是继承的。这两个属性都会被for...in循环遍历;
为了在for...in循环中获得对象自身的属性,可以采用hasOwnProperty方法判断一下;
for(var name in object) {
if(object.hasOwnProperty(name)) {
// ... ...
}
}
*/
var oo1 = {
name: 'Zhang'
};
var oo2 = Object.create(oo1, {
age: {
value: 23,
enumerable: true,
configurable: true,
writable: true
}
});
for(key in oo2) {
console.log(key);
} // age, name
console.log('----------');
</script>
<script>
function Father(name, age) {
this.name = name;
this.age = age;
};
Father.prototype.show = function() {
console.log('Hello');
}
function Son() {
}
Son.prototype = new Father('zhang',101);
// console.dir(Father);
console.dir(Son);
</script>
</body>
</html>