Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new运算符模拟实现 #19

Open
wolfdu opened this issue Dec 30, 2017 · 0 comments
Open

new运算符模拟实现 #19

wolfdu opened this issue Dec 30, 2017 · 0 comments

Comments

@wolfdu
Copy link
Owner

wolfdu commented Dec 30, 2017

https://wolfdu.fun/post?postId=5a473e9a25322c62a62e7b15

home-new
new运算符:

new 运算符创建一个自定义对象或具有构造函数的内置对象的实例。

(⊙o⊙)…好像Java里面的new有木有,然而不要这样想,他们完全不是同一个东东,如果你不知道Java中的new请放心食用。

我们从一定的场景带入,一步一步接近new的真相。

想象我们置身于骑砍 `<_` 的世界中,已经有自己的兵营了,现在我们要自建自己的骑兵团队。
首先我们先捏一个骑兵出来:

var cavalry = {
  id: 1, // 用于区分每个士兵
  type:'cavalry', // 士兵类型 
  damage:5, // 伤害值
  HP:42, // 血量
  riding:function(){ /*骑马*/},
  attack:function(){ /*攻击*/   },
  defense:function(){ /*防御*/       }
}

我的骑兵我的团↖(▔^▔)↗

当我们捏出一个骑兵后,我们要达到量产呀,这次我们训练出一百个骑兵来组成骑兵团队。

// 骑士团1.0
var knights = []
var cavalry
for(var i =0; i < 100; i++){
	cavalry = {
		id: i, // 用于区分每个士兵
		type:'cavalry', // 士兵类型 
		damage:5, // 伤害值
		HP:42, // 血量
		riding:function(){ /*骑马*/},
		attack:function(){ /*攻击*/   },
		defense:function(){ /*防御*/ }
	}
	knights.push(cavalry)
}

console.log(knights)

这样我们就得到了我们的骑士团了。
这里我们会发现一些问题:

  • 每个骑兵都有riding,attack,defense的动作,而我们是每创建一个骑兵的时候我们都为他重复的创建了相同方法,我们可以让他们引用同一个函数就可以了,这样就减少了不必要的内存浪费
  • 我们创建的兵种,伤害值和血量都是初始值,感觉也不用每次都创建一次
  • 那么就只有id是每次创建士兵的时候都需要赋值的

那我们来改进一下我们的训练方式
我们这里可以通过JavaScript原型链去解决这个问题,如果你原型链还没搞定可以先虐杀原型,再来建立骑士团。

// 我们先来创建一个骑兵的原型
var _cavalry = {
	type:'cavalry', // 士兵类型 
	damage:5, // 伤害值
	HP:42, // 血量
	riding:function(){ /*骑马*/},
	attack:function(){ /*攻击*/   },
	defense:function(){ /*防御*/ }
}

// 骑士团2.0
var knights = []
var cavalry
for(var i =0; i < 100; i++){
	cavalry = {
		id: i, // 用于区分每个士兵
	}
	// 将实例的__proto__指向_cavalry
	cavalry.__proto__ = _cavalry
	knights.push(cavalry)
}

console.log(knights)

吼啦,我们可以解决创建骑士团1.0的问题了,但是2.0整体代码我们还是可以改进一下的。

// 我们先来创建一个骑兵的原型
var _cavalry = {
	type:'cavalry', // 士兵类型 
	damage:5, // 伤害值
	HP:42, // 血量
	riding:function(){ /*骑马*/},
	attack:function(){ /*攻击*/   },
	defense:function(){ /*防御*/ }
}

function cavalryFactory (id) { // 创建骑兵的兵工厂
	var cavalry = {}
	cavalry.__proto__ = _cavalry
	cavalry.id = id
	return cavalry
}

// 骑士团3.0
var knights = []
for(var i =0; i < 100; i++){
	knights.push(cavalryFactory(i))
}

console.log(knights)

这样我们骑士团3.0看起来是不是高大上了很多呢

简单来讲这里cavalryFactory骑兵工厂发挥的作用和我们new运算符的作用就已经很接近啦,那我们就来模拟实现一个真正的new运算符吧。

模拟实现new运算符

在模拟之前,我们先看看使用new运算符如何去创建我们的骑士团。

// 骑兵的构造函数
var Cavalry = function(id){
    this.id = id
}

// 设置原型属性
Cavalry.prototype.type = 'cavalry'
Cavalry.prototype.damage = 5
Cavalry.prototype.HP = 42
Cavalry.prototype.riding = function(){ /*骑马*/}
Cavalry.prototype.attack = function(){ /*攻击*/}
Cavalry.prototype.defense = function(){ /*防御*/}

// 骑士团
var knights = []
for(var i =0; i < 100; i++){
  knights.push(new Cavalry(i))
}

console.log(knights)

我们有了骑士团3.0的经验,我们再结合原型链相关知识我们可以很轻松的模拟出第一版objFactory

function objFactory(){
    // 初始化一个临时对象
    var obj = new Object()
    // 获取第一个参数为构造函数,shift会改变原数组哦
    var Construct = [].shift.call(arguments)
    // 将临时对象的原型指向构造函数的prototype
    obj.__proto__ = Construct.prototype
    // 使用apply给obj添加属性
    Construct.apply(obj, arguments)

    return obj
}

function Cavalry (id) {
    this.id = id
}

Cavalry.prototype.type = 'cavalry'
Cavalry.prototype.damage = 5
Cavalry.prototype.HP = 42
Cavalry.prototype.riding = function(){ /*骑马*/}
Cavalry.prototype.attack = function(){ /*攻击*/}
Cavalry.prototype.defense = function(){ /*防御*/}

// 骑士团4.0
var knights = []
for(var i =0; i < 100; i++){
    knights.push(objFactory(Cavalry, i))
}

console.log(knights)

额,在模拟过程中我们使用apply给临时obj添加了id属性和值,如果对apply功能有疑问可以猛戳JavaScript模拟实现call,apply,bind等方法 ( ´´ิ∀´ิ` )

这里我们观察骑士团4.0与我们使用new创建的骑士团其实已经没有什么区别了。

模拟构造函数返回值

既然我们模拟new运算符的实现,我们再考虑下构造函数有返回值的情况,先来看一个示例:

function Cavalry(id, type){
    this.id = id
    this.type = type
    this.HP = 42

    return {
        id
    }
}

var cavalry = new Cavalry(1, 'cavalry');

console.log(cavalry.id) // 1
console.log(cavalry.type) // undefined
console.log(cavalry.HP) // undefined

可以发现当构造函数返回一个对象时,我们的实例对象cavalry只能访问到返回对象中的属性值。

那么当返回一个基本类型时会是什么情况呢?

function Cavalry(id, type){
    this.type = type
    this.HP = 42

    return 'i am cavalry'
}

var cavalry = new Cavalry(1, 'cavalry');

console.log(cavalry.id) // undefined
console.log(cavalry.type) // cavalry
console.log(cavalry.HP) // 42

从返回结果我们可以发现,当返回基本类型时,此时的处理相当于没有返回值时的处理。

那么我们需要判断下构造函数的返回值是一个object还是基本类型,这里我来改进下终极版objFactory:

function objFactory(){
    // 初始化一个临时对象
    var obj = new Object()
    // 获取第一个参数为构造函数,shift会改变原数组哦
    var Construct = [].shift.call(arguments)
    // 将临时对象的原型指向构造函数的prototype
    obj.__proto__ = Construct.prototype
    // 使用apply给obj添加属性
    var result = Construct.apply(obj, arguments)

    return (typeof result === 'object' && result !== null) ? result : obj
}

小结下

  • 使用new运算符的时候一个新对象被创建,它的原型对象指向自构造函数的prototype
  • 使用new运算符构造函数中的this会指向一个新创建的对象
  • 构造函数和普通函数并没有什么两样,关键在于你是否使用new运算符

参考文章:
JS 的 new 到底是干什么的?
JavaScript深入之new的模拟实现

若文中有知识整理错误或遗漏的地方请务必指出,非常感谢。如果对你有一丢丢帮助或引起你的思考,可以点赞鼓励一下作者=^_^=

@wolfdu wolfdu changed the title WolfDu后山 | wolfdu.fun new运算符干了些啥? Dec 30, 2017
@wolfdu wolfdu changed the title new运算符干了些啥? new运算符模拟实现 Dec 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant