面向对象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">×</span></li>
<li><span>测试2</span><span class="close">×</span></li>
<li><span>测试3</span><span class="close">×</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">×</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