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

读《你不知道的javascript》上卷—原型和行为委托 #13

Open
lizhongzhen11 opened this issue Aug 12, 2018 · 0 comments
Open

Comments

@lizhongzhen11
Copy link
Owner

lizhongzhen11 commented Aug 12, 2018

前言

最近一段时间没怎么登github,公司内部走代理访问github打开特别慢,排版布局又很乱,据悉是公司内部防火墙的缘故。
晚上回来也迟,就没有打开电脑去看搜藏的博客。不过不要紧,毕竟也买了不少技术书,那就静下心来看看书吧。
PS:《你不知道的javascript》上中两卷买回来起码半年了,昨天才看完上卷,太懒了!!!得逼自己一把,不然怎么快速进步!!!
看书之前以为我都会,看完之后才发现我还不够!
尤其看完这两章,我对原型链以及js继承又有了全新的认识,这里特地把自己以前没有学习到的新知识记录下来,做个总结。
建议大家去看该系列书籍,github上有英文版:《你不知道的javascript》

get到的新点

1.属性设置和屏蔽

这里主要讲了当对象(例如myObj)与它的原型对象(例如obj)上都存在同名属性(例如foo)时,myObj.foo会屏蔽掉它原型对象上的同名foo属性(这里指obj.foo)。即当进行get操作时得到的是myObj.foo,这点大家都清楚。

但是!如果myObj中没有foo属性,而它的原型对象obj上有foo属性,那么当进行 myObj.foo= 赋值时,情况可能并不是我们想当然的那样,只在myObj中新增一个foo属性!

这里有个专业术语:屏蔽
这里涉及到三种情况:

  1. 如果在[[Prototype]]链上层存在名为foo的普通数据访问属性并且没有被标记为只读(writable: false),那就会直接在myObj中添加一个名为foo的新属性,它是屏蔽属性。(PS:原型链上有同名属性,但是可以修改的情况下)
    示例代码:
var obj = {
  foo: 1
}
var myObj = Object.create(obj)
myObj.foo // 1
myObj.foo = 2
obj.foo // 1
  1. 如果在[[Prototype]]链上层存在foo,但是它被标记为只读(writable: false),那么无法修改已有属性或者在myObj上创建屏蔽属性。如果运行在严格模式下,代码会抛出错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
    示例代码:
var obj = {}
Object.defineProperty(obj, 'foo', {
  value: 1,
  writable: false,
  configurable: true,
  enumerable: true
})
var myObj = Object.create(obj)
myObj.foo // 1
myObj.foo = 2
myObj.foo // 1  这里还是1!
  1. 如果在[[Prototype]]链上层存在foo并且它是一个setter,那就一定会调用这个setterfoo不会被添加到myObj,也不会重新定义这个setter
    代码示例:
var obj = {}
Object.defineProperty(obj, 'foo', {
  configurable : true,
  enumerable : true,
  set: function() {
	this.value = 1
  },
  get: function () {
    return this.value
  }
})
var myObj = Object.create(obj)
myObj.foo = 2
myObj.foo // 1

如果希望在第二、三种情况下也能对myObj设置foo属性,那么请使用Object.defineProperty(),避免使用=

隐式屏蔽
摘抄自书上代码:

var anotherObject = {
  a: 2
}
var myObject = Object.create(anotherObject);
anotherObject.a // 2
myObject.a //2
anotherObject.hasOwnProperty('a') // true
myObject.hasOwnProperty('a') // false
myObject.a++ // 隐式屏蔽
anotherObject.a // 2
myObject.a // 3
myObject.hasOwnProperty('a') // true

上述代码关键点在myObject.a++这一步!myObject.a++相当于myObject.a = myObject.a + 1!!!myObject的原型对象anotherObject 上的a属性不是只读也没有固定死setter,所以这里相当于对myObject增加了一个a属性!!!

2.constructor属性

实例本身并没有constructor属性,constructor属性存在于它的原型对象上!!!
示例代码:

function Foo() {}
var f = new Foo()
f.constructor === Foo // true
Foo.prototype.constructor === Foo // true

之前学习原型链时知道了上述代码的关系,但是,其实这里我不是很懂。直到看了书我才知道,上述代码中f对象其实本身并没有constructor属性,它的原型对象Foo.prototype上才有,f.constructor本质上是去原型链上找到constructor属性即Foo.prototype.constructor!!!

这就是为什么当我们使用=操作符去进行原型继承时,需要对constructor重新赋值。
代码示例:

function A() {}
A.prototype.constructor // A
A.constructor // Function
function B() {}
B.constructor // Function
B.prototype.constructor // B
B.prototype // Function 这里是函数
B.prototype = new A() // A {} 这里是对象
B.prototype.constructor // A
B.constructor // Function
var c = new B() // B {}
c.__proto__ // A {} 这里就有问题了,c明明是由B构造的,却直接跳过了B找到了A
c.constructor === B.prototype.constructor // A 这里也有问题,不符合规范了
B.prototype.constructor = B // 强行赋值,改变指向

按照规范来说,B.prototype.constructor应该指向B自身,但是上述代码如果不手动改变指向,则会造成constructor属性指向错误!

3.面向委托的设计思想

个人理解,面向委托的设计模式需要避免使用prototype以及constructor属性,主要通过this关键字来把控,配合Object.create()来实现。
典型的原型风格 与 面向委托两种不同设计模式实现对比:
类:

function Foo(who) {
  this.me = who
}
Foo.prototype.identify = function () {
  return "I am " + this.me
}
function Bar(who) {
  Foo.call(this, who)
}
Bar.prototype = Object.create(Foo.prototype)
Bar.prototype.speak = function () {
  alert("Hello, " + this.identify() + ".")
}
var b1 = new Bar("b1")
var b2 = new Bar("b2")
b1.speak()
b2.speak()

面向委托:

var Foo = {
  init: function (who) {
    this.me = who
  },
  identify: function () {
    return "I am " + this.me
  }
}
var Bar = Object.create(Foo)
Bar.speak = function () {
  alert("Hello, " + this.identify() + ".")
}
var b1 = Object.create(Bar)
b1.init("b1")
var b2 = Object.create(Bar)
b2.init("b2")
b1.speak()
b2.speak()

面向委托的写法看起来更简洁也更容易理解。而且,b1.constructor === Object; Foo.constructor === Object; Bar.constructor === Object!避免了原型继承模式下的constructor指向混乱的问题。

总结

面向委托的思想是重点,以前从来都没接触过。但是需要不断实践才能真正深入了解。
接下来时间里继续看《你不知道的javascript》

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant