Bootstrap

十、原型、原型链、闭包和立即执行函数、插件开发初始

十、原型、原型链、闭包和立即执行函数、插件开发初始

原型

什么是原型(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);
}(); //不执行表达式,因为或运算符有真 直接结束运算

作业:

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

;