十、原型、原型链、闭包和立即执行函数、插件开发初始
原型
什么是原型(prototype)?
无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性,指向原型对象。
function Car(){};
console.log(Car.prototype);
布白理解:
prototype属性的属性值是对象的形式,所以我们说是prototype指向原型对象。
类似于,构造函数的this属性的属性值是对象的形式,而构造函数会默认返回this,所以我们说构造函数会创建一个对象,this指向这个对象。
属性值是对象,我们就说这个属性指向这个对象。
为什么要引入原型?= 原型的作用
简单来说:解决代码冗余问题->
减少构造函数中,对被构造出的不同对象,相同属性值的多次配置的冗余问题。
:
prototype是定义的构造函数构造出的每个对象的公共祖先。
所有被构造函数构造出的对象都可以继承原型上的属性和方法。
function HandPhone(color, brand){
this.color = color;
this.brand = brand;
}
HandPhone.prototype.screen = '18.9';
HandPhone.prototype.chose = function (obj) {
console.log(obj.color, obj.brand);
}
var hp1 = new HandPhone('red', '小米');
var hp2 = new HandPhone('white', '华为');
console.log(hp1,hp2);
console.log(hp1.screen, hp2.screen);
hp1.chose(hp1);
hp1.chose(hp2);
补充:构造函数构造出的对象优先访问this上的属性,没有的话再去prototype上去寻找。
实际开发中:
我们会把对象的属性写到构造函数内部 -> 配置不同的属性值 (传的参数写到构造函数内部)
把不同对象的公共属性和方法写到prototype上 (写死的值挂到原型)
实例化对象对prototype的增删改查
查:√
function Car(){
this.name = 'benz';
}
Car.prototype.name = 'prototype';
Car.prototype.num = '1';
var car = new Car();
console.log(car.name, car.num);
console.log(Car.prototype, car);
实例化对象只能访问prototype,不能对其增、删、改。
补充:实际开发中,我们不会一条条给prototype添加属性,而是直接写成对象的形式,一次添加多条属性。
Car.prototype = {
name: 'prototype',
num: '1',
call: function(){
console.log('I am calling somebody');
}
}
原型链
前导知识:
函数中有一个原型属性,原型中有一个constructor属性:
function Car(){}
console.log(Car.prototype);
函数的原型对象的constructor属性,属性值是构造函数(constructor属性指向函数本身)。
function Car(){}
console.log(Car.prototype.constructor === Car); //ture
注意:这个constructor可以被手动更改指向吗?√
__ proto__属于实例化对象
打印一下实例化对象:
function Car(){}
Car.prototype.name = 'Benz';
Car.prototype.num = '1';
var car = new Car();
console.log(car);
console.log(car.__proto__ === Car.prototype); //true
__ proto__属于实例化对象
每次调用构造函数创建一个新实例,这个实例内部[[Prototype]]指针就会被赋值为构造函数的原型对象。脚本中没有访问这个[[Prototype]]特性的标准方式,但Firefox、Safari和Chrome会在每个对象上暴露__proto__属性,通过这个属性可以访问原型。
访问对象属性的顺序:
访问对象的属性时,先去this上寻找属性,若没有,去this的__proto__上寻找, __proto__指向构造函数的原型对象,即去构造函数的原型对象上寻找。
__proto__中只能指向构造函数的原型吗?可以更改指向吗?√
实例化对象后,重写prototype,对象的属性值是重写前还是重写后?
function Car(){
}
Car.prototype.name = 'Mazda'
var car = new Car();
console.log(Car.prototype);
// {name: 'Mazda', constructor: ƒ}
Car.prototype = {
name: 'benz'
}
console.log(Car.prototype);
//{name: 'benz'}
console.log(car.name); // 'Mazda'
实例化对象后,重写prototype,通过__proto__访问对象的属性,仍然为重写前原型的属性。
原因:
构造函数实例化对象时,对象才有了__proto__属性,指向此时构造函数的原型对象;这时即使重写原型对象(prototype指向了别的对象),也只是构造函数的原型对象改变了,而不是实例化对象的__proto__指向改变。
function Car(){
}
Car.prototype.name = 'Mazda'
Car.prototype = {
name: 'benz'
}
var car = new Car();
console.log(car.name); // 'benz'
__proto__永远指向实例化对象后此时的原型对象。
什么是原型链?
原型链有什么用?
尚未讲完
闭包和立即执行函数
写一个闭包:
function test() {
var a = 1;
function plus() {
a++;
console.log(a);
}
return plus;
}
var plus = test();
plus(); //2
plus(); //3
plus(); //4
内部函数被返回到函数外部,相当于把这个函数放到了全局。
window与return的关系
它们有啥区别?-> 写法区别,效果(功能)一样
function adc(){
window.a = 1;
}
adc();
console.log(a); // 1
上面代码利用window把一个变量放到全局。
那我们能不能用window实现上面的闭包:√
function test() {
var a = 1;
function plus() {
a++;
console.log(a);
}
window.plus = plus;
}
test();
plus(); // 2
plus(); // 3
plus(); // 4
立即执行+闭包
var plus = (function(){
var a = 1;
function plus() {
a++;
console.log(a);
}
return plus;
})();
plus(); // 2
plus(); // 3
plus(); // 4
立即执行+window
(function(){
var a = 1;
function plus() {
a++;
console.log(a);
}
window.plus = plus;
})();
plus(); // 2
plus(); // 3
plus(); // 4
区别:
- return:必须把返回的函数赋值给全局变量,全局变量再去执行
- window:在函数内部设置好全局变量,在全局直接执行,不需要全局再用一个变量接收。
插件就是用window的这种写法,window+闭包
插件开发初识:立即执行函数 + 构造函数相关(prototype) + window抛出构造函数
(function(){
var a = 1;
function Test(){ //构造函数
console.log(1);
}
Test.prototype = {
}
window.Test = Test;
})();
var test = new Test();
console.log(Test);
var Test = function(){
console.log(2);
}
console.log(Test);
console.log(a); //报错
为什么我们要这么写???
或者问:为什么要使用立即执行函数+window来写插件
- 隔离全局作用域:ES5没有块级作用域,立即执行函数可以隔离全局作用域:在立即执行函数中声明的变量对于全局来说是不可见的。
- 把函数放到全局:最后一句:window.Test = Test是为了把写好的函数放到全局,也就是说函数Test在全局中可见也可以被重写。
插件写法:立即执行函数 + 构造函数相关 + window抛出构造函数
补充
立即执行函数写法:
-
第一种:分号在最后
(function(){})(); (function(){})();
-
第二种:分号在最前
;(function(){})() ;(function(){})()
运算符 + 函数 -> 表达式
只有表达式才可以立即执行
(function(){
console.log(1);
})(); //控制台打印1
+function(){
console.log(1);
}(); //控制台打印1
!function(){
console.log(1);
}(); //控制台打印1
false || function(){
console.log(1);
}(); //控制台打印1
true || function(){
console.log(1);
}(); //不执行表达式,因为或运算符有真 直接结束运算
作业:
写一个插件,任意传两个数字,调用插件内部方法可以进行加减乘除。