1. 字面量创建数据和实例的方式创建数据

1. 实例的创建方式,通过 new 操作符调用当前类型的构造函数,会得到当前类型的一个实例

  • 创建引用数据类型:
  1. var ary2 = new Array(1, 2, 3, 4); // 用实例的方式创建一个数组
  2. ary2.push(5);
  3. var ary = [1, 2, 3, 4]; // 字面量创建数组
  4. ary.push(5);
  • 创建引用基本类型
  1. var str1 = new String('javascript');
  2. console.log(str1.toUpperCase());
  3. var str2 = 'javascript';
  4. console.log(str2.toUpperCase());
  5. console.log(typeof str1); // 'object'
  6. console.log(typeof str2); // 'string'

基本数据类型的只能通过字面量的方式创建,如果用实例的方式创建,这个实例将会是变成一个对象,而对象不是基本数据类型;

2. 字面量创建方式:

  • 创建引用数据类型:字面量创建的引用该数据类型和实例的方式创建的引用数据类型没有区别
  1. // 1. 对象:{}
  2. var obj = {};
  3. // 2. 数组:[]
  4. var ary = [1, 2, 4];
  5. // 3. 正则 /^\d$/
  6. var reg = /^\d$/;
  • 创建基本数据类型:
  1. var num = 1;
  2. var str = 'abc';
  3. var bool = true;
  4. var empty = null;
  5. var notDefined = undefined;
  6. var sym = Symbol('cbd');

new 调用构造函数和普通调用的区别

  1. function Teacher(name, age, subject, from) {
  2. this.name = name;
  3. this.mission = '传道受业解惑';
  4. this.age = age;
  5. this.subject = subject;
  6. this.from = from;
  7. this.teach = function () {
  8. console.log(`${name} 老师教 ${subject} 学科`);
  9. };
  10. // 上面这种通过 this.xxx = xxx 的方式直接向实例上添加的属性成为私有属性。
  11. var career = '前端'; // 这只是一个私有变量,不会和实例产生联系
  12. }
  13. // 普通函数执行:
  14. let t1 = Teacher('马宾', 18, 'js', '珠峰');
  15. console.log(t1); // undefined
  16. let t2 = new Teacher('姜文', 19, 'js', '珠峰');
  17. console.log(t2); // Teacher {naem:......}
  • 为啥会出现这种情况呢?这是因为 new 调用和普通调用有着本质的区别;

    函数的普通调用过程:

  1. 新开辟栈内存作为执行的作用域
  2. 形参赋值
  3. 私有作用域变量提升
  4. 代码从上到下执行
  5. 释放栈内存

new 调用时:

  1. 开辟栈内存
  2. 形参赋值
  3. 变量提升
  4. 隐式创建一个实例对象,并且把当前构造函数中的this指向这个实例对象。
  5. 执行函数体中的代码,当遇到 this.xxx = xxx 时就是在向实例对象上增加属性;
  6. 隐式返回实例对象
  7. 释放栈内存

显式设置返回值

返回基本数据类型:

  1. function Teacher(name, age, subject, from) {
  2. this.name = name;
  3. this.mission = '传道受业解惑';
  4. this.age = age;
  5. this.subject = subject;
  6. this.from = from;
  7. this.teach = function () {
  8. console.log(`${name} 老师教 ${subject} 学科`);
  9. };
  10. return 61; // 返回基本类型值
  11. }
  12. let t1 = new Teacher('马宾', 18, 'js', 'zf');
  13. console.log(t1); // Teacher {....}

返回引用数据类型

  1. function Teacher(name, age, subject, from) {
  2. this.name = name;
  3. this.mission = '传道受业解惑';
  4. this.age = age;
  5. this.subject = subject;
  6. this.from = from;
  7. this.teach = function () {
  8. console.log(`${name} 老师教 ${subject} 学科`);
  9. };
  10. return {haha: '哈哈'}
  11. }
  12. let t2 = new Teacher('姜文', 19, 'js', '珠峰');
  13. console.log(t2); // {haha: ...}

如果我们手动修改构造函数的返回值时:

  1. 如果 return 一个基本数据类型的值,没有任何影响,不会覆盖原有实例;
  2. 如果 return 引用数据类型,原有的实例就会被这个引用类型值覆盖

慎重修改构造函数的返回值

原型模式

  1. function Teacher(name, age, subject, from) {
  2. this.name = name;
  3. this.mission = '传道受业解惑';
  4. this.age = age;
  5. this.subject = subject;
  6. this.from = from;
  7. this.teach = function () {
  8. console.log(`${name} 老师教 ${subject} 学科`);
  9. };
  10. }
  11. // let t1 = new Teacher('mabin', 18, 'js', 'zf');
  12. // let t2 = new Teacher('mabin', 19, '架构', 'zf');
  13. //
  14. // t1.teach();
  15. // t2.teach();
  16. // console.log(t1.teach === t2.teach); // false 因为 t1 和 t2 是两个实例,而 teach 又是 t1 和 t2 的私有方法

但是数组的 push 方法呢?

  1. var ary1 = [1, 2, 3];
  2. var ary2 = [1, 3, 5];
  3. console.log(ary1.push === ary2.push); // true
  4. console.log(ary1);
  5. console.log(ary2);
  • 思考? 只要是数组就该能 push,因为这是数组类的功能。而老师是 Teacher 类的实例,只要是老师就能 teach,teach 是老师类的功能,不需要再每个实例上强调一遍。这是一个公有的特性,不该放在实例,而是应该放在一个公共的地方,以后只要是这个类的实例就有这个功能。放在哪里呢?

原型对象:

原型prototype:每一个函数(普通函数、构造函数【类】)都天生自带一个属性prototype(原型)。这个属性的值是一个对象,用来存储当前类型的共有的属性和方法。保存在原型上面的属性和方法称为公有属性或公有方法。

所以改造 Teacher 类型:

  1. function Teacher(name, age, subject, from) {
  2. this.name = name;
  3. this.mission = '传道受业解惑';
  4. this.age = age;
  5. this.subject = subject;
  6. this.from = from;
  7. }
  8. Teacher.prototype.teach = function () {
  9. console.log(`${this.name} 老师教 ${this.subject} 学科`);
  10. };
  11. console.log(Teacher.prototype);
  12. let t1 = new Teacher('mabin', 18, 'js', 'zf');
  13. let t2 = new Teacher('jiangwen', 19, '架构', 'zf');
  14. t1.teach();
  15. t2.teach();
  16. console.log(t1.teach === t2.teach); // true

如何检测属性是公有还是私有?

hasOwnProperty() 方法:检测某个属性是否是对象的私有属性,如果是私有属性,返回 true,否则返回 false;

  1. var r1 = t1.hasOwnProperty('name'); // true
  2. var r2 = t2.hasOwnProperty('teach'); // false

原型链

  1. function Teacher(name, age, subject, from) {
  2. this.name = name;
  3. this.mission = '传道受业解惑';
  4. this.age = age;
  5. this.subject = subject;
  6. this.from = from;
  7. }
  8. Teacher.prototype.teach = function () {
  9. console.log(`${this.name} 老师教 ${this.subject} 学科`);
  10. };
  11. console.log(Teacher.prototype);
  12. let t1 = new Teacher('mabin', 18, 'js', 'zf');
  13. let t2 = new Teacher('jiangwen', 19, '架构', 'zf');
  14. t1.teach();
  15. t2.teach();

思考? 我把这个方法写在了 Teacher 的原型上,那么 t1 和 t2 是怎么找到的呢?

  1. console.log(t1);
  2. console.log(t2);
  3. console.log(Teacher.prototype);

在私有属性中没有发现 teach 方法。我们发现这个 t1 和 t2 中有一个 proto** 属性,这个属性值里面有 teach 方法。那是怎么找过去呢?Teacher.prototype中也有一个 __proto__**,这是干嘛呢的?

原型链:对象的属性查找机制

每个实例都有一个属性 proto** 属性,它指向当前实例所属类的 prototype 对象。当我们访对象的一个的属性时,如果有,就使用私有属性,如果没有就通过实例 __proto**找到实例所属类的 prototype (原型)上查找,如果找到就使用 prototype 上的属性,如果还没找到,就通过 prototype 的 proto__ 继续向上查找,一直找到 Object 的 prototype 就停止查找。如果还没找到就返回 undefined。