False Begginer - 9.原型、原型链、闭包立即执行函数、插件开发

1,034 阅读6分钟

9.1 原型是什么?原型的作用是什么?

// 观察下列代码:

// 函数也是对象的一种形式
function Handphone(brand, color) {
	this.brand = brand;
  this.color = color;
  this.say = function() {
  	console.log('I say hello!')
  }
}
var h1 = new Handphone('Benz', 'blue'); // { brand:'Benz', color:'blue' };
var h2 = new Handphone('BMW', 'red'); // { brand:'BMW', color:'red' };
console.log(h1.say === h2.say); // false

1.在构造函数实例化对象的时候,实例化对象h1和h2是两个互不影响的两个实例对象,所以对象中的say函数也是不同的两个函数,在某种意义上来说,这属于代码的庸余。那我们有没有办法能够让实例化出来的对象都能够继承共同的方法呢?
  
//2. 此时原型就能够帮助我们解决上述问题

function Handphone(brand, color) {
	this.brand = brand;
  this.color = color;
  this.say = function(){
  	console.log('I say hello!');
  }
}
var h1 = new Handphone('Benz', 'blue'); 
var h2 = new Handphone('BMW', 'red');

// 我们能够发现通过构造函数的prototype属性能够访问到原型,而且原型还是对象的形式。
console.log(Handphone.prototype); // { constructor: Handphone(brand, color){} }

3. 那么对象可以设置属性和方法,那原型身为对象的形式,那我们也对它添加属性试试。

Handphone.prototype.rom = '64G';
Handphone.prototype.ram = '8G';
Handphone.prototype.screen = '18:9';
Handphone.prototype.system = 'Android';
Handphone.prototype.call = function() {
		console.log('I am calling somebody');
}

4. 通过打印,我们可以发现实例化对象可以访问到原型上的属性和方法,而且不同的实例化对象可以通过原型访问到共同的方法,实现属性和方法的继承效果。
console.log(h1.rom); // '64G'
console.log(h2.ram); // '8G'
console.log(h1.call === h2.call); // true

1. prototype(原型)是构造函数的属性,只不过prototype属性是个对象而已。构造函数.prototype的方式能够访问到原型,原型是构造函数构造出来的每个实例化对象的公共祖先。

2. 所有被该构造函数构造出来的对象都可以继承原型上的属性和方法。

3. 原型的作用:对于构造函数中的属性(配置项),通过this.brand = 'benz'这种方式设置静态属性和方法是庸余的,我们想是否能让这些静态属性被实例对象继承而来。所以通常方法是写在prototype原型上,而对于动态的属性的设置写在构造函数中,用来实例对象。

9.2 实例对象对原型的增删改查问题?原型的constructor属性?

9.2.1 实例对象对原型属性的增删改查都不生效,也就是说子类是不能修改祖先属性。

function Handphone(brand, color) {
	this.brand = brand;
  this.color = color;
}
Handphone.prototype.name = 'prototype';
var h1 = new Handphone('Benz, blue');
console.log(h1); // { brand:'Benz', color:'blue' };

h1.name = 'proto';
console.log(h1); // { brand:'Benz', color:'blue', name:'proto' };

h1.sex = 'male';
console.log(h1); // { brand:'Benz', color:'blue', name:'proto', sex:'male' };

delete h1.name;
console.log(h1); // { brand:'Benz', color:'blue', sex:'male'}

// 通过上面代码的观察,实例对象对原型原始值属性的增删改查都不生效,也就是说子类是不能修改祖先属性。

9.2.2 ### 原型的constructor属性

function Handphone(brand, color) {
	this.brand = brand;
  this.color = color;
}

console.log(Handphone.prototype); // { constructor:function Handphone(brand, color)};

console.log(Handphone.prototype.constructor); // function Handphone(brand, color){};

// constructor是属于Handphone.prototype(原型)的属性,原型的constructor属性是指向构造函数本身,但是可以更改constructor的指向,因为constructor是属性,所以可以任意更改赋值。

function Telephone() {};

Handphone.prototype = {
	constructor : Telephone
}

console.log(Handphone.prototype.constructor); // function Telephone(){}

1. constructor是属于Handphone.prototype(原型)的属性

2. constructor是属性,就可以任意更改属性值,改变constructor属性的指向。

10.3 构造函数,原型,实例化对象的关系和原理

function Handphone() {
  // 隐式
  // this = {
  	// brand:'Benz',
    // color:'blue',
  	// __proto__ : Handphone.prototype
  // }
	this.brand = 'Benz';
  this.color = 'blue';
  // 隐式
  // return this
}
Handphone.prototype.name = 'xiaomi';
var h1 = new Handphone();
console.log(h1.name); // xiaomi

1. 通过new关键字对构造函数Handphone进行实例化,new关键字在构造函数中隐式创建this空对象,改变this指向,将this对象返回到全局中。

2. 将实例化后的this对象赋值给变量h1,此时h1就被构造函数完成实例化。

3. 在构造函数实例化之后,隐式创建的this对象内部中默认有__proto__属性,里面存放着构造函数的prototype属性。

4. 实例化对象通过h1.name寻找name属性,但是对于实例化对象的h1来说,自身并没有name属性,所以去__proto__进行寻找,而__proto__又是实例化对象原型存放的容器,所以就会去原型中寻找name属性。

1. __proto__属性是实例化以后的结果,__proto__属于实例对象,如果不通过new对构造函数实例化,连this空对象都不存在,那么__proto__属性也不存在,所以是实例化对象之后的结果。

2. __proto__是每一个实例化对象存放原型的容器。

3. 实例对象中存放着__proto__属性,其中保存着构造函数的prototype属性(原型Handphone.prototype),所以原型是属于实例化对象的,不是属于构造函数的。

4. 实例化对象默认总有__proto__属性,保存着其构造函数的prototype属性。

10.4 更改__proto__属性和重写prototype属性的问题?

1. 更改实例对象的__proto__属性

// 观察下列代码

function Person(){}
Person.prototype.name = '张三';
var p1 = {
	name:'李四'
}
var person = new Person();
console.log(person); // {}

// 更改实例对象的__proto__属性
person.__proto__ = p1;
console.log(person.name); // '李四'

// 因为person.__proto__存放着实例对象的原型,本身原型也是对象,所以肯定可以更改。

2. 重写原型和对原型赋值的区别

// 原型赋值
function Car() {};
Car.prototype.name = 'Benz';
var car = new Car();
Car.prototype.name = 'Mazda';
console.log(car.name); // 'Mazda'


// 重写原型
function Car() {};
Car.prototype.name = 'Benz';
var car = new Car();
Car.prototype = {
	name:'Mazda'
}
console.log(car.name); // 'Benz';

// 为什么重写之后,相同的代码执行结果就不同了呢?内部到底发生了什么呢?

1. 首先new关键字在构造函数Car中隐式创建了this空对象,而this空对象在实例之后才具有__proto__属性,__proto__属性中存储着其构造函数的prototype属性也就是原型,此时重写Car.prototype,因为还没有实例化,所以改变的属性和方法都存储在constructor构造函数的构造器中,并没有实例化,所以重写之后并不影响实例化对象的__proto__属性,实例化对象中的__proto__属性存放的是实例化之后的Car.prototype原型。


2. 整体的流程:
function Car() {
	var this = {
  	__proto__:Car.prototype
  }
}
var car = new Car();

// 实例化之前:
Car.prototype.constructor ---> Car构造函数 --> Car.prototype --> Car.prototype原型 = { name:'Benz' };

// 实例化之后:
Car.prototype.constructor ---> Car构造函数本身进行属性值的初始化 --> Car.prototype原型完成赋值 = {
	name:'Benz'
}
var this = {
  __proto__:Car.prototype = {
  		name:'Benz'
  }
}

// 重写原型:
Car.prototype.constructor --> Car构造函数重新构造name='Mazda'属性,当你实例化的时候,Car构造函数才会通过Car.prototype属性给原型进行赋值设置属性 = { name : 'Mazda'};实例化之后实例对象中__proto__:Car.prototype存放的就是实例化之后的Car.prototype原型。
var this = {
  __proto__:Car.prototype = {
  		name:'Mazda'
  }
}
                                 

9.5 window和return的区别

function fn() {
	var a = 1;
  function plus() {
  	a++;
    console.log(a);
  };
  return plus;
}
var plus = fn(); // 此时plus作为闭包函数被返回出来,被全局变量plus接收,在全局环境下执行,那么相对于plus函数处于window对象中,那么我们以window.的形式可以实现上面的效果吗?
plus(); // 2
plus(); // 3
plus(); // 4


function fn() {
	var a = 1;
  function plus() {
  	a++;
    console.log(a);
  }
  window.plus = plus;
  // 隐式默认函数返回undefined
  // return undefined;
}
fn();
plus(); // 2
plus(); // 3
plus(); // 4

// 通过window.的形式也能实现相同的效果,但是不能立即执行,还需要通过调用的方式进行一次执行,那么结合JS的立即执行函数就衍生出JS插件的写法。

;(function(){
	function Test(){};
  window.Test = Test;
})()
var t1 = new Test(); // 即可使用插件

插件写法,任意传递两个数字,调用插件内部方法可进行加减乘除功能

;(function(){
	function Compute(firstNum, secondNum) {
  	this.firstNum = firstNum || 0;
    this.secondNum = secondNum || 0;
  };
  Compute.prototype = {
  	add:function() {
    	return this.firstNum + this.secondNum;
    },
    mul:function() {
    	return this.firstNum * this.secondNum;
    },
    sub:function() {
    	return this.firstNum - this.secondNum;
    },
    sur:function() {
    	return this.firstNum / this.secondNum;
    }
  }
  window.Compute = Compute;
})()

var compute = new Compute(1, 2);
console.log(compute.add());
console.log(compute.mul());
console.log(compute.sub());
console.log(compute.sur());