原型拓展方法+继承

原型拓展方法+继承

扩展原型上的方法

数组indexOf的扩展

// console.log(Array.prototype.indexOf);
// 在标准浏览器返回函数,在IE8及下返回undefined

if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (val, index) {
        index = typeof index === 'undefined' ? 0 : index;
        for (var i = index; i < this.length; i++) {
            if (this[i] === val) {
                return i;
            }
        }
        return -1;
    }
}

var arr = [3, 4, 2, 3, 3, 4];
console.log(arr.indexOf(3)); // 0
console.log(arr.indexOf(3, 1)); // 3

字符串trim的扩展

// 字符串的trim

// 在标准浏览器下返回函数,在IE8及以下返回undefined
// console.log(String.prototype.trim);

// 扩展IE8及以下原型上的trim

if (!String.prototype.trim) {
    String.prototype.trim = function () {
        var re = /^\s+|\s+$/g;
        return this.replace(re, '');
    }
}
var str = '    平头哥   ';
console.log('(' + str.trim() + ')');

call与apply与bind

  • 函数.call(新的this指向, 参数1, 参数2, …)
  • 函数.apply(新的this指向, [参数1, 参数2, …])
  • 函数.bind(新的this指向, 参数1, 参数2, …)

作用:call与apply都是调用函数,而bind不会调用函数,都修改函数中的this的指向。

区别:call与bind的后续参数是以一个参数列表传入,而apply的后续参数是以一个数组传入

共同点:修改函数中的this,第一个参数都是新的this指向,都有返回值,call与apply返回值就是函数返回值,而bind返回值为由指定的 this 值和初始化参数改造的原函数拷贝,因为bind不会调用函数。

function fn(a, b) {
    console.log(this);
    console.log(a, b);
}

fn(3, 5);
fn.call(document, 5, 6);
fn.apply(document, [7, 8]);

var f = fn.bind(document,5,6);
f();

// --------------------------------
// 如果第一个参数是null 和 undefined,则this指向window
fn.call(undefined, 5, 6);
// 找出数组的最大值
var arr = [4, 6, 2, 6, 5, 8, 4, 7, 3];
// Math.max(4, 6, 2, 6, 5, 8, 4, 7, 3)
console.log(Math.max.apply(Math, arr));

继承

原型链继承

  • 原型链继承:将父的实例赋给子的原型

  • 如何用一句话实现继承:将父的实例赋给子的原型。

  • 不足:

    1、如果父级中有引用类型,创建的子级会一改全改

    2、constructor本来应该指向自己的构造函数,但是却指向了父级的构造函数

// 父类(学生类)
function Student() {
    this.job = '学生';
    this.ah = ['打游戏', '睡觉', '找平头妹'];
}
Student.prototype.showJob = function () {
    console.log(this.job);
}

// ----------------------------------------
// 子类(小学生)
function SmallStudent(name) {
    this.name = name; // 子自己添加的
}
SmallStudent.prototype = new Student(); // 继承了父的属性和方法

// 原型链继承:将父的实例赋给子的原型
// 如何用一句话实现继承:原型链继承
// 不足:
// 1、如果父级中有引用类型,创建的子级会一改全改属性
// 2、constructor本来应该指向自己的构造函数,但是却指向了父级的构造函数
//解决问题1、手动改变constructor指向
SmallStrudent.prototype.construcor = SmallStrudent;


// -------------------------------------
var s1 = new SmallStudent('zs');
console.log(s1);
console.log(s1.name);
console.log(s1.job);
console.log(s1.ah);
s1.showJob();
console.log(s1.constructor); // Student,本来应该指向SmallStudent

s1.ah.push('画画'); // 修改s1的爱好,后面创建的s2也会改变

var s2 = new SmallStudent('ls');
console.log(s2);
console.log(s2.ah); // ["打游戏", "睡觉", "找平头妹", "画画"]

原型拓展方法+继承

对象冒充(call)继承

  • 对象冒充继承:在子的构造函数中,调用父类的构造函数,并用call改this指向
  • 不足:不能继承原型上的方法。
// 父类(学生类)
function Student() {
    this.job = '学生';
    this.ah = ['打游戏', '睡觉', '找平头妹'];
}
Student.prototype.showJob = function () {
    console.log(this.job);
}

// ----------------------------------------
// 子类(小学生)
function SmallStudent(name) {
    Student.call(this); // 继承了父类构造函数中的属性
    this.name = name; // 加上我自己的属性
}

// 对象冒充继承:在子类的构造函数中,调父类的构造函数,并用call改this指向
// 好处:解决了原型链继承引用类型一改全改属性的问题
// 问题:只是继承了父类的属性,而没有继承父类原型上的方法

// -----------------------------------
var s1 = new SmallStudent('zs');
s1.ah.push('画画');
console.log(s1);


var s2 = new SmallStudent('ls');
console.log(s2);

原型拓展方法+继承

组合继承(已能实现但浪费性能)

  • 组合继承:对象冒充+原型链
  • 对象冒充继承属性
  • 原型链继承方法
// 父类(学生类)
function Student() {
    this.job = '学生';
    this.ah = ['打游戏', '睡觉', '找平头妹'];
}
Student.prototype.showJob = function () {
    console.log(this.job);
}

// ----------------------------------------
// 子类(小学生)
function SmallStudent(name) {
    Student.call(this); // 对象冒充继承
    this.name = name;
}
SmallStudent.prototype = new Student(); // 原型链继承


// 组合继承:原型链继承 + 对象冒充继承
// 问题
// 1、constructor指向不对
// 2、同名的属性会在原型链中出现两次
// 3、父的构造函数会被调用两次


//解决问题1、手动改变constructor指向
SmallStrudent.prototype.construcor = SmallStrudent;

// -------------------------------------------
var s1 = new SmallStudent('zs');
console.log(s1);

原型拓展方法+继承

原型链继承图
原型拓展方法+继承

寄生组合继承 (继承的标准模式)

// 父类(学生类)
function Student() {
    this.job = '学生';
    this.ah = ['打游戏', '睡觉', '找平头妹'];
}
Student.prototype.showJob = function () {
    console.log(this.job);
}

// ----------------------------------------
// 子类(小学生)
function SmallStudent(name) {
    Student.call(this); // 继承父的属性
    this.name = name; // 添加子类自己的属性(写在继承的后面)
}

// 继承父的原型上的方法
inherits(SmallStudent, Student);

// 添加自己原型上的方法,一定要写在继承的后面
SmallStudent.prototype.showName = function () {
    console.log(this.name);
}

// 寄生组合式继承(继承的标准模式)
// 父的属性和方法要分开继承

// -----------------------------------------
var s1 = new SmallStudent('zs');
console.log(s1);
// console.log(s1.constructor);


// ------------------------------------------
// 寄生组合继承父类原型上方法的封装,原型链继承升级
function inherits(Child, Parent) {
    var F = function () { };
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TqT3xLvi-1632922046848)(C:\Users\Administrator\Desktop\课堂笔记\第二阶段\笔记\image\对象冒充继承.jpg)]

原型链继承升级图

原型拓展方法+继承

原型对象引用总结(地址问题)

  • 上图箭头指向谁即谁的地址被引用,如Fun实例.__proto__指向Fun原型,即Fun实例拿到Fun原型的地址。
  • Son.prototype = new Fun(); 表示Son.prototype拿到Fun实例对象的地址,这时采用赋值形式则Son.prototype改变内容则Fun里的内容也会改变,因为地址一样。
  • 而Fun实例.__proto__指向Fun原型,Fun实例拿到Fun原型的地址,则改变Fun实例不会改变Fun原型,因为他们之间用__proto__指向的。如改变Fun实例.__proto__则会改变Fun原型。同理:原型.constructor一样。
  • Fun.prototype = Farther.prototype; 表示

继承应用

判断所有数据类型

var obj = {};
console.log(obj);

//this指向实例对象obj。所以打印[object Object]。
console.log(obj.toString());

//本来直接调用this指向Object原型对象上。所以打印[object Object]。
console.log(Object.prototype.toString());

//改变toString的this指向。使this指向实例对象上。而这个实例对象就是数据类型是由不同构造函数构造的。打印[object 构造函数]。
console.log(Object.prototype.toString.call());

//例:1为Number构造函数的实例对象,所以打印[object Number]。
console.log(Object.prototype.toString.call(1));

面象对象注意事项

实例对象方法的引用方式


function Person() {
    this.uname = '学生';
    this.age = 1;
    this.fn2();//第二种方式。
}
Person.prototype.fn1 = function () {
    console.log(1);
}
Person.prototype.fn2 = function () {
    console.log(2);
}
Person.prototype.fn3 = function () {
    console.log(3);
}

var s1 = new Person();
s1.fn1();//第一种方式。

new.Person().fn3();//第三种方式。

this的指向问题

   this指向为谁调用指向谁,主要看在谁的作用域里。拥有这个作用域的函数被谁调用就指向谁。
- 谁调用了函数,this就指向谁
- this不是在定义的时候确定的,而是在调用的时候确定的

// 1、全局环境下的this: window
         console.log(this);

// 2、事件处理函数中的this:是触发这个事件的元素
         box.onclick = function () {
             console.log(this); // box
         }

        
// 3、定时器中的this: window
         setTimeout(function () {
             console.log(this);
         }, 1000);


// 4、函数直接执行中的this, 非严格模式是window,严格模式下是undefined
        //"use strict"; // 严格模式
         function fn() {
             console.log(this);
         }
        fn();

       
 // 5、call apply

// 6、对象方法内部调用
     var obj = {
      fn: function () {
          console.log(this);
           }
        };
       
      obj.fn(); // obj
        
      var v = obj.fn;
      v(); // window
     
// 7、构造函数中的this:new 出来的哪个对象(实例)
new将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)。
     
// 8、原型链函数中的this,一般都指实例
因为只能由实例调用使用。   
        
// 9、所有的回调函数里面的this是 window
        move(box, {}, function () { })
这个回调函数是实参在全局作用域里。

法内部调用
var obj = {
fn: function () {
console.log(this);
}
};

  obj.fn(); // obj
    
  var v = obj.fn;
  v(); // window

// 7、构造函数中的this:new 出来的哪个对象(实例)
new将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)。

// 8、原型链函数中的this,一般都指实例
因为只能由实例调用使用。

// 9、所有的回调函数里面的this是 window
move(box, {}, function () { })
这个回调函数是实参在全局作用域里。




上一篇:#es6数组的扩展


下一篇:js原型,原型链(不断补充中)