javascript综合 一(类,对象,正则表达式,构造函数和原型,继承)

面向对象OOP(Object Ordiented Programming)

特性:
封装性
继承性
多态性

ES6中的

<script>
    //constructor中的this指的是创建的实例对象
    //类的方法中的this指向的是这个方法的调用者
    var that;
    class Star {
        //类的共有属性放到constructor里面
        constructor(uname, age) {
            this.uname = uname;
            this.age = age;
        }

        //声明方法
        sing() {
            that = this;
            console.log(this);
            console.log("sing");
        }
        sum() {
            console.log(this.uname + this.age);
        }
    }

    //类的继承
    class Son extends Star {
        constructor(uname, age,sex) {
            // this.uname = uname;子类执行sum方法会报错
            // this.age = age;子类执行sum方法会报错
            super(uname, age); //super可以调用父类中的构造函数和普通函数
            this.sex=sex;
            //this.uname = uname; //super必须在子类this之前调用 
            //this.age = age;
        }
    }
    var son = new Son(1, 2,'女');
    son.sing(); //打印sing
    console.log(that === son);//打印true
    son.sum(); //打印3
</script>

面向对象的运行-tab栏切换

可添加,删除tab栏,可对tab栏的标题和内容双击进行修改
在这里插入图片描述
这里用到了一种新方法:element.insertAdjacentHTML(position, text);
position是相对于元素的位置,并且必须是以下字符串之一:

beforebegin: 元素自身的前面。
afterbegin: 插入元素内部的第一个子节点之前。
beforeend: 插入元素内部的 最后一个子节点之后。
afterend: 元素自身的后面。
text是要被解析为HTML或XML,并插入到DOM树中的字符串。
例如:

// <div id="one">one</div> 
var d1 = document.getElementById('one'); 
d1.insertAdjacentHTML('afterend', '<div id="two">two</div>');

// 此时,新结构变成:
// <div id="one">one</div><div id="two">two</div>

html代码:

<body>
    <div class="tabsbox" id="tab">
        <!-- tab标签 -->
        <nav class="firstnav">
            <ul>
                <li class="liactive"><span>测试1</span><span class="close">&times;</span></li>
                <li><span>测试2</span><span class="close">&times;</span></li>
                <li><span>测试3</span><span class="close">&times;</span></li>
                <button>+</button>
            </ul>

        </nav>
        <!-- tab内容 -->
        <div class="tabscon">
            <br>
            <section class="conactive"><span>测试1</span></section>
            <section><span>测试2</span></section>
            <section><span>测试3</span></section>
        </div>
    </div>
</body>

js代码:

var that;
class Tab {
    //每new一个对象 constructor会自动执行
    constructor(id) {
        that = this;
        //获取元素
        this.main = document.querySelector(id);
        this.add = this.main.querySelector('button');
        this.ul = this.main.getElementsByTagName('ul')[0];
        //注意下面这种用法
        //this.ul = this.main.querySelector('.firstnav ul:first-child');
        this.fsection = this.main.querySelector('.tabscon');
        this.init();
    }

    //方法
    //初始化 给每一个tab一个index和一个点击事件
    init() {
        //重新获取所有的li和section
        this.updateNode();
        this.add.onclick = this.addTab;
        for (var i = 0; i < this.lis.length; i++) {
            this.lis[i].index = i;
            //这里不能给toggletab方法加小括号 
            //加了的话页面一加载就会执行
            this.lis[i].onclick = this.toggleTab;
            this.remove[i].onclick = this.removeTab;
            this.spans[i].ondblclick = this.editTab;
            this.sections[i].ondblclick = this.editTab;
        }
    }

    updateNode() {
        this.spans = this.main.querySelectorAll('.firstnav li span:first-child');
        this.remove = this.main.querySelectorAll('.close');
        this.lis = this.main.querySelectorAll('li');
        this.sections = this.main.querySelectorAll('section');
    }

    //清除标签的所有类
    clearClassName() {
        for (var i = 0; i < this.lis.length; i++) {
            this.lis[i].className = '';
            this.sections[i].className = '';
        }
    }

    //切换
    toggleTab() {
        //这里面的this指的是lis,因为是lis调用了toggletag方法
        //lis中没有sections属性所以这里不是用this.sections而是用that.sections来调用实体类中的srctions
        //lis中也没有clearclassname方法 所以这里是用that来调用实体类中的clearclassname方法
        console.log(this.index);
        that.clearClassName();
        this.className = 'liactive';
        that.sections[this.index].className = 'conactive';
    }

    //添加
    addTab() {
        //alert(1);
        //创建li元素和section元素
        that.clearClassName();
        var li = '<li class="liactive"><span>新增加的</span><span class="close">&times;</span></li>';
        var section = '<section class="conactive">新增加的</section>';
        //追加到父元素里面
        //这里使用了新的方法insertadjacenthtml
        that.ul.insertAdjacentHTML('beforeend', li);
        console.log(that.fsection);
        that.fsection.insertAdjacentHTML('beforeend', section);
        that.init();
    }

    //删除
    removeTab(e) {
        //当点击叉号时,会触发父元素即li元素的点击事件
        //所以这里需要阻止冒泡
        e.stopPropagation();
        var index = this.parentNode.index;
        console.log(index);
        that.lis[index].remove();
        that.sections[index].remove();
        that.init();
        //当删除的不是处于选定状态的li时 原来处于选定状态的li保持不变
        //判断是否有li处于选定状态 如果有 则直接删除 不执行后面的
        if (document.querySelector('.liactive')) {
            return;
        }
        //如果没有(代表此时删除的是处于选定状态的li)则执行后面的 
        else {
            //当删除处于选中状态的li的时候,让它的前一个li处于选定状态
            index--;
            //这个click()手动调用点击事件 不需要鼠标点击触发
            //这里&&表示前面的为真时才执行后面的 否则不执行
            that.lis[index] && that.lis[index].click();
        }
    }

    //编辑
    editTab() {
        //获取原来的内容
        var str = this.innerText;
        //双击禁止选中文字
        window.getSelection ? window.getSelection().removeAllRanges : document.selection.empty();
        //alert("222");
        //双击后生成文本框
        this.innerHTML = '<input type="text" style="width:90%;height:90%"/>';
        var input = this.children[0];
        input.value = str;
        //让文本框中的文字处于选定状态
        input.select();
        //当焦点离开文本框就把文本框里的内容给span
        //当鼠标失去焦点
        input.onblur = function() {

            //这里的this指的是input
            this.parentNode.innerHTML = this.value;
        }

        //按下回车把文本框里的值给span
        input.onkeyup = function(e) {
            if (e.keyCode === 13) {
                //blur()手动调用失去焦点事件 不需要鼠标触发
                this.blur();
            }
        }
    }
}
new Tab('#tab');

正则表达式

主要功能:匹配,替换,提取
声明

//声明一个正则表达式
var reg = new RegExp(/123/)//正则表达式中不需要加引号 不管是数字还是字符;
//或者
var reg2 = /123/;

正则表达式中的特殊字符

  • 边界符
    ^ 表示匹配行首的文本(以谁开始)
    $ 表示匹配行尾的文本(以谁结束)
    如果^和$在一起,表示必须是精确匹配
var reg1 = /^abc/;
console.log(reg1.test('abcjjj')); //true
console.log(reg1.test('fabc')); //false

var reg2 = /abc$/;
console.log(reg2.test('abcjjj')); //false
console.log(reg2.test('jjjabc')); //true

var reg3 = /^abc$/;//精确匹配 只能是‘abc’字符 其他都不行
console.log(reg3.test('abc'));//true
console.log(reg3.test('abcbcaabc'));//false
console.log(reg3.test('abcjjjabc'));//false
console.log(reg3.test('abcabc'));//false
  • 字符类[]
    表示有一系列字符可供选择,只要匹配其中一个就可以
var rg1 = /[abc]/; //只要包含字符 a b c其中的一个即是true
console.log(rg1.test('andy')); //true
console.log(rg1.test('baby')); //true
console.log(rg1.test('color')); //true
console.log(rg1.test('red')); //false

var rg2 = /^[abc]$/; //只有 a 或者 b 或者 c才为true
console.log(rg2.test('a')); //true
console.log(rg2.test('b')); //true
console.log(rg2.test('c')); //true
console.log(rg2.test('abc')); //false
  • [-] 方括号内部范围符-
var regg1 = /^[a-z]$/;//只能是a-z这其中的字母
//字符组合
var regg2 = /^[a-zA-Z0-9_-]$/;
console.log(regg2.test('a9')); //false
console.log(regg2.test('B')); //true
console.log(regg2.test('_-')); //false

var regg3 = /[a-zA-Z0-9_-]/;
console.log(regg3.test('a9')); //true
console.log(regg3.test('B')); //true
console.log(regg3.test('ahBf86-_')); //true
console.log(regg3.test('!')); //false
  • [^] 方括号内部有 ^ 表示取反
//如果[]里面包含^ 则表示取反
var regg4 = /[^a-zA-Z0-9_-]/;
console.log(regg4.test('a9')); //false
console.log(regg4.test('B')); //false
console.log(regg4.test('ahBf86-_')); //false
console.log(regg4.test('!')); //true
  • 量词符
    用来设定某个模式出现的次数
    在这里插入图片描述
//量词符
//* 表示出现>=0次
var reeg1 = /^a*$/;
console.log(reeg1.test('aaaaa')); //true
console.log(reeg1.test('')); //true
//+ 表示出现>=1次
var reeg2 = /^a+$/;
console.log(reeg2.test('aaaaa')); //true
console.log(reeg2.test('')); //false
//?表示出现0次或1次
var reeg3 = /^a?$/;
console.log(reeg3.test('aaaaa')); //false
console.log(reeg3.test('')); //true
console.log(reeg3.test('a')); //true
//{n} 表示出现n次
var reeg4 = /^a{3}$/;
//var reeg4 = /^abc{3}$/;这里只是让c重复3次 只能是abccc
console.log(reeg4.test('aaaaa')); //false
console.log(reeg4.test('aaa')); //true
console.log(reeg4.test('a')); //false
//{n,} 表示出现>=n次
var reeg5 = /^a{3,}$/;
console.log(reeg5.test('aaaaa')); //true
console.log(reeg5.test('aaa')); //true
console.log(reeg5.test('a')); //false
//{n,m} 表示出现n<=m次
var reeg6 = /^a{3,6}$/;
console.log(reeg6.test('aaaaaa')); //true
console.log(reeg6.test('aaa')); //true
console.log(reeg6.test('a')); //false
console.log(reeg6.test('aaaaaaaaa')); //false
  • ()表示优先级
var reeg4 = /^(abc){3}$/;//这里是让abc重复3次 只能为abcabcabc
  • 预定义类
    预定义类是某些常见模式的简写方式
    在这里插入图片描述
  • 参数
    /表达式/switch
    switch也叫修饰符
    在这里插入图片描述

对象-Object

- 创建对象

var obj={
	name:'zhang',
	age:'18',
	//注意这里方法的声明方式
	eat:function(){
	console.log("eat meat");
}};
//或者
//参数为null或者undefined,将会创建并返回一个空对象
//
var obj1=new Object();


var o = new Object(true);
//等价于 o = new Boolean(true);
console.log(o); //打印Boolean{true}
// 等价于 o = new Boolean(false);
var o2 = new Object(Boolean());
console.log(o2); //打印Boolean{false}

在这里插入图片描述

- Object构造函数的方法

Object.assign(target,source1,source2…)

主要用于对象的合并,将source对象的所有可枚举属性合并到target对象上,这个方法只拷贝source对象的自身属性,不拷贝继承的属性(不能拷贝prototype上的属性和方法)。

  • Object.assign()方法可以实现浅拷贝 而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。同名属性会替换。
  • Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
  • Object.assign可以用来处理数组,但是会把数组视为对象。
let target = {
        age: 10,
        page: 1
    };
let objA = {
    age: 12,
    year: 20,
    newObj: {
        sex: '男'
    }
};
Object.assign(target, objA);
console.log(target);
/*
打印:
{age: 12, page: 1, year: 20, newObj: {…}}
age: 12
newObj: {sex: "男"}
page: 1
year: 20
__proto__: Object
*/


function Father() {
    this.name = 'father';
}
Father.prototype.age = 29;
Father.prototype.sing = function() {
    console.log('sing');
}
let son = new Father();
son.age = 10;
let obj = {
    sex: '男'
};
console.log(Object.assign(obj, son));
//只拷贝自身的属性 不拷贝prototype上面的属性
// {sex: "男", name: "father", age: 10}
// age: 10
// name: "father"
// sex: "男"
// __proto__: Object

Object.create(prototype[,propertiesObject])

prototype:新创建对象的原型对象
propertiesObject:可选。如果没有指定为 undefined,则是要添加到新创建对象的不可枚举(默认)属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。
如果propertiesObject参数是 null 或非原始包装对象,则抛出一个 TypeError 异常。

该方法是使用指定的原型对象和属性创建一个新对象,其实就是创建了这个指定对象的实例
返回新对象
Object.create(Father.prototype)相当于new Father()(Father是一个构造函数)相当于Object.create(father(father是一个对象变量))
Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法

var o = Object.create(Object.prototype, {
// foo会成为所创建对象的---数据属性
foo: {
    enumerable: false, //对象属性是否可通过for-in循环,flase为不可循环,默认值为true
    writable: true, 对象属性是否可修改,flase为不可修改,默认值为true
    configurable: true, 能否使用delete、能否需改属性特性、或能否修改访问器属性、,false为不可重新定义,默认值为true
    value: "goodnice"
},
// bar会成为所创建对象的 ---访问器属性
bar: {
    configurable: false,
    get: function() {
        return 10
    },
    set: function(value) {
        console.log("Setting `o.bar` to", value);
    }
}
});
console.log(o);
//{foo: "goodnice"}
// bar: 10
// foo: "goodnice"
// get bar: ƒ ()
// set bar: ƒ (value)
// __proto__: Object
//这样的对象变量不能通过new的方式来创建实例
//例如 let p=new person()是错误的
//只有构造函数是通过new来创建对象实例
const person = {
  isHuman: false,
  printIntroduction: function() {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};

const me = Object.create(person);//

me.name = 'Matthew'; // 
me.isHuman = true; // 重写person中的isHuman属性

me.printIntroduction();// "My name is Matthew. Am I human? true"

使用Object.create()实现原型链继承+构造函数继承
和之前的方法一样。都是先把父构造函数的this指向子构造函数,然后让子构造函数的prototype指向父构造函数的实例
这里是将Son.prototype = new Father();改成了Son.prototype = Object.create(Father.prototype);

//实现Son继承Father
//组合继承
function Father(name, age) {
    this.name = name;
    this.age = age;
}
Father.prototype.sing = function() {
    console.log('Father-sing');
}

function Son(name, age, sex) {
    //继承父构造函数的属性
    //让父构造函数的this指向子构造函数
    Father.call(this, name, age);
    this.sex = sex;
}
//继承父构造函数的方法
//Son.prototype = new Father();
//这里改了
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
Son.prototype.dance = function() {
    console.log('A-dance');
}
var a = new Son('zry', 20, '女');
var b = new Father('22', 20);
//a.sing();
console.log(a);
console.log(b);

Object.defineProperty(obj,prop,descriptor)

obj—需要定义属性的对象;
prop—需定义或修改的属性的名字;
descriptor—将被定义或修改的属性的描述符;
在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

const object1 = {};

Object.defineProperty(object1, 'property1', {
  value: 42,
  writable: false
});

object1.property1 = 77;
// throws an error in strict mode

console.log(object1.property1);
// expected output: 42

configurable
当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
默认为 false。
enumerable
当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
默认为 false。
数据描述符还具有以下可选键值:

value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。
默认为 undefined。
writable
当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
默认为 false。
存取描述符还具有以下可选键值:

get
属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
默认为 undefined。
set
属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
默认为 undefined。

这里的yyear是访问器属性,_year和edition是数据属性

var book = {
    _year: 2004,
    edition: 1
};
Object.defineProperty(book, "yyear", {
    get: function() {
        return this._year;
    },
    set: function(newvalue) {
        if (newvalue > 2004) {
            this._year = newvalue;
            this.edition += newvalue - 2004;
        }
    }
})
book.yyear = 2005;
console.log(book.edition);//2

Object.defineProperties(obj,props)

直接在一个对象上定义新的属性或修改现有属性,并返回该对象。

var objb = {};
Object.defineProperties(objb, {
    'property1': {
        value: true,
        writable: true
    },
    'property2': {
        value: 'Hello',
        writable: false
    }
    // etc. etc.
});
console.log(objb);
// {property1: true, property2: "Hello"}
// property1: true
// property2: "Hello"
// __proto__: Object

给对象添加多个属性并分别指定它们的配置。

Object.keys(obj)

返回一个由给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致 (两者的主要区别是 一个 for-in 循环还会枚举其原型链上的属性)。

// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // ["0", "1", "2"]

// array like object
var obje = {
    0: 'a',
    1: 'b',
    2: 'c'
};
console.log(Object.keys(obje)); // ["0", "1", "2"]

// array like object with random key ordering
var anObj = {
    100: 'a',
    2: 'b',
    7: 'c'
};
console.log(Object.keys(anObj)); // ["2", "7", "100"]

let obja = {
    age: 20,
    sex: '男'
};
console.log(Object.keys(obja)); // ["age", "sex"]

Object.values(obj)

返回给定对象自身可枚举值的数组。值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]

// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.values(obj)); // ['a', 'b', 'c']

// array like object with random key ordering
// when we use numeric keys, the value returned in a numerical order according to the keys
//属性是无序的,但是返回的值是有序的
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.values(an_obj)); // ['b', 'c', 'a']

Object.setPrototypeOf(obj,prototype)

设置对象的原型(即内部 [[Prototype]] 属性)。
Object.getPrototypeOf()
返回指定对象的原型对象。
Object.getOwnPropertyDescriptor()
返回对象指定的属性配置。
Object.getOwnPropertyNames()
返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。
Object.getOwnPropertySymbols()
返回一个数组,它包含了指定对象自身所有的符号属性。
Object.entries()
返回给定对象自身可枚举属性的 [key, value] 数组。

  • 访问对象

  • 点操作符(常量键访问)
    obj.name

  • 中括号操作符(变量键访问)
    注意中括号里面是字符串!!!不是变量 obj[age]这样是错的
    obj[‘age’]

- 对象的基本操作

删除对象:
删除属性的唯一方法是使用 delete 操作符;设置属性为 undefined 或者 null 并不能真正的删除属性, 而仅仅是移除了属性和值的关联。
delete obj.name
判断键值是否存在:obj.hasOwnProperty('name') //true
获取key值:
Object.keys(obj) //返回一个存储对象中所有key值的数组
获取某个key对应的值:
Object['key']

构造函数和原型

静态成员和实例成员

//构造函数
function Star(uname, age) {
    this.uname = uname;
    this.age = age;
    this.sing = function() {
        console.log("sing");
    }
}
var zry = new Star('张如意', 21);
//实例成员
//是构造函数内部通过this添加的成员 如uname age sing
//实例成员只能通过实例对象来访问 如zry.uname


Star.sex = '女';
//静态成员
//是在构造函数本身添加的成员 如sex
//静态成员只能通过构造函数来访问 如Star.sex

构造函数的问题
当创建多个构造函数的实例时,每个实例对象都要开辟一个内存空间来存储对象中的方法,尽管这些方法都是一样的,这样就很浪费内存。
在这里插入图片描述

var obj1 = new Star('zry', 18);
var obj2 = new Star('ddd', 21);
console.log(obj1.sing === obj2.sing); //false

我们希望所有的实例对象都是用同一个函数,这样就比较节省内存,所以需要prototype

prototype对象
构造函数通过原型分配的函数是所有实例对象所共享的
每个构造函数都有一个prototype属性,这个属性是一个指针,指向这个构造函数的原型对象,这个对象的所有属性和方法,都会被构造函数所拥有
在这里插入图片描述

Star.prototype.sing = function() {
    console.log("sing");
}
console.log(obj1.sing === obj2.sing); //true

在这里插入图片描述
** proto 原型**
每一个实例对象都有一个 __proto__属性,这个属性会指向构造函数的prototype,之所以对象可以使用构造函数prototype的属性和方法,就是因为对象有 __proto__的存在

在这里插入图片描述

console.log(obj1.__proto__ === Star.prototype);//true

方法的查找规则:
首先看obj1对象上是否有sing方法,如果有则执行
如果没有,就去构造函数prototype对象上查找sing方法,再执行
在这里插入图片描述

在这里插入图片描述
constructor-构造函数
在__proto__和prototype里面都有一个constructor构造函数属性,它指回构造函数本身,它是一个指针
例如new father=new Father(params);
Father.prototype和father.proto 指的都是构造函数的原型对象,构造函数的原型对象有一个constructor属性指向构造函数本身,有一个__proto__属性指向Object.prototype,而在Object.protorype里面有一个constructor属性指向Object
在这里插入图片描述
在这里插入图片描述

但是很多情况下我们需要手动的利用constructor属性指回原来的构造函数,比如当方法太多的时候,我们将方法封装到一个对象中,这时候就需要手动为construcotor属性赋值

//这里如果没有手动利用constructor这个属性指向原来的构造函数
//Star.prototype的constructor属性就不再指向Star了
//这是因为这里用了一个新的对象覆盖了prototype原来指向的对象
//里面的constructor属性也被覆盖了
//所以要手动添加constructor
Star.prototype = {
    //手动赋值
    constructor: Star,
    sing: function() {
        console.log('sing');
    },
    movie: function() {
        console.log('movie');
    }
}
console.log(Star=== Star.prototype.constructor); //true

在这里插入图片描述

在这里插入图片描述

实例与原型

function Person() {

}

Person.prototype.name = 'Kevin';

var person = new Person();

person.name = 'Daisy';
console.log(person.name) // Daisy

delete person.name;
console.log(person.name) // Kevin

原型链

console.log(Star.prototype);
console.log(Star.prototype.__proto__ === Object.prototype);//true

在这里插入图片描述
图中蓝色的就是原型链
在这里插入图片描述
js成员查找机制
在这里插入图片描述
原型对象prototype中this的指向问题
构造函数中的this指向实例对象
原型对象中的this也指向实例对象

var that;
Star.prototype.sing = function() {
    console.log("sing");
    that = this;
}
var ldh = new Star('ldh', 18);
ldh.sing();//sing
console.log(ldh === that);//true

原型对象的应用-扩展内置对象的方法
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组添加自定义的求偶数和的功能。

Array.prototype.xxxx=function(){}

继承

在ES6之前没有继承,可以通过构造函数+原型对象模拟实现继承,这个被称为组合继承

改变函数内部this指向

  • call()
    可以调用函数
    fun.call(thisArg,arg1,arg2,……)
    thisArg表示当前调用函数this的指向对象
function fun() {
    console.log('这是一个fun');
    console.log(this); //这里的this原本指向window
}
var obj = {
    name: 'zry'
}
//1.call()可以调用函数
fun.call();

//2.可以改变函数的this指向 此时这个函数的this指向了obj对象
fun.call(obj);

在这里插入图片描述

  • apply()
    可以调用函数
    fun.apply(thisArg,[argsArray])
    thisArg:在fun函数运行时指定的this值
    argsArray:传递的值,必须包含在数组里面
    返回值就是函数的返回值,因为他就是调用函数
    参数传递的是数组形式,但是实际上是数组里面的内容
//apply()的应用
//数组中没有max方法,但是math中有
//可以利用apply以数组的形式将值传进去
var arr = [4, 5, 6, 7, 8, 9];
Math.max.apply(null, arr);
  • bind()
    不可以调用函数
    fun.bind(thisArg,arg1,arg2,……)
    thisArg:在fun函数运行时指定的this值
    arg1,arg2:传递的其他参数
    返回由指定的this值和初始化参数改造的原函数拷贝
    即返回的是一个具有新的this指向的函数
var o = {
    name: 'zry'
};
function fn() {
    console.log(this);
}
//因为bind返回的是一个新的函数
//所以要调用新的函数
var f = fn.bind(o);
f();

//如果有的函数不需要立即调用但是又要改变这个函数内部的this指向
//此时要用bind
//例如,有一个按钮,点击之后就禁用,三秒之后就开启
var btn = document.querySelector('button');
btn.onclick = function() {
    this.disabled = true;
    var that = this;
    setTimeout(function() {
        //定时器里面的this指向的是window
        //所以不能像下面一样直接使用window
        //this.disabled = false;
        //像下面这样也可以
        //that.disabled = false;
        //使用bind方法,绑定btn
        this.disabled = false;

    }.bind(this), 3000);
}

构造函数继承

借用构造 继承父类型属性
基本思想:在子类型构造函数的内部调用父类型构造函数
核心原理:通过call()把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性

//父构造函数
function Father(uname, age) {
    //this指向父构造函数的实例对象
    this.uname = uname;
    this.age = age;
}

function Son(uname, age, score) {
    //this指向子构造函数的实例对象
    //如果要实现Son继承Father,
    //则要将Father中的this指向子构造函数的对象
    Father.call(this, uname, age);
    this.score = score;
}

var son = new Son('zry', 18, 100);
console.log(son);

在这里插入图片描述

组合继承

借用原型对象继承父类型原型上的属性和方法

**原型链继承的基本思想:**利用原型让一个引用类型继承另一个引用类型的属性和方法。

构造函数继承+原型链继承=组合继承

**组合继承的基本思想:**使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承
组合继承的问题就是:无论什么情况下,都会调用两次父构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数的内部。子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性

Father.call(this, uname, age);//第二次调用父构造函数
Son.prototype = new Father();//第一次调用父构造函数
第一次Son的prototype上存在了Father的属性,这些属性也属于Son的实例,只是现在存在于Son的prototype上;第二次又在Son的实例上面创建Father中的属性,这一次这里几个属性会屏蔽原型中的几个同名属性。如图:
在这里插入图片描述

//父构造函数
function Father(uname, age) {
    //this指向父构造函数的实例对象
    this.uname = uname;
    this.age = age;
}
Father.prototype.money = function() {
    console.log('money方法');
}

function Son(uname, age, score) {
    //this指向子构造函数的实例对象
    //如果要实现Son继承Father,
    //则要将Father中的this指向子构造函数的对象
    Father.call(this, uname, age);//第二次调用父构造函数
    this.score = score;
}

//让子构造函数继承父构造函数的money方法
//1-----让子构造函数的prototype=父构造函数的prototype
//这样son对象的原型中就含有money方法
//但是这样的赋值,当Son原型对象改变后,Father原型对象也会改变
//Son.prototype = Father.prototype;

//2-----让子构造函数的prototype=父构造函数的实例
//这就是原型链继承
//这样子构造函数的改变就不会影响父构造函数
//但是这时Son的prototype对象被Father对象覆盖,
//contructor属性就指向Father
//所以要手动修改constructor的指向
//要先实现继承!!!!再为Son添加新的方法!!!不要添加方法后再实现继承 这样新方法会被覆盖掉
//通过这样的方法继承,money()方法仍然在Father.prototype中,但是Father中的属性则位于Son.prototype中
//因为Father的每个实例都拥有它的属性,而在这里Son.prototype变成了Father的一个实例,
//意味着Son.prototype上面直接就有Father的属性,所以所有的Son实例都会共享这个属性
//这也导致了后面原型链继承的问题
Son.prototype = new Father();//第一次调用父构造函数
Son.prototype.constructor = Son;

//这里为Son构造函数又添加了exam方法
Son.prototype.exam = function() {
    console.log('exam方法');
}
var son = new Son('zry', 18, 100);
console.log(son);//__proto__中包含exam和money方法
console.log(Father.prototype);//__proto__也包含exam和money方法
//这是因为当执行Son.prototype = Father.prototype;时
//Son.prototype指向了Father.prototype的地址
//当改变Son.prototype时,也会改变Father.prototype

在这里插入图片描述
在这里插入图片描述
原型链继承的问题
引用类型的属性会被共享

//原型链继承的问题
function SuperType() {
    this.colors = ['red', 'blue', 'green'];
}

function SubType() {

}
//继承了SuperType
//SuperType的每个实例都会拥有一个colors属性,而在这里SubType.prototype变成了SuperType的一个实例,
//意味着SubType.prototype上面直接就有一个colors数组,所以所有的SubType实例都会共享这个属性
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); //["red", "blue", "green", "black"]

var instance2 = new SubType();
console.log(instance2.colors); //["red", "blue", "green", "black"]

使用构造函数继承就不会有这样的问题

//构造函数继承不会有这样的问题
function SuperType() {
    this.colors = ['red', 'blue', 'green'];
}

function SubType() {
	//这一步相当于将SuperType构造函数中的this值变成SubType的实例,这样每个SubType的实例都有自己的colors属性了
    SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); //["red", "blue", "green", "black"]

var instance2 = new SubType();
console.log(instance2.colors); //["red", "blue", "green"]

原型式继承

**基本思想:**借助原型可以基于已有的对象创建新对象,同时还不比因此创建自定义类型
本质是执行对给定对象的浅复制,浅复制后的副本可以得到进一步改造

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

ES5新增了Object.create()规范了原型式继承。这个方法接收两个参数,一个是用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

如果为create方法添加第二个参数,则会覆盖原型对象上的同名属性

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
                   
var anotherPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});

alert(anotherPerson.name);  //"Greg"

寄生式继承

**基本思想:**创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回这个对象

function createAnother(original) {
var clone = object(original); //创建一个新对象
clone.sayhi = function() {
    console.log("hi");
}
return clone; //返回这个对象
}

var person = {
name: 'zzz',
fridens: ['x', 'd', 'g']
};
var anotherPerson = createAnother(person);
anotherPerson.sayhi(); //hi

寄生组合式继承

**基本思想:**通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
解决了组合继承要多次调用父类型构造函数而导致的低效率问题

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

//这个方法的作用就是将子构造函数的原型指定为父构造函数的原型
//相当于Son.prototype = new Father();Son.prototype.constructor = Son;
function inheritPrototype(subType, superType) {
	var prototype = object(superType.prototype); //创建了superType.prototype的一个副本
	prototype.constructor = subType; //为这个副本指定一个constructor,指定为subType
	subType.prototype = prototype; //将subType.prototype指定为这个副本
}
                        
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    alert(this.name);
};

function SubType(name, age){  
    SuperType.call(this, name);//构造函数继承
    this.age = age;
}

inheritPrototype(SubType, SuperType);//替代了原型链继承的那两行代码

SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);  //"red,blue,green,black"
instance1.sayName();      //"Nicholas";
instance1.sayAge();       //29


var instance2 = new SubType("Greg", 27);
alert(instance2.colors);  //"red,blue,green"
instance2.sayName();      //"Greg";
instance2.sayAge();       //27
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值