JS高级02
回顾
面向过程编程: 就是把代码一步步写出来逐行执行, 没有任何的复用和封装性
面向对象编程: 是对象在调用各种功能方法, 封装类进行复用
类(模板)/属性/方法:
class 类名 {
constructor (形参1, 形参2){
this.属性名 = 形参1;
this.属性名 = 形参2;
}
方法名1() {
}
方法名2() {
}
}
实例对象 (拥有类里的属性和方法)
new 类名(实参1, 实参2);
new的作用
- 创建一个空白对象{}
- 执行并替代constructor里this的值
- 在this(空白对象{})身上添加属性和对应的值
- constructor自动返回这个对象到调用处
this指向当前函数调用者, constructor指向实例对象
extends用于继承, 如果子类有constructor需要在使用this之前, 调用super()确认父类里的属性和方法
在子类的普通方法里, 还可以使用super.父类普通方法()来调用父类的方法
实例成员指的是new 类名() 创建出的实例对象
封装tab栏插件
为了高薪铺垫
6. 构造函数
学习目标:
-
目的: class的本质
-
语法: function 大写开头函数名() { this身上绑定属性 } - 相当于class的constructor
-
使用: new 大写开头函数名()
6.0 构造函数_基础
什么是构造函数
ES6新增class定义类, 但是之前ES5只能用构造函数+原型来模拟
// 定义构造函数(类)
function Star(theName, theAge) {
// 定义属性和方法
this.uName = theName;
this.age = theAge;
this.sing = function(){
console.log("学习唱歌");
}
}
// new实例化对象
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
// 调用属性和方法
console.log(ldh);
console.log(ldh.uName);
ldh.sing();
// 总结:
// 构造函数大写名字开头(为了和普通函数区分)
// 用this来添加属性
// 用new来触发构造函数, 可以传参进去, 必会返回一个对象(回顾new的作用), 不用new 就是个普通函数
6.1 实例成员和静态成员
function Star(theName, theAge) {
// 实例成员
this.uName = theName;
this.age = theAge;
this.sing = function(){
console.log("学习唱歌");
}
}
// new实例化对象
var ldh = new Star("刘德华", 18);
// 静态成员
// (1): 函数名本身就是个对象
// (2): 在构造函数身上添加的属性和方法 - 都属于静态成员
Star.hobby = "唱歌";
Star.play = function(){
console.log("去打架子鼓");
}
// 调用时的区别 (口诀:谁绑定谁调用)
console.log(ldh.uName);
ldh.sing();
console.log(Star.hobby);
Star.play();
// 总结: 添加在构造函数身上的叫静态成员, 在构造函数内绑定在this身上的叫实例成员 (成员=属性+方法)
6.2 练习
请定义一个构造函数Person, 有name, height, weight, homeTown, sing方法. 方法中打印一句话"学习唱歌", 并实例化2个对象打印即可.
function Person (tName, tHeight, tWeight, tHomeTown) {
this.name = tName;
this.height = tHeight;
this.weight = tWeight;
this.homeTown = tHomeTown;
this.sing = function(){
console.log("学习唱歌");
}
}
var per1 = new Person("孙悟空", 180, 140, "花果山");
var per2 = new Person("猪八戒", 150, 280, "高老庄");
console.log(per1.height); // 180
console.log(per2.sing); // function(){console.log("学习唱歌");}
per2.sing();
7. 原型
7.0 内存浪费问题
实例对象this上属性的值 - 是函数时, 会造成内存浪费
function Star(theName, theAge) {
this.uName = theName;
this.age = theAge;
this.sing = function(){
console.log("学习唱歌");
}
}
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
console.log(ldh.sing === zxy.sing); // false (证明2个方法的内存地址是不相等的, 也就证明在内存中有2个sing方法存在哦)
// 注意: 判断sing的值是否相等, 不要写成了sing()
// 总结:
// 在构造函数中属性的值如果是函数, 会造成内存浪费
7.1 构造函数原型 prototype
构造函数通过原型属性分配的函数是所有实例对象所共享的
构造函数天生自带的属性: 构造函数.prototype
function Star(theName, theAge) {
this.uName = theName;
this.age = theAge;
}
Star.prototype.sing = function(){
console.log("学习唱歌");
}
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
console.log(ldh.sing === zxy.sing); // true (代表内存地址相同, 指向的同一个函数体)
// 总结:
// 在原型属性上添加, 让所有实例对象共享, 可以保证内存的不浪费
// 属性写在构造函数内, 函数绑定在prototype上
// 为什么sing在prototype身上, 实例对象也能直接访问到sing方法呢? 往下看
7.2 对象原型__proto__
对象天生自带的属性, 为了让对象能调用更过的方法, 这是内置属性无需手动调用
function Star(theName, theAge) {
this.uName = theName;
this.age = theAge;
}
Star.prototype.sing = function(){
console.log("学习唱歌");
}
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
console.log(ldh.sing === zxy.sing); // true (代表内存地址相同, 指向的同一个函数体)
ldh.sing();
console.log(ldh.__proto__ === Star.prototype); // true
// 查找规则: 如果对象本身没有这个方法, 会通过对象的__proto__向上查找, 如果有则使用
// 总结:
// 因为对象.__proto__ 指向 构造函数.prototype 所以对象能访问prototype上的方法
因为无需显示写出调用, 所以
__proto__
也叫隐式原型
必须张口就来的话: 实例对象.__proto__
等于(指向) 构造函数.prototype
7.3 prototype原型对象的constructor属性
constructor它称作构造函数, 它的值指回当前构造函数
function Star(theName, theAge) {
this.uName = theName;
this.age = theAge;
}
// 1. 打印constructor属性的值
console.log(Star.prototype.constructor); // Star
console.log(Star === Star.prototype.constructor) // true
var ldh = new Star("刘德华", 18);
// 总结
// 这个属性一般查看某个对象是由哪个构造函数创建出来的 ldh.__proto__.constructor
console.log(ldh.__proto__.constructor);
构造函数, 实例对象, 原型
铁三角关系
JS内部对象的组成关系
7.4 原型链
对象通过__proto__
逐级向上查找的过程就叫原型链
function Star(theName, theAge) {
this.uName = theName;
this.age = theAge;
}
Star.prototype.sing = function(){
console.log("学习唱歌");
}
var ldh = new Star("刘德华", 18);
ldh.sing();
console.log(ldh.toString());
console.log(ldh.__proto__ === Star.prototype) // true
console.log(Star.prototype);
console.log(Star.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__); // null
查找过程
- ldh对象本身
- ldh的
__proto__
(指向的Star.prototype) - ldh的
__proto__
的``proto` 指向的对象 (Object.prototype) - 找到就使用 - ldh的
__proto__
的__proto__
的__proto__
的对象找 (发现是null) - 到顶了还没找到要用的方法 - JS直接抛出错误到控制台: xxxx.xxx is not a function
7.5 instanceof 使用
含义: instanceof检测构造函数的 prototype是否出现在某个实例对象的原型链上
作用: 我们可以判断一个对象是否是由某个构造函数创建出来的
语法: 实例对象 instanceof 构造函数
// 1. 如何判断一个对象是否属性某个构造函数呢
var arr = []; // 相当于new Array(), []写法叫字面量(语法糖)
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
function Star(theName, theAge) {
this.uName = theName;
this.age = theAge;
}
Star.prototype.sing = function () {
console.log("学习唱歌");
}
var ldh = new Star("刘德华", 18);
console.log(ldh instanceof Star); // true
// 2. 判断对象是否由Star构造函数创建的
console.log(ldh instanceof Star); // true
// 总结
// 用来判断某个对象是否由某个构造函数创建的
7.6 this的指向
function Star(theName, theAge) {
this.uName = theName;
this.age = theAge;
}
Star.prototype.sing = function(){
console.log(this.uName + "在" + this.age + "岁唱歌");
}
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
ldh.sing();
zxy.sing();
// 总结
// 看到this, 先看所在function函数(所以把函数看成一个整体)的名字
// 再找到这个函数名.前面那个人就是调用者(this的值就是它) - new特殊记忆
7.7 利用prototype扩展方法(高级)
原理: 用到了原型链
对象本身没有方法, 会在构造函数.prototype上去查找使用
// 构造函数.prototype上添加方法
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
// 以后实例对象, 能够调用prototype上的方法使用
var arr = [1, 2, 3];
console.log(arr.sum());
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum());
7.8 练习
练习1: 定义ChuanTool构造函数
属性接受2个数字(numA, numB)
定义2个方法(getMax, randomNum)
getMax方法返回numA和numB其中比较大的值
randomNum方法返回一个随机数(范围就是numA到numB, 假设numA是小于numB的)
练习2: 创建实例对象, 给属性传值, 然后调用getMax和randomNum方法, 接收结果打印
function ChuanTool(theA, theB){
this.numA = theA;
this.numB = theB;
}
ChuanTool.prototype.getMax = function(){
return Math.max(this.numA, this.numB);
}
ChuanTool.prototype.randomNum = function(){
return Math.floor(Math.random() * (this.numB - this.numA + 1) + this.numA);
}
var toolObj = new ChuanTool(10, 100);
console.log(toolObj.getMax());
console.log(toolObj.randomNum());
总结
- 函数有prototype, 让所有实例对象共享方法使用, 节省内存
- 对象有
__proto__
, 让对象调用更多的属性和方法 - 对象通过
__proto__
逐级查找的过程叫原型链 - 对象.
__proto__
指向 构造函数.prototype - this指向当前函数的调用者
学习目的
- 了解JS对象查找的机制, 明白内部运作的过程 (有助于理解和找错)
- 能够在内置/别人的构造函数上扩展额外的方法, 让我们的实例对象能够调用更强大的方法
8. 继承
8.0 call方法
- 马上调用前面函数执行
- 本次改变函数内this的指向为参数1的值
// call 方法
function fn(x, y) {
console.log('我想喝手磨咖啡');
console.log(this);
console.log(x + y);
}
var o = {
name: 'andy'
};
// fn();
// 1. call() 可以调用函数
// fn.call();
// 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2);
console.log(o);
8.1 寄生组合继承 - 属性
属性的继承: 在子构造函数里用 父构造函数.call(this)
// 1. 父构造函数(父类)
function Father(theName, theAge){
this.uname = theName;
this.age = theAge;
}
// 2. 子构造函数(子类)
function Son(uname, uage, uhobby){
Father.call(this, uname, uage); // 属性继承
this.hobby = uhobby;
}
var son = new Son("刘德华", 18, "唱歌");
console.log(son.name);
console.log(son.age, son.hobby);
8.2 Object.create方法
**Object.create()
**方法创建一个新对象,使用传入的对象来提供新对象的__proto__
function Father(){
}
Father.prototype.money = function(){
console.log(10000);
}
// 作用:
// (1): 创建一个新对象
// (2): 以参数作为新对象的__proto__的值使用
var newObj = Object.create(Father.prototype);
console.log(newObj);
console.log(newObj.__proto__ === Father.prototype); // true
8.3 寄生组合继承 - 方法
方法的继承:子类.prototype = Object.create(父类.prototype)
function Father(theName, theAge) {
this.name = theName;
this.age = theAge;
}
Father.prototype.money = function(){
console.log(10000);
}
function Son(uname, uage, uhobby) {
// 属性继承
Father.call(this, uname, uage);
this.hobby = uhobby;
}
// 方法继承
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
Son.prototype.exam = function(){
console.log("考试");
}
var son = new Son("刘德华", 18, "唱歌");
console.log(son.name, son.age, son.hobby);
son.exam();
var fa = new Father("刘爸爸", 50);
console.log(fa);
还需要ppt画图来解释哦
8.4 类的本质
class 本质是 构造函数function
class拥有的, 构造函数也拥有
// ES6 之前通过 构造函数+原型实现面向对象 编程
// (1) 类的prototype
// (2) 类的prototype, 里面有constructor属性, 指向构造函数本身
// (3) 类里的方法, 都会挂载到prototype上
// (4) 类的实例对象__proto__ 指向 构造函数的prototype
class Star {
constructor(tName, tAge) {
this.uname = tName;
this.uage = tAge;
}
sing() {
console.log('学习唱歌');
}
}
// 1. 类的本质其实还是一个函数 我们也可以简单的认为 类就是 构造函数的另外一种写法
// (1) 类有原型对象prototype
console.log(Star.prototype);
// (2) 类原型对象prototype 里面有constructor 指向类本身
console.log(Star.prototype.constructor);
// (3) 类的prototype的值上 都是方法
console.log(Star.prototype)
var ldh = new Star();
// (4) 类创建的实例对象有__proto__ 原型指向 类的原型对象
console.log(ldh.__proto__ === Star.prototype);
经验值
问题:构造函数 - 实例化报错
- 描述:
-
分析:
- 控制台报错,直接找到报错的位置
var person_1 = New Star("zhangs", 18);
- 报错分析:意外?哪里意外了?
Unexpected identifier:意外的标识符
- 函数名大写开头, 但是new是小写的
-
解决:
new Star("zhangs", 18);
-
总结:语法记忆一定要清晰,不要张冠李戴呦
问题:构造函数 - prototype - 设置方法,实例对象调用报错
- 描述:prototype上设置方法sing,实例化p1调用时有报错找不到该函数;
-
分析:
- 报错提醒在19行,查看19处代码:
p1.sing();
- p1只是个实例化对象,如果实例化对象报错找不到sing这个方法,那么意味着其构造函数的原型对象上该方法设置有问题
- 打印
p1.__proto__
:发现其__proto__
对象上此时是一个名为sing的函数方法;
- 实例对象.
__proto__
指向 构造函数.prototype 但是这里不是构造函数.prototype的原型对象, 直接变成了一个函数, 瓦特? 这出问题了, 解决它 - 绑定方法的代码如下:
Star.prototype = function sing() { console.log(1); }
- prototype默认为构造函数的原型对象,是一个对象,上面的代码对这个属性名重新赋值为函数,所以会报错
- 报错提醒在19行,查看19处代码:
-
解决:prototype默认为构造函数的原型对象,原型对象上添加新的方法为:
Star.prototype.sing = function() {
console.log(1);
}
- 总结:各语法都有基本的规则和用法;在使用的过程中应该记忆一些核心的规则;
问题:数组 - prototpye - 添加求和方法,使用不报错, 无结果
-
描述:输出为undefined
- 分析:
-
顺藤摸瓜, 在22行打印的, 调用getSum()返回的结果是个undefined
-
那getSum()是调用函数, 函数内得用return返回值, getSum()原地才有值, 否则就是undefined
-
所以来到getSum函数体内(看好function的大括号在哪里一对), 发现内部末尾没有return
-
加上return sum; 以后, 发现返回值竟然是0, 这时, 需要顺一下getSum方法里代码执行的过程和结果了
-
继续顺sum的值在哪里来的, 发现是16行累加的, 在16行和for之间打印sum - 发现打印没执行
-
再接着看, 因为for循环没进来才没打印, 接着看arr的值发现是空数组
-
但是我们想计算的是testArr数组, 那么testArr本身就是实例对象(因为[]是语法糖, 本质是new Array())
-
然后实例对象调用getSum() 那么在getSum内部this就是testArr
-
所以15行arr可以换成this, 16行换成this[i]
-
- 解决:需求为函数内部获取实例化对象;关键字this;
Array.prototype.getSum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i]
}
return sum;
}
- 总结:
- 初次使用构造函数及其原型对象上函数内部的this,同学会感到陌生,这里算是语法规则,大家需要记忆;
- 这里的this为:为了某次的实例化对象,所以对象上属性和方法都可以使用;
- 一定要在函数第一行写上注释, 代表this的值是谁
- 口诀: this的值指向当前函数的调用者
面试题
什么叫语法糖
是一种编写代码的语法, 写更少的代码, 实现更多的功能
[] // 相当于 new Array()
{
} // 相当于 new Object()
var i = 10;
i++; // 相当于 i = i + 1; (i++就是一种语法糖)
ES6的继承和ES5的继承有什么区别
es5用function函数定义类, 继承是通过prototype和call来实现
es6用class关键字定义类,类之间通过extends和super关键字实现
typeof和instanceof 区别
判断一个变量的类型可以用typeof
typeof 数字 / 字符串 / 布尔 / undefined 返回都是当前类型的字符串
typeof 函数 返回的是function字符串
typeof 对象 / 数组 / null 返回都是object
在javascript中,instanceof用于判断某个对象是否被另一个函数构造。更精确区分数组和对象
如何准确判断一个变量是数组类型?
变量 instanceof Array
说说你对原型(prototype)理解(必会)
JavaScript是一种通过原型实现继承的语言
JavaScript是的动态的弱类型语言
JavaScript中所有都是对象,原型也是一个对象,通过原型可以实现对象的属性继承
原型的主要作用就是为了实现继承与扩展对象
简单说说js中的继承(必会)
在子构造函数中, 使用call/apply方法, 把父构造函数中的属性蹭下来
再让子构造函数.prototype指向父构造函数的实例对象(并且确保子构造函数.prototype.constructor = 子构造函数
常见的继承方法(必会)
寄生组合式继承
思路:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
在子构造函数里: 父类.call(this),
子类.prototype = Object.create(父类.prototype)
extend()方法才用了这样的方式
of 区别
判断一个变量的类型可以用typeof
typeof 数字 / 字符串 / 布尔 / undefined 返回都是当前类型的字符串
typeof 函数 返回的是function字符串
typeof 对象 / 数组 / null 返回都是object
在javascript中,instanceof用于判断某个对象是否被另一个函数构造。更精确区分数组和对象
如何准确判断一个变量是数组类型?
变量 instanceof Array
说说你对原型(prototype)理解(必会)
JavaScript是一种通过原型实现继承的语言
JavaScript是的动态的弱类型语言
JavaScript中所有都是对象,原型也是一个对象,通过原型可以实现对象的属性继承
原型的主要作用就是为了实现继承与扩展对象
简单说说js中的继承(必会)
在子构造函数中, 使用call/apply方法, 把父构造函数中的属性蹭下来
再让子构造函数.prototype指向父构造函数的实例对象(并且确保子构造函数.prototype.constructor = 子构造函数
常见的继承方法(必会)
寄生组合式继承
思路:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
在子构造函数里: 父类.call(this),
子类.prototype = Object.create(父类.prototype)
extend()方法才用了这样的方式
如有不足,请多指教,
未完待续,持续更新!
大家一起进步!