面向对象三大特征: 封装 、 继承 、 多态
封装 : 把代码放入对象的方法中
继承 : 一个对象(子对象) 拥有 另一个对象(父对象) 所有的成员(属性和方法) js的重点: 继承
多态 : 一个对象 在不同情况下的不同状态(js语言基本不涉及多态,主要是java)
何为原型继承? 把父对象 作为子对象构造函数的原型对象.
语法:子对象.prototype = 父对象
// 父对象
let father = {
adress: {
adress: '剑气长城',
price: '无价'
},
love: {
name: '小米粒',
price: '无价'
}
}
// 子对象构造函数
function Son(name, age) {
this.name = name
this.age = age
}
// 原型继承: 把父对象 作为子对象构造函数的原型对象
Son.prototype = father
let s1 = new Son('平安', 18)
let s2 = new Son('宁姚', 17)
console.log(s1, s2);
原型链
原型链 : 每一个对象都有自己的原型, 而原型也是对象, 也有自己的原型, 以此类推, 形成链式结构.
解释一下上面这句话: 每一个对象都有自己的原型(__proto__),即对象.__proto__,而对象.__proto__,指向了原型对象, 原型对象也是对象, 所以她也有自己的原型 __proto__, 依次类推, 形成了链式结构.
对象访问原型链规则: 当访问实例对象的成员(属性和方法时),先访问自身的, 自身没有就去找原型, 原型也没有, 就找原型的原型.以此类推直到原型链终点(null), 如果还找不到, 如果访问的是属性, 则获取Undefined, 如果找的是方法没找到, (假设找的是Son.learn()这个方法,Son.learn属性返回的是Undefined, 所以Son.learn() == undefined() , undefined没有()语法, 从而报错), 报错显示的是Son.learn is not a function.
以下面这段代码来画出原型链的图:
//(1)构造函数
function Person(name,age){
this.name = name
this.age = age
}
//(2)原型对象
Person.prototype.love = function(){
console.log('cxp')
}
//(3)实例对象
let p1 = new Person('陈平安',19)
有一些构造函数是我们自己定义的, 有些是内置的,我们有个最大的构造函数
function Object(), 不需要我们写,系统给我们内置好了
每一个对象(数组、字符串等)的原型链都会经过Object.prototype,因为原型对象是创建构造函数时, 系统帮忙创建的,而Object又已经是最大的构造函数,所以这里的系统就可以理解为是Object, 即原型对象是创建构造函数时, Object这个构造函数帮忙创建的,所以最大的构造函数Object,其原型对象Object.prototype都会被不同类型(数组、字符串、Number、Boolean、Date等)的对象的原型链经过.
面试题
原型链作用: 继承用的
js用什么技术实现面向对象的继承: 原型链
补充点额外知识
- DOM对象的原型链
2. instanceof(关键字): 检测构造函数的原型在不在实例对象的原型链中.
语法:实例对象 instanceof 构造函数
注意:instanceof的左边一定要是实例对象.
不能这么写
'abc' instanceof String 因为abc是值类型,除非改为new String('abc') instanceof String
可以这么写 let arr = [10, 20, 30 ]
arr instanceof Array :// 底层原理(new Array([10, 20, 30 ]已经把arr变成实例对象了)
面试点instanceof运算符原理: 检测右边构造函数原型 在不在 左边实例对象的原型链中.
应用: 封装某些函数的时候,为了限制参数的数据类型。可以用instanceof做一个判断.
//写一个数组,把数组翻转成字符串
function reverseStr(arr) {
//判断用户传的是不是arr
if (arr instanceof Array) {
//(1)把数组翻转
arr.reverse()
//(2)数组元素拼接成字符串
console.log(arr.join(''))
} else {
console.log(arr)
}
}
reverseStr(['夏', '华', '爱', '我']) //我爱华夏 可以翻转
reverseStr(20) //参数错误 不能翻转,但是不会报错
</script>
3. 构造函数和原型函数的this都指向实例对象
先验证构造函数里的this指向:
<script>
let that
// 构造函数
function tryHard(uname) {
that = this
this.uname = uname
}
// 实例对象 ldh
// 构造函数里面的 this 就是 实例对象 ny
const ny = new tryHard('陈平安')
console.log(that === ny) //true 验证了构造函数里的this指向实例对象
</script>
再验证原型对象里的this指向:
<script>
let that
// 构造函数
function tryHard(uname) {
//that = this
// console.log(this)
this.uname = uname
}
// 原型对象里面的函数this指向的还是 实例对象 ny
tryHard.prototype.xixi = function () {
that = this
}
// 实例对象 ny
// 构造函数里面的 this 就是 实例对象 ny
const ny = new tryHard('陈平安')
ny.xixi() // 先调用一次,执行原型对象里的代码, 进行赋值
console.log(that === ny) //true 验证了原型对象里的this也指向实例对象
</script>
4. arguments关键字: 获取函数所有的 实参,只能用在函数里哦
argument获取的实参都放在了数组里,是一个伪数组.
function fn(a, b) {
return (arguments)
}
console.log(fn(10, 20, 30, 40, 50));
这个伪数组的原型指向Object, 而操作数组的一些常用方法并不在Object里,所以伪数组无法使用数组的一些方法.
应用 : 用于参数数量 不确定的函数.
例如: arr.push()、Math.max() 这些方法可以传很多个参数,在函数内部就需要用 arguments 来获取所有的实参.
小技巧: 当我们将鼠标放在传参的函数上,vscode会出现一个特殊的提示...,看到这个表示,就可以认为这个函数传的参数不限制个数.
5.剩余参数(rest) : 获取函数 剩余的实参
语法: a,...b
意思是把传进来的实参, 第一个实参给a, 剩下的全部给b, b存的是一个真数组, 如果把a省略, 则几乎和arguments功能相同, 只是一个得到的是真数组, 一个是伪数组而已, 剩余参数(...)只能写在最后位置.
6. var const let
var的特点 :
预解析: var声明提前到当前作用域最前端
console.log( num )
var num = 10
console.log( num )
上述代码实际执行顺序是(即预解析了num):
var num
console.log( num )
num = 10
console.log( num )
没有块级作用域: 分支和循环里面的变量都是全局
目前var在前端里基本不怎么用.
const 和 let 的特点:
相同点:
- 变量一定要先声明,才能使用
- 有块级作用域: 分支和循环里面的变量都是局部的 不同点:
- let 变量, 可以修改
- const 常量, 不可修改, 只有在声明的时候进行赋值
面试题
/* for-in与for-of区别
1. 功能不同
for-in : 会遍历下标和元素
for-of : 只会遍历元素
2. 原型不同
for-in : 会遍历原型中的属性
for-of : 不会遍历原型的属性
3. 数据类型不同
for-in : 能遍历数组和对象
for-of : 只能遍历数组
<script>
//数组
let arr = [10, 20, 30] //下标0 1 2
Array.prototype.cxp = 521
//for-in
for (let key in arr) {
console.log(key) //属性名
console.log(arr[key]) //属性值
}
// //for-of
for (let item of arr) {
console.log(item)
}
</script>