面向对象是一种“编程思想 [OOP:Object-Oriented Programming]”,在此思想中,最主要的就是:对象、类、实例
基本概念
对象:一种泛指,JS中所有的内容都是我们要学习、研究和使用的对象
类:把所有的内容,按照功能特征进行划分,分为一些大类(父类)和小类(子类)
实例:类中具体的实物,其具备一些自己私有的属性和方法,也拥有类赋予其的公有的属性和方法
类(构造函数:所有的类本身都是函数数据类型)
内置类
- 数据类型内置类:Number、String、Boolean、Symbol、BigInt、Object、Array、RegExp、Date、Error、Function…(每一种数据类型值,都是自己所属类的实例)
DOM相关的内置类
- 元素集合类:HTMLCollection
- 节点集合类:NodeList
- 每一个元素标签(或元素对象)都有自己所属的类:HTMLBodyElement、HTMLDivElement、HTMLParagraphElement、HTMLAnchorElement…
详细的大类分小类的链条HTMLXxxElement—>HTMLElement—>Element—>Node—>EventTarget—>Object
IntersectionObserver
- Promise
- …
- 自定义类
- 创建一个函数[约定的规范:函数名遵循PascalCase规范]
- 基于new来执行这个函数
- 此时这个函数就变成了”构造函数(类)”
- 返回值一般都是这个类的实例
:::info
前端的命名方式:
1. kebab-case 例如:class=”index-box”
2. camelCase 例如:let indexBox = null 小驼峰命名法
3. PascalCase 例如:function IndexBox(){} 帕斯卡尔命名「大驼峰」
- 创建实例的方式
- 字面量方式[含基于特定API创建]
- 基于new创建[构造函数方式]
对于对象数据类型来讲,即便基于不同方式,创造出来的结果都一样:都是某个类的实例、都是对象数据类型的值
let arr1=[10,20,30]
let arr2=Array.of(10,20,30)
let arr3=new Array(10,20,30)
console.log(arr1,arr2,arr3)
但是对于原始类型来讲,基于字面量方式创造的是原始值,不是所属类的实例;基于构造函数方式(或者基于Object([value])这种方式)创造出来的是“非标准特殊对象”,这些才是所属类的实例
let num1=10
let num2=new Number(10)-->非标准特殊对象
let num3=Object(10)-->非标准特殊对象
console.log(num1,num2,num3)
console.log(num2.toFixed(2))//'10.00'
console.log(num1.toFixed(2))//'10.00' 浏览器默认会进行“装箱”:把原始值转为对象类型的值(基于Object([value])方法处理的)
console.log(num1+10)//20
console.log(num2+10)//20 浏览器默认会进行“拆箱”:把对象类型的值转为原始值
new执行和普通函数执行的区别
function Fn(x, y) {
let sum = 10
this.total = x + y
this.say = function () {
console.log(`我计算的和是:${this.total}`)
}
}
let res = Fn(10, 20)//作为普通函数执行
let f1 = new Fn(10, 20)
let f2 = new Fn
console.log(f1.sum)
console.log(f1.total)
console.log(f1.say === f2.say)
function Foo() {
getName = function () {
console.log(1)
}
return this
}
Foo.getName = function () {
console.log(2)
}
Foo.prototype.getName = function () {
console.log(3)
}
var getName = function () {
console.log(4)
}
function getName() {
console.log(5)
}
Foo.getName()
getName()
Foo().getName()
getName()
new Foo.getName()
new Foo().getName()
new new Foo().getName()
函数具备的角色
- 普通对象「操作的是函数堆中,存储的静态私有属性方法」
- Foo.xxx=xxx
- console.log(Foo.xxx)
- 函数
- 普通函数「闭包作用域」
- 构造函数「即拥有闭包作用域、还具备实例、proto、prototype」
重写new方法
![PLMCSP5[J_YQ`V8YC%BTFB.jpgconst _new = function _new(Ctor, ...params) {
//Ctor:我们需要操作的构造函数[就是创造它的一个实例]-->Dog
//params:数组,存储给Ctor传递的实参信息-->['三毛']
//4.对Ctor要做校验[不能是null/undefined、不能是Symbol/BigInt、必须具备prototype]
if(Ctor==null||Ctor===Symbol||Ctor===BigInt||!Ctor.prototype)throw new TypeError('Ctor is not a constructor')
//1.创建一个空的实例对象(空对象、__proto__指向类的prototype)
let instance = Object.create(Ctor.prototype)
//2.在构造函数作为普通函数执行的时候,让函数中的this指向创建的实例对象
let result = Ctor.call(instance, ...params)
//3.监测函数执行的返回值[返回的是对象,则以函数自己返回的为主,否则返回创建的实例对象]
if(result!==null&&/^(object|function)$/.test(typeof result))return result
return instance
}
this情况梳理
函数的执行主体(通俗来讲,就是谁把这个函数执行的),不是函数执行所处的上下文;我们都是去研究函数中的this,全局上下文中的this是window「前提:在浏览器环境下运行」;块级私有上下文中没有自己的this,用到的this都还是宿主环境中的
关于函数中的this到底是谁,有以下五条规律:
给元素进行事件绑定「DOM0/DOM2」
当事件触发,绑定的方法执行,方法中的this是当前被操作的元素「给谁绑定的,this就是谁」
普通函数执行,看函数前面是否有“点”
- 有:“点”前面是谁,函数中的this就是谁
- 例如:
- arr.push(100) -> this:arr
- arr.proto.push(100) -> this:arr.proto
- Array.prototype.push(100) -> this:Array.prototype
- const push=Array.prototype.push
- push(100) -> this:undefined/window
- …
- 没有:函数中的this是window(非严格模式)或者undefined(严格模式)
- 像 自执行函数 或者 回调函数 等匿名函数,如果没有经过特殊的处理,那么函数中的this,一般都是window/undefined
- 构造函数执行(NEW执行),函数体中的this指向创建的实例对象
- 我们可以基于 call/apply/bind 强制改变函数中this的指向
- call/apply:把函数立即执行,让函数中的this指向自己指定的值
- 函数.call(THIS,实参1,实参2,…)
- 函数.apply(THIS.[实参1,实参2,…])
- bind:bind语法和call一致,只不过其不是把函数立即执行,而是基于”柯里化函数思想”,创建一个闭包,把需要改变的THIS及传递的实参保存起来,供后续使用
- 最核心的区别:this「箭头函数中没有this」
- 箭头函数没有 arguments 「可以基于“…”剩余运算符获取传递的实参
- 箭头函数没有 prototype 「不能被NEW执行」
-
项目中用谁?
不涉及到this的问题,用谁都可以「推荐使用箭头函数」
- 想作为一个构造函数,只能用普通函数
- 涉及this问题,用谁处理起来方便,就选择用谁「一般都是外层普通函数,内层箭头函数」
call/apply/bind
在Function.prototype上具备call/apply/bind三个方法;每一个函数都是Function类的实例,所以都可以调用这三个方法,其目的是:改变函数执行中的this指向
const fn = function fn(x, y) {
console.log(this, x, y)
return x + y
}
const obj = {
name: 'obj',
fn:1000
}
需求:把fn执行,让this指向obj,传递10/20
obj.fn(10,20)//Uncaught TypeError: obj.fn is not a function[obj和fn没有关联]
不使用call方法,我们只需要让obj和fn建立关系(把函数作为obj的一个成员),再基于obj.fn执行即可
重写内置的call方法
console.log(
/*步骤:
@1 fn函数首先基于__proto__,找到Function.prototype上的call方法,并且把找到的call方法执行;
@2 给call方法传递了obj/10/20三个实参
@3 call方法执行做了以下的事情[方法内部实现的]:
+把fn(call中的this)执行
+把fn中的this改为obj
+把第二个及以后传递给call方法的实参,作为参数传递给fn
+接收fn执行的返回值,作为call执行的返回值
*/
fn.call(obj, 10, 20)
)
/* 重写内置的call方法 */
//给对象新增一个不可枚举的成员(可删除可修改)
const define=function define(obj,key,value){
Object.defineProperty(obj,key,{
value,//"value":value | value:value
writable:true,
configurable:true,
enumerable:false
})
}
const call=function call(context,...params){
//context:最后fn执行需要改变的this指向-->obj
//params:最后fn执行需要传递的实参信息-->[10,20]
//call方法中的this是:fn
//最终目的:把this(fn) 执行,传递实参params([10,20]),并且让函数中的"this指向"指向context(obj),并且接收函数执行的返回值,作为call执行的返回值
//context是null/undefined,我们让其默认为window
if(context==null)context=window
//给除null/undefined以外的原始值设置成员,不会报错,但是也不会生效[也就是要去context 必须是对象类型,如果不是,则转换为非标准特殊对象(装箱)]
if(!/^(object|function)$/.test(typeof context))context=Object(context)
if(context==null)context=window
const sym=Symbol('insert-key')
context[sym]=this
const result=context[sym](...params)
delete context[sym]
return result
}
define(Function.prototype,'call',call)
重写bind方法
需求:点击Body的时候执行fn,让fn中的this是obj,让x/y是10/20,让ev是事件对象
点击Body执行fn,fn->this:body x:事件对象 y:undefined
document.body.onclick = fn
call方法会把fn立即执行,不等点击Body就执行了
document.body.onclick = fn.call(obj, 10, 20)
点击Body才会执行匿名函数
document.body.onclick = function (ev) {
// this:body ev:事件对象
// 在匿名函数执行的时候,再基于call方法,把真正要执行的函数fn执行,改变其this和参数即可
fn.call(obj, 10, 20, ev)
}
![19P`S)%PJ)D60_C@U6)(_U_tmb.jpg
const bind = function bind(context, ...params) {
// this->fn context->obj params->[10,20]
let that = this
return function anonymous(...args) {
// this->body args->[ev]
// 在返回的匿名函数执行中,把真正需要执行的函数执行「改变THIS & 传递实参」
params = params.concat(args)
return that.call(context, ...params)
}
}
define(Function.prototype, 'bind', bind)
基于bind可以解决我们的需求:预先改变函数中的this指向(及参数),但是不会把函数立即执行
document.body.onclick = fn.bind(obj, 10, 20)
document.body.onclick = anonymous 点击的时候执行的是bind返回的小函数anonymous
总结call/apply/bind方法重写
const call = function call(context, ...params) {
if (context == null) context = window
if (!/^(object|function)$/.test(typeof context)) context = Object(context)
const sym = Symbol('insert-key')
context[sym] = this
const result = context[sym](...params)
delete context[sym]
return result
}
const apply = function apply(context, params) {
if (context == null) context = window
if (!/^(object|function)$/.test(typeof context)) context = Object(context)
const sym = Symbol('insert-key')
context[sym] = this
const result = context[sym](...params)
delete context[sym]
return result
}
const bind = function bind(context, ...params) {
let that = this
return function anonymous(...args) {
params = params.concat(args)
return that.call(context, ...params)
}
}
define(Function.prototype, 'call', call)
define(Function.prototype, 'apply', apply)
define(Function.prototype, 'bind', bind)