JS高级(一):类、原生tab栏切换、原型和原型链
创始人
2024-01-22 20:24:03

JavaScript高级(一)

  • 一、类(之前ES6学过)
    • 1.类的用法
    • 2.类的继承
      • 2.1 extends关键字
      • 2.2 super关键字
        • (1)子类属性和父类一致,constructor可以省略
        • (2)子类要添加属性,必须要先写super
        • (3)super关键字调用父类中的普通函数
    • 3.类中this的指向问题
  • 二、面向对象tab栏
  • 三、原型和原型链
    • 1.构造函数中的方法浪费内存的问题
    • 2.原型对象
    • 3.对象的__proto__属性
    • 4.原型中的constructor属性
    • 5.原型链
    • 6.利用原型对象扩展内置对象的方法

一、类(之前ES6学过)

抽取了对象的公共部分,它泛指某一大类
对象特指某一个通过类实例化出来的东西

1.类的用法

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() {}
}

2.类的继承

2.1 extends关键字

Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。

class Father {
}class Son extends Father {
}

2.2 super关键字

1、如果父类和子类属性一致,子类的constructor可以省略
2、子类在继承父类的方法后,可以重写父类的方法,也可以添加其他方法,super必须在子类的this之前调用。
3、其实super也可以理解成改变父类构造函数中的this指向,让this指向子类的实例。
4、所有子类继承的父类的属性都写到super里,子类独有的要在super后添加

(1)子类属性和父类一致,constructor可以省略

如果子类和父类在构造器函数中的属性一致,那么子类中的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

(2)子类要添加属性,必须要先写super

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 男

(3)super关键字调用父类中的普通函数

通过super.函数名() 调用。
如果子类继承父类,子类和父类中有同名方法,那么子类中的实例调用时优先调用子类中的方法,如果子类没有该方法,才去调用父类的该方法。

class Father {say() {return '我是爸爸';}
}class Son extends Father {say() {console.log(super.say() + '的儿子');}
}let son = new Son();
son.say();  //我是爸爸的儿子   
//就近原则,如果子类有方法,优先调用子类,子类没有才去调用父类里的

3.类中this的指向问题

永远记住,谁调用函数,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();

二、面向对象tab栏

学完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');

    三、原型和原型链

    1.构造函数中的方法浪费内存的问题

    每次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…………
    

    2.原型对象

    1、原型对象prototype可以解决上面的问题,每一个构造函数都有一个prototype原型对象。
    2、一般情况下,公共属性定义在构造函数中,公共方法定义在原型对象上。
    3、我们可以把那些不变的方法,直接定义在prototype对象上,这样所有的实例都可以共享这些方法,不需要再另外开辟内存空间。
    4、可以看到,写到原型对象上后,ldh.sing === zxy.singtrue

     
    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!!!
    

    3.对象的__proto__属性

    对象身上系统会自动添加__proto__属性,这个属性会自动指向构造函数的原型对象

    console.log(ldh.__proto__ === Star.prototype)  //true
    

    方法的查找规则:
    1、首先看实例上有没有sing方法, 如果有就执行。
    2、如果实例没有sing方法, 因为有__proto__的存在,就通过__proto__去构造函数的prototype身上去查找sing这个方法。
    3、实际开发中不使用__proto__这个属性,它只是提供了一条路线指向prototype

    在这里插入图片描述

    4.原型中的constructor属性

    对象的原型(__ 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找到当前对象指向谁。

    三角关系:

    在这里插入图片描述

    5.原型链

    理解下面这个图,原型链你就明白了
    在这里插入图片描述

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

    对象成员的查找规则:先从自身找,如果没有 => 通过__proto__向上找原型对象,如果没有 => 继续通过__proto__向上找原型对象,一直找到null => 如果都没就是undefined

    console.log(ldh.dj);  //undefined
    

    6.利用原型对象扩展内置对象的方法

    比如我可以在数组对象中添加一个求和的方法,数组就可以直接调用

    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
    

    相关内容

    热门资讯

    埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
    最古之史实为方志 左传三不朽 学习时报人生的价值究竟是什么?用什么来衡量人生价值?这是一个曾被无数人问过且继续为无数人追问的话题,...
    苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
    北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
    应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
    脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
    长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
    猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
    世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
    埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
    开山种田打一字谜 开山创始人打... 贵阳市花果园半山小镇兰花坡森林公园——居民开山种地 公园变成菜园山林菜地从山脚一直延伸至山顶,面积近...
    北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
    苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...