类抽取了对象的公共部分,它泛指某一大类
对象特指某一个通过类实例化出来的东西
ES6中的class其实就类似于ES5中的构造函数
比如下面两个写法是等价的:
function Point(x, y) {this.x = x;this.y = y;
}Point.prototype.sum = function () {return this.x + this.y ;
};var p = new Point(1, 2);
p.sum();
class Point {constructor(x, y) {this.x = x;this.y = y;}sum() {return this.x + this.y ;}
}var p = new Point(1, 2);
p.sum();
定义sum()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加逗号会报错。
constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。
class Point {
}// 等同于
class Point {constructor() {}
}
Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。
class Father {
}class Son extends Father {
}
1、如果父类和子类属性一致,子类的constructor可以省略
2、子类在继承父类的方法后,可以重写父类的方法,也可以添加其他方法,super必须在子类的this之前调用。
3、其实super也可以理解成改变父类构造函数中的this指向,让this指向子类的实例。
4、所有子类继承的父类的属性都写到super里,子类独有的要在super后添加
如果子类和父类在构造器函数中的属性一致,那么子类中的constructor完全可以省略
class Person {constructor(name,age) {this.name = name;this.age = age;}speak() {console.log(this.name, this.age);}}class Student extends Person {say() {console.log(this.name + '11');}} let zzy = new Student('zzy',18);console.log(zzy.age); //18console.log(zzy.name); //zzyzzy.speak(); //zzy 18zzy.say(); //zzy11
1、子类在继承父类的方法后,可以重写父类的方法,也可以添加其他方法,super必须在子类的this之前调用。
2、其实super也可以理解成改变父类构造函数中的this指向,让this指向子类的实例。
3、所有子类继承的父类的属性都写到super里,子类独有的要在super后添加
4、如果父类和子类属性一致,子类的constructor可以省略
class Person {constructor(name,age) {this.name = name;this.age = age;}speak() {console.log(this.name, this.age);}}class Student extends Person {constructor(name,age,sex) {// super调用父类构造函数,也可以理解成让父类中this指向子类的实例super(name,age); //super表示和父类重复的属性,this.sex = sex; //添加子类独有的属性}//添加子类独有的方法say() {console.log(this.name + '666');}//重写父类的方法speak() {console.log(this); //Studentconsole.log(this.name, this.age, this.sex);}} let zzy = new Student('zzy',18,'男');console.log(zzy.name); //zzy super让父类中的this指向子类的实例了console.log(zzy.sex); //男zzy.say(); //zzy666zzy.speak(); //zzy 18 男
通过super.函数名() 调用。
如果子类继承父类,子类和父类中有同名方法,那么子类中的实例调用时优先调用子类中的方法,如果子类没有该方法,才去调用父类的该方法。
class Father {say() {return '我是爸爸';}
}class Son extends Father {say() {console.log(super.say() + '的儿子');}
}let son = new Son();
son.say(); //我是爸爸的儿子
//就近原则,如果子类有方法,优先调用子类,子类没有才去调用父类里的
永远记住,谁调用函数,this就指向谁。不过下面这个如果我要调用公共方法,用箭头函数怎么解决this的问题?奇怪。其实可以用bind也行, this.btn.onclick = this.sing.bind(this)。这样调用的话,sing里面的this指向的就是constructor里面的this
let that;
class Father {constructor(uname, uage) {that = this;this.name = uname;this.age = uage;// this.sing();this.btn = document.querySelector('button');this.btn.onclick = this.sing; //这个this指向的是btn//谁调用方法,this就指向谁}sing() {console.log(this); //console.log(that.name); //zzy}
}let f = new Father('zzy', 18);
f.sing();
学完vue再来敲原生js感觉真的是非常麻烦,vue一行就能搞定的事情原生js要写几十行。
这个案例值得学习的地方就是老师很多代码的思路,比如利用逻辑短路去做优化。
可以去github下载:https://github.com/ForMyselfs/tabSwitch-by-nativeJs
var that;
class Tab {constructor(id) {that = this;//获取大的容器this.main = document.querySelector(id);//获取添加按钮this.add = this.main.querySelector('.tabadd');this.ul = this.main.querySelector('.fisrstnav ul');this.fsection = this.main.querySelector('.tabscon');this.init();}//获取标题和内容节点(由于更新dom需要重新获取,所以要单独定义个方法)updateNode() {this.lis = this.main.querySelectorAll('li');this.sections = this.main.querySelectorAll('section');//获取删除按钮this.remove = this.main.querySelectorAll('.iconfont');//获取修改元素this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child');}//0.初始化操作,让相关的元素绑定事件init() {this.updateNode(); //重新获取更新后的节点//0.1切换绑定事件for (let i = 0; i < this.lis.length; i++) {this.lis[i].index = i; //给每个li添加index属性保存索引值this.lis[i].onclick = this.toggleTab;this.remove[i].onclick = this.removeTab; /* 删除绑定事件 */this.spans[i].ondblclick = this.editTab; //编辑功能this.sections[i].ondblclick = this.editTab; //编辑功能}//0.2添加绑定事件this.add.onclick = this.addTab;}//1.切换toggleTab() {//排他思想that.clearClass();this.className = 'liactive';that.sections[this.index].className = 'conactive';}clearClass() {for (let i = 0; i < this.lis.length; i++) {this.lis[i].className = '';this.sections[i].className = '';}}//2.添加addTab() {that.clearClass();let random = Math.random();//2.1创建元素let li = `测试${random} `let section = `测试${random} `//2.2追加到父元素中that.ul.insertAdjacentHTML('beforeend', li);that.fsection.insertAdjacentHTML('beforeend', section);//2.3重新初始化,获取节点并绑定事件(不重新初始化,获取的还是旧DOM)that.init();}//3.删除removeTab(e) {e.stopPropagation(); //阻止事件冒泡到li的切换,只删除就行let index = this.parentNode.index;that.lis[index].remove();that.sections[index].remove();that.init(); //重新获取更新后的DOM,避免出现不必要的问题//删除之后让它前边那个卡高亮if(document.querySelector('.liactive')) return;index--;that.lis[index] && that.lis[index].click(); //逻辑短路,如果index=-1就不点击}//4.修改editTab() {let value = this.innerHTML;// 双击禁止选定文字window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();//双击生成文本框this.innerHTML = `value} >`;let input = this.children[0];input.select();//离开文本框时把文本框的值给spaninput.onblur = function() {this.parentNode.innerHTML = this.value; }input.onkeyup = function(e) {if(e.key === 'Enter') {this.blur();}}}
}new Tab('#tab');
每次new一个实例,就会单独开辟一个内存空间存放实例中的sing函数,如果我们new一百个实例,就要开辟100个内存空间来存放sing函数,这显然很low
function Star(name, age) {this.name = name;this.age = age;this.sing = function() {console.log('我会唱歌');}
}let ldh = new Star('刘德华','18');
let zxy = new Star('张学友','17');
console.log(ldh.sing === zxy.sing); //false…………
1、原型对象prototype可以解决上面的问题,每一个构造函数都有一个prototype原型对象。
2、一般情况下,公共属性定义在构造函数中,公共方法定义在原型对象上。
3、我们可以把那些不变的方法,直接定义在prototype对象上,这样所有的实例都可以共享这些方法,不需要再另外开辟内存空间。
4、可以看到,写到原型对象上后,ldh.sing === zxy.sing就true了
function Star(name, age) {this.name = name;this.age = age;
}Star.prototype.sing = function() {console.log('sing');
}let ldh = new Star('刘德华','18');
let zxy = new Star('张学友','17');
console.log(ldh.sing === zxy.sing); //true!!!
对象身上系统会自动添加__proto__属性,这个属性会自动指向构造函数的原型对象
console.log(ldh.__proto__ === Star.prototype) //true
方法的查找规则:
1、首先看实例上有没有sing方法, 如果有就执行。
2、如果实例没有sing方法, 因为有__proto__的存在,就通过__proto__去构造函数的prototype身上去查找sing这个方法。
3、实际开发中不使用__proto__这个属性,它只是提供了一条路线指向prototype

对象的原型(__ proto__)和构造函数的原型对象(prototype)里都有一个属性constructor,我们成为构造函数,因为它指向构造函数本身(无限套娃,原型对象里有constructor,constructor里是构造函数,构造函数又有原型对象,原型对象又有constructor…………)。

有的时候我们需要手动让原型对象重新指向构造函数,比如我们要重写prototype对象
function Star(name, age) {this.name = name;this.age = age;
}Star.prototype = {constructor: Star, //这句话如果不加,就不知道实例指向谁了sing: function () {console.log('sing');}
}let ldh = new Star('刘德华', '18');
console.log(ldh.__proto__); //加constructor:{constructor: ƒ, sing: ƒ}
console.log(ldh.__proto__); //不加constructor:{sing: ƒ}
不过这个地方有点没搞懂constructor有什么用。。。目前的理解是可以通过constructor找到当前对象指向谁。
三角关系:

理解下面这个图,原型链你就明白了

console.log(Star.prototype.__proto__ === Object.prototype); //true
console.log(Object.prototype.__proto__); //null
对象成员的查找规则:先从自身找,如果没有 => 通过__proto__向上找原型对象,如果没有 => 继续通过__proto__向上找原型对象,一直找到null => 如果都没就是undefined
console.log(ldh.dj); //undefined
比如我可以在数组对象中添加一个求和的方法,数组就可以直接调用
Array.prototype.sum = function() {let sum = 0;//这里面的this指向的是函数的调用者for (let i = 0; i < this.length; i++) {sum += this[i];}return sum;
}let arr = [1,2,3,4,5];
console.log(arr.sum()); //15
下一篇:期望太多,失望就太多。