7 The Secret Life of Objects

234 阅读9分钟

The Secret Life of Objects

方法

  • 方法是指向了函数值的普通属性

    var rabbit = {}
    rabbit.speak = function (line) {
      console.log("The rabbit says '" + line + "'")
    }
    rabbit.speak("I'm alive.")
    
  • 当一个函数以方法形式被调用时 有一个特殊变量this指向方法被调用时所属对象

    ==this不能赋值== this.xxx可以

obj = {}
obj.c = 8
obj.f = function (a, b) {
  console.log(a + b + this.c)
}
obj.f(1, 2)
//11
var aryLike = {
  0: 1,
  1: 2,
  length: 2
}

aryLike.push = function (val) {
  this[this.length] = val
  this.length++
  return this.length
}

aryLike.push(3)
console.log(aryLike)
var ary = [1, 2, 3]
ary.push2 = function (val) {
  this[this.length] = val
  return this.length
}
//数组length会自动增加
var ary = []
ary[8] = 0
console.log(ary.length)
//9
function speak(line) {
  console.log("The" + this.type + " rabbit says '" + line + "'")
}
var whiteRabbit = { type: "white", speak: speak }
var fatRabbit = { type: "fat", speak: speak }

whiteRabbit.speak('ha')
  • bind和apply 的 第一个参数 this

    xxx.f(a, b) 等价于 f.apply(xxx, [a, b])

function add(a){
  return a +this[0]
}
add.apply([3],[5])
//8
  • call函数 和apply函数一样 区别是正常传入参数 而非以数组形式
f.apply(xx, [1, 2, 3])
f.call(xx, 1, 2, 3)
f.call(xx, ...[1, 2, 3]) //spread
function add(a, b, ...args) { } //rest paramater
var f = function () { return this.val }
var obj = {
  val: 3,
  f: f
}
var obj = {
  val: 3,
  f: function () { return this.val }
}
obj.f()
//3
obj2 = {
  val: 4, 
  f: obj.f
}
obj2.f()
//4
//f函数不属于任何一个对象 obj和obj2对于f的关系是对等的
f = obj.f  //把f函数读出来
f()        //单独的函数调用
// undefined  this是window window没有val属性 undefined
  • this 指向什么取决于函数的调用形式 不取决于函数的定义位置 调用位置

  • 函数能访问到的非形参变量 取决于函数在哪定义 以及他在哪个作用域 优先形参

f() //全局变量window
obj.f() // obj
f.call(o)
f.apply(o) //第一个参数o
var a = 1
console.log(this.a)
//window

function f() {
  console.log(this, a)
}

function add() {
  var a = 5
  f() //this window
  var obj = {
    f: f
  }
  obj.f() // this obj
  f.call([1, 2, 3])// this [1,2,3]
}

Prototypes

var empty = {}
console.log(empty.toString)
//[Function: toString]
console.log(empty.toString())
//[object Object]
  • 对象除了有自己的属性外 几乎所有的对象还有一个原型

    当访问一个对象没有的属性时 会到他的原型上找 原型也是一个对象 然后原型的原型

得到对象原型

console.log(Object.getPrototypeOf({}) == Object.prototype)
//true
//是{}的原型 同时也被Object.prototype属性指向
console.log(Object.getPrototypeOf(Object.prototype))
//null
  • Object 是一个函数 变成对象 Object(2)
  • 是命名空间 Object.keys({ a: 1, b: 2, c: 3 })["a", "b", "c"] 获得对象的属性
  • Object.getPrototypeOf() 获取对象原型
  • empty._proto_ 得到原型的另外一种方法
p = { a: 1, b: 2 }
empty._proto_ = p
//p所指的对象成了empty的原型
empty.a //1
empty.b //2

p._proto_ = null

empty.a = 8 //增加自己的a属性 不会修改原型上的
empty._proto_.a = 9 //修改原型
  • 对象原型之间关系形成树状结构 不能有环 对象属性可以有环
  • 树的根部是Object.prototype
var obj = {}
obj.prototype = { x: 1, y: 2 }
console.log(obj.x)
//undefined obj 的真正原型是在_proto_  没有x 所以是undefined prototype就是个普通的属性名字
ary = [] 
ary._proto_ === Array.prototype   //ary的原型 恰好被Array的prototype属性指向而已
x =2
x._proto_ === Number.prototype
x = 'fdas'
x._proto_ === String.prototype
ary.push === Array.prototype.push
ary._proto_.push === Array.prototype.push
delete String.toString   //删除String的toString属性
odjToString = Object.prototype.toString

//调用Object.prototype的toString方法 
objoString.call('a')
Object.prototype.toString.call('c')

function(){}._proto_ === Function.prototype  //报错
(function(){}._proto_) === Function.prototype

String.prototype.__proto__ === Object.prototype

Object.create()

  • 可以使用Object.create()来创建一个被指定了特定原型的对象
var obj ={}
obj = Object.create({a:1,b:2})
//返回一个对象 obj以{a:1,b:2} 为原型

obj._proto_ === {a:1,b:2}
//false 这里的{a:1,b:2} 不是create里面的对象了 
function speak(line) {
  console.log(this.type, line)
}
var rabbiteProto = {
  speak: speak
}
r1 = { type: fat }
Object.setPrototypeOf(r1, rabbiteProto)
r2 = { type: white }
r2 = Object.create(rabbiteProto)
r3 = { type: killer }
r3._proto_ = rabbiteProto

r0 = Object.create(rabbiteProto, {
  type: { value: 'killer' },
  age: { value: 18 },
  gender: { value: 'boy' }
})

创建一系列以相同对象为原型的对象

proto = { speak: function () { } }
r1 = Object.create(proto, { type: { value: 'killer', writable: false, enumerable: true } })
//writable不可修改  enumerable 可枚举性 for in 不会遍历到这个属性
r1.type = 999 //赋值   type还是killer

r3 = { type: 'fat' }
r3._proto_ = proto

r4 = {
  type: 'four',
  _proto_: proto
}

r5 ={type:'five'}
Object.setPrototypeOf(r5,proto)

构造函数

  • 创建共享原型对象(用于访问公共属性)更方便的方法是使用构造函数
  • 当一个函数调用前面带有一个new关键字的时候,这个函数就被当做一个构造函数
  • 构造函数的this指向全新的空对象
  • 除非明确的返回另一个对象值,这个this指向的新的对象就会被返回,return 2 2会被忽略
function TreeNode(val) {
  this.val = val
  this.left = null
  this.right = null
}

var a = new TreeNode(2)
console.log(a)
//TreeNode { val: 2, left: null, right: null }
  • 一个用new创建出来的对象一般被称做这个函数的实例
  • 所有function都有prototype属性( 箭头函数没有)
  • 这个属性默认指向空对象 里面只有consrtructor 指向自己 TreeNode.prototype 一般都以Object.prototype
  • 每一个被这个函数创建出来的实例都将以这个对象作为其原型对象的prototype
function TreeNode(val) {
  this.val = val
  this.left = null
  this.right = null
}

var a = new TreeNode(1)
var b = new TreeNode(2)
var c = new TreeNode(3)
//他们都以TreeNode.prototype 为原型

console.log(TreeNode.prototype)
//TreeNode {}
console.log(a.__proto__ === TreeNode.prototype)
//true

//给对象添加一个属性(方法)
TreeNode.prototype.getValue = function () {
  return this.value
}

a.getValue()
//1
b.getValue()
//2
c.getValue()
//3
  • __proto__属性 null 和undefined 没有
Rabbit.prototype.speak = function(line){
  console.log(line)
}
blackRabbit.speak("Doom")
  • ==函数有prototype属性==

    ==对象有_proto_原型==

    ==构造函数自己的原型是 Function.prototype==

  • TreeNode.prototype === a._proto_

  • TreeNode._proto_ === Function.prototype

  • Function._proto_ === Function.prototype

d ={}
d._proto_ = TreeNode.prototype //核心 仅仅是原型指向构造函数的原型属性
// d TreeNode{}

Overriding derived properties

  • 对衍生属性(即原形中的属性)的覆盖

  • 当你给一个对象增加一个属性时,不管属性是否存在于原型中,新属性都会增加到对象自己上。

    function TreeNode(val) {
      this.val = val
      this.left = null
      this.right = null
    }
    TreeNode.prototype.getValue = function () {
      return this.val
    }
    var a = new TreeNode(1)
    
    Object.prototype.toString.call(a)
    //[object Object]
    
    a.getValue = 8 //给对象a添加了属性
    a.getValue() //报错 不是方法
    
    a._proto_.getValue()
    //不行 成了a._proto_.的getValue
    a._proto_.getValue.call(a)
    //1 
    
    function Rabbit(type) {
      this.type = type
    }
    
    Rabbit.prototype.toString = function () {
      return '兔子'
    }
    
    killerRabbit = new Rabbit('killer')
    console.log(killerRabbit.toString())
    //兔子
    
    console.log([1, 2, 3].toString())
    //1,2,3
    Array.prototype.toString = function () {
      return '[' + this.join(',') + ']'
    }
    console.log([1, 2, 3].toString())
    //[1,2,3]
    
    Object.prototype.toString.call(1)
    Object.prototype.toString.call(2)
    Object.prototype.toString.call(true)
    Object.prototype.toString.call("fdsfd")
    Object.prototype.toString.call(null)
    Object.prototype.toString.call(undefined)
    Object.prototype.toString.call(() => 1)
    
    function isNumber(val){
      return Object.prototype.toString.call(val) === '[object Number]'
    }
    function isBoolean(val) {
      return Object.prototype.toString.call(val) === '[object Boolean]'
    }
    function isArray(val) {
      return Object.prototype.toString.call(val) === '[object Array]'
    }
    //只能判断内置类型
    

    Prototype interference

  • 原型可以随时给基于它的对象新增方法和属性

    TreeNode.prototype ={
      constructor:TreeNode
      setValue:function(val){
        this.val = val
      },
      setLeft:function(leftTree){
        this.left = leftTree
      },
      setValue:function(val){
        this.right = rightTree
      },
    }
    //new之前可以 但也会覆盖原来的prototype属性 new之后会覆盖
    
    
    TreeNode.prototype.setValue = function(val){
      this.val = val
    }
    TreeNode.prototype.setLeft = function(leftTree){
      this.left = leftTree
    }
    TreeNode.prototype.setValue = function(val){
      this.right= rightTree
    }
    //可以new之后写
    
function People() {
}
a = new People()
People.prototype = {}
b = new People()

// a 和 b 原型不一样 a原型{constructor: ƒ} b原型{}

原型产生的问题

var map = {}
function storePhi(event, phi) {
  map[event] = phi
}

storePhi("pizza", 0.069)
storePhi("touched tree", -0.081)
//用for/in loop 填值  用in运算符查看是否在map里

Object.prototype.nonsense = 'hi'
for (var name in map) {
  console.log(name)
}

// pizza
// touched tree
// nonsense

toString in map
//true

创建不可枚举属性

  • js区分可枚举和不可枚举属性
Object.defineProperty(map, 'c', { value: 1, writable: false, enumerable: false, configurable: false })
//第三个参数属性描述符 不可改 不可枚举 定义不可改,不可以再配置了,比如将writable改为true是不可能的


//配置多个属性
a = {}
Object.defineProperties(a, {
  c: { value: 1, writable: false },
  d: { value: 1, writable: false }
})


obj2 = Object.create(null, {
  a: { value: 1, writable: false, enumerable: true },
  b: { value: 8, writable: true }
})
  • 所有直接赋值到对象上的属性都是可枚举的
  • Object.prototype 上的标准属性不可枚举

判断是否有某个自由属性

a = { a: 1, b: 2 }
'a' in a //true
'toString' in a //true

a.hasOwnProperty('toString')
//false
只遍历自由属性
var map = {
  x: 1,
  y: 2
}
for (var key in map) {
  if (map.hasOwnProperty(key)) {
    console.log(key)
  }
}

保险做法

var map = {
  x: 1,
  y: 2,
  hasOwnProperty: 8
}

// Object.setPrototypeOf(map, null)
//map没有原型了 访问不到hasOwnPropert 或者属性重名
//不能够保证对象有hasOwnProperty属性 就把他读出来

hasOwn = Object.prototype.hasOwnProperty

map.hasOwnProperty(key)

for (var key in map) {
  if (hasOwn.call(map, key)) {
    console.log(key)
  }
}
//遍历函数自由属性
function forOwn(obj, action) {
  var hasOwn = Object.prototype.hasOwnProperty
  for (var key in map) {
    if (hasOwn.call(map, key)) {
      action(obj[key], key, obj)
    }
  }
  return obj
}

回顾

obj = {
  val: 1,
  a: 2
}

obj.f = function f() {
  function foo() {
    return this.val //window
  }
  return this.a + foo()
}

obj.f()
//NaN

//obj是右面的函数 运行 this.a + foo()
//f()的this是obj this.a = 2 foo() 的this是window 没有val NaN




obj = {
  val: 1,
  a: 2
}
  (function g() {
    obj.bar = function () {
      console.log(this)
    }
  })()

obj.bar()
// 函数调用obj
function f() {
  console.log(this)
}

f2 = f.bind(f, obj1, 1, 2)
f2()



function bind(f, thisArg, ...fixedArgs) {
  return function bound(...args) {
    return f.call(thisArg, ...fixedArgs.concat(args))
    //f.apply(thisArg, fixedArgs.concat(args))
    //apply 传入一个数组 不能分开
  }
}

//f2是 function bound f2里没用过this
f3 = f2.call(obj2)
f3()
//f2 this是obj2 但是f3运行调用f2 但f2没用this 没意义
//f3的this是window


a = new f()
//this 是新创建对象 对象以f的prototype为原型
//f不返回对象 会返回this

箭头函数的不同

af = (a) => { return a + b }
new af()
//错误 箭头函数不能做构造函数
//箭头函数里面没有this 箭头函数内部也没有arguments 词法作用域 找this往外看 
f=()=>console.log(this)
//往外找 this是window
f.call([1,2,3])
//this 还是window 箭头函数的this没法绑定
function f() {
  var af = () => {
    arguments
    return this.val * this.val
  }
  return af.call({ val: 8 })
}
obj = {
  val: 2,
  foo: f
}
obj.foo()
// this 是obj
//箭头函数不要写在原型链上
function TreeNode(val) {
  this.val = val
  this.left = this.right = null
}

TreeNode.prototype.getValue = () => {
  return this.val
}
a = new TreeNode(5)
b = new TreeNode(6)
a.getValue()
//this 箭头函数 往外看 是window

a.getValue === b.getValue
//true
function TreeNode(val) {
  this.val = val
  this.left = this.right = null
  this.getValue = () => {
    return this.val
  }
}


a = new TreeNode(5)
a.getValue()
b = new TreeNode(6)
b.getValue()

a.getValue === b.getValue
//false

ary.push2 = function (val) {
  this[this.length] = val
  return this.length
}

function forEach(ary, action) {
  for (var i = 0; i < ary.length; i++) {
    action(ary[i], i, ary)
    //这里是函数调用 action的this 是window
  }
}

var ary = [1, 2, 3]
[4, 5].forEach(ary.push)
//尝试把push(ary[i], i, ary) 的this undefined 转换为数组   
ary.push(4, 0, [4, 5])



p = ary.push
p(5) //this还是 数组
p.call(obj2={0:1,1:2,length:2},8)

[3.5].forEach(ary.push.bind(ary))


[3.5].forEach(ary.push,thisArgOfFirstArgument)
//ary.push的this 变成 指定参数


p=ary.push.bind(ary)
p(5)
p(6)

function forEach(ary, action,thisArg=undefined) {
  for (var i = 0; i < ary.length; i++) {
    action.call(thisArg,ary[i], i, ary)
  }
}

  • f() 这样调用 this是window 或者 undefined
var a = [1, 3, 4, 5, 2];
//这里一定要加分号 因为下一行是[]开头
[8, 9].forEach(a.push.bind(a))
//bind返回的是函数
console.log(a)
//[1, 3, 4, 5, 2, 8, 0, [8, 9], 9, 1, [8, 9]]


var a = [1]

[8, 9].forEach(a.push.call(a))
// 报错()传的是a.push call a 的结果 相当于调用push 返回数 代表数组被push后的长度 apply 同理不行

console.log(a.push.call(a))
//1 相当于a,push()

[1,2,3].forEach((it)=>a.push(it))
a.push(...[1,2,3])
//伪代码
function getProperty(obj, p) {
  if (obj == null) {
    return Error('can not read property' + p + "of" + obj)
  }
  if (obj has own property p) {
    return obj[p]
  } else {
    return getProperty(obj._proto_, p)
  }
}
  • 原型继承 继承属性

如果想要创建一批共享同一个原型对象

var proto = {}
a = Object.create(proto)
b = Object.create(proto)

a = {}
a._proto_ = proto
b = {}
b._proto_ = proto


function TreeNode(val) {
  this.val = val
  this.left = null
  this.right = null
}
var a = new TreeNode(1)
var b = new TreeNode(2)
var c = new TreeNode(3)

// a._proto_ 和 TreeNode.prototype 指向同一个对象
  • 匿名函数、箭头函数的名字是第一次绑定的变量的变量名,.name调用
Object.prototype.forOwn = function (action) {
  var hasOwn = Object.prototype.hasOwnProperty
  for (var key in this) {
    if (this.hasOwnProperty.call(this, key)) {
      action(this[key], key, this)
    }
  }
}
obj.forOwn((val, key) => {
})