new原理介绍
new 关键词的主要作用就是指向一个构造函数,返回一个实例对象。
实现思路
1、创建一个新对象
2、将构造函数的作用域赋给新对象(this指向新对象)
3、执行构造函数中的代码(为这个新对象添加属性)
4、返回新对象
如果不用new的话,返回的结果就是undefined
function Person(){
this.name = 'tom'
}
const p = Person()
console.log(p) //undefined, 没有加new的情况下,函数只是普通函数的执行,this在默认情况下是指向window
console.log(name) // 因此name就是 tom
console.log(p.name) // Cannot read properties of undefined (reading 'name')
当构造函数中有个 return 一个对象的操作
当构造函数最后return出来的是个与this无关的对象时,new 命令会直接返回这个新对象,而不是通过new执行步骤生成的this对象
function Person() {
this.name = 'tom'
return {age:18}
}
const p = new Person()
console.log(p) // {age:18}
console.log(p.name) //undefined
console.log(p.age) // 18
当构造函数中 显示返回 return发的不是一个对象是一个基本数据类型,则会使用 new 生成的对象返回
当构造函数return 返回的不是一个对象时,会根据new 关键词执行逻辑,生成新的对象(绑定this),最后返回出来
function Person() {
this.name = 'tom'
return 'hello'
}
const p = new Person()
console.log(p) // {name:'tom'}
console.log(p.name) //tom
总结
new 关键词执行之后总是返回一个对象,要么是实例对象,要么是return 语句指定的对象
apply、call、bind 原理
基本使用
fun.call(thisArg, params1, params2,params3)
fun.apply(thisArg, [params1,params2, params3])
fun.bind(thisArg, params1, params2, params3)()
共同点和不同点
都能改变函数 fun 的this 指向,call 与 apply 的不用在于传参不同,apply 的第二个参数为数组,而call 则是从第二个参数到第 N 个 都是给 func 传参;
而bind 和 (call 、apply)又不同,bind 虽然改变了this的指向,但不是马上执行,返回的是一个待执行的函数,而这两个 ( call、apply) 是在改变了函数的this 指向后立马执行。
通过代码深入理解
a 对象有个 getName 的方法,B 对象也临时需要使用同样的方法,那么这时候为 B 对象借用 A 对象的方法,即可达到目的,又节省重复定义,节约内存空间
let a = {
name : 'tom',
getName(msg) {
return msg + this.name;
}
}
const b = {
name : 'zq'
}
console.log(a.getName('hello'))
console.log(a.getName.call(b, 'hi ~'))
console.log(a.getName.apply(b,['hio ~']))
const name = a.getName.bind(b, 'hello -')
console.log(name())
通过改变 this 的指向,让 b 对象 可以直接使用 a 对象的 getName 方法。
改变this指向应用场景
Object.prototype.toString 来判断类型
// 这就是借用 Object 的原型上的 toString 方法,最后用返回用来判断 传入的参数
Object.prototype.toString.call()
细讲分析类型判断
Object.prototype.toString() // '[object Object]'
//Array 借用 Object 的原型链上的 toString()
Array.prototype.toString() // ''
Array.prototype.toStrings = Object.prototype.toString
Array.prototype.toStrings() // '[object Array]'
Object.prototype.toString.call([]) //'[object Array]'
// Number 借用 Object 原型链上的 toString 方法
Number.prototype.toString() // '0'
Number.prototype.toStrings = Object.prototype.toString //将 Object 原型链上的 toString 借到 Number 上
var num = 1
num.toStrings() //'[object Number]'
num.toString() // '1'
类数组借用方法
类数组不是真正的数组,所以没有数组类型上的自带的种种方法,所以我们就可以利用一些方法取借用数组的方法,比如数组的push方法
const arrayLike = {
0: 'java',
1: 'script',
length:2
}
Array.prototype.push.call(arrayLike, 'tom', 'zq')
console.log(typeof arrayLike) // object
console.log(arrayLike) //{0: 'java', 1: 'script', 2: 'tom', 3: 'zq', length: 4}
获取数组的最大最小值
let arr = [18,2,19,1,20,3,10,8]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(Math, arr)
console.log(max) // 20
console.log(min) // 1
继承
function Parent3() {
this.name = 'parent3'
this.play = [1,2,3]
}
Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
//将Parent3 函数执行的this指向 当前函数,也就是借用Parent3函数为 Child3生成属性
Parent3.call(this)
this.type = 'child3'
}
new 的实现原理
new 被调用做了哪些事:
1、让实例可以访问私有属性;
2、让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性;
3、构造函数返回的最后结果是引用数据类型。
简洁版
//简版
//1、创建一个新对象
//2、将构造函数的作用域赋给新对象(this指向新对象)
//3、执行构造函数中的代码(为这个新对象添加属性)
//4、返回新对象
function _new (ctor, ...argums) {
if(typeof ctor != 'function'){
throw 'ctor is not function'
}
const obj = {}
const res = ctor.apply(obj,argums)
console.log(111111111,res,argums ,obj)
return obj
}
//构造函数
function Fun(name,sex){
this.name = name
this.sex = sex
}
Fun.prototype.getName = function() {
console.log('name:',this.name)
}
const fun = _new(Fun,1,2) //能拿到new 生成的对象
console.log('getName:',fun.getName) //undefined 无法拿到构造函数定义的原型方法
/*
* 核心解析:
* ctor(...argums) //这样写的函数里的this指向是window,那函数执行创建的对象跟new没啥关系了,而是跟window挂钩了
*
* 利用apply改变构造函数 this的指向,obj借用ctor构造函数为自己创建属性
*/
function Fun(name,sex){
this.name = name
this.sex = sex
}
Fun(1,2)
console.log(name) // 1
console.log(sex) // 2
完整版
简洁版缺陷:
1、当前构造函数如果显示返回对象,没有使用显示返回的对象进行返回。
2、当前构造函数如果显示返回 基本数据类型,没有做判断处理
3、当前构造函数如果显示返回 函数,没有做返回处理
4、只是单纯的创建对象,拿不到构造函数原型上的属性
注意:
new 关键词执行之后总是返回一个对象,要么是实例对象,要么是return 语句指定的对象
//完整版
//1、创建一个新对象
//2、将构造函数的作用域赋给新对象(this指向新对象)
//3、执行构造函数中的代码(为这个新对象添加属性)
//4、返回新对象
function _new (ctor, ...argums) {
if(typeof ctor != 'function'){
throw 'ctor is not function'
}
//1、创建一个新对象
//const obj = {}
// 2、obj的原型指向构造函数的原型,这样obj就可以访问构造函数原型上的属性,原型链上的对象不能丢失
//obj.__proto__ = Object.create(ctor.prototype)
//步骤1和步骤2可以优化成一个步骤
const obj = Object.create(ctor.prototype)
//3、将构造函数的this指向obj,这样obj就可以访问构造函数的属性
const res = ctor.apply(obj,argums)
//4、这里用来弥补简洁版缺陷,这样就比较容易理解了
const isObject = typeof res === 'object' && res != null //判断new构造函数执行是否显式返回了对象
const isFunction = typeof res === 'function' //判断new构造函数执行是否返回了函数
return isObject || isFunction ? res : obj //如果即没有显式返回函数也没显式返回对象,则返回由new创建的函数
}
//构造函数
function Fun(name,sex){
this.name = name
this.sex = sex
}
Fun.prototype.getName = function() {
console.log('name:',this.name)
}
const fun = _new(Fun,1,2) //能拿到new 生成的对象
console.log('getName:',fun.getName) //能拿到构造函数上的原型方法
其他知识点
Object.create
这个方法可能大部分只是用来创建一个对象,但它最重要的功能是用在创建对象的原型,也就是要继承的对象或属性、方法
//proto 创建对象的原型,也就是要继承的对象
//propertiesObject 也是一个对象,用于创建的对象进行初始化
Object.create(proto,propertiesObject)
//Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或方法
Object.create 原理
基础版实现,顺带研究Object.create 的底层实现,按照api 根据自己思路实现,有不对的地方一起讨论
Object.create = function(prop, description){
if(typeof prop != 'object'){
//对象原型只能是Object或null:未定义
throw 'Object prototype may only be an Object or null: undefined'
}
if(description === null){
//无法将未定义或null转换为对象
throw ' Cannot convert undefined or null to object'
}
if(typeof description === 'object'){
var keyArray = Object.getOwnPropertyNames(description)
for(var i = 0; i < keyArray.length; i++){
if(typeof description[keyArray[i]] !== 'object') {
//属性描述必须是对象:2
throw 'Property description must be an object: 2'
}
}
}
if(!description){
var obj = new Object()
obj.__proto__ = prop
} else {
description.__proto__ = prop
}
return obj || description
}
//该实现只支持这几种方法
// 初始化对象:属性描述必须是对象:2
Object.create({a:1},{a:1})
// 无法将未定义或null转换为对象
Object.create({a:1},null)
// 对象原型只能是Object或null:未定义
Object.create({a:1},null)
apply、call、bind原理
apply和call的实现基本一样,只不过参数不同而已
apply
Function.prototype.apply = function(context, args) {
var context = context || window
//this 调用apply的方法,args 调用apply的数组参数
context.fun = this
//将参数传进去即可
const res = context.fun(...args)
delete context.fun
return res
}
call
Function.prototype.call = function (context, ...args) {
var context = context || window
// this: 调用call 的函数,args,调用call的参数
context.fun = this
const res = context.fun(...args)
delete context.fun
return res
}
obj.getName.call()
bind
bind的实现思路基本和apply一样,但是返回结果不同,不需要立即执行,而是通过返回一个函数的方式将结果返回,通过执行这个结果,得到想要的执行效果
Function.prototype.bind = function(context, ...args){
var self = this
var fun = function() {
console.log('this',this) // window,
console.log('argums',[...arguments]) // 返回函数执行的参数
console.log('context:',context) //要改变this指向的对象
console.log('self',self) //调用call的执行的函数
console.log('this instanceof self :',this instanceof self)
console.log(111111111111111111111)
self.apply(this instanceof self ? this : context, args.concat([...arguments]))
}
if(this.prototype) {
// 在返回fun的过程中,原型链上的对象不能丢失,所以需要在这里使用Object.create 将this.prototype 上的属性挂到 fun的原型上面
fun.prototype = Object.create(this.prototype)
}
return fun
}
总结
方法特征 | call | apply | bind |
---|---|---|---|
方法参数 | 多个参数 | 单个数组 | 多个 |
方法功能 | 函数调用改变this | 函数调用改变this | 函数调用改变this |
返回结果 | 直接执行 | 直接执行 | 返回待执函数 |
底层实现 | 通过调用this拿到函数,接受参数执行 | 通过调用this拿到函数,接受参数执行 | 间接调用apply执行 |