js原型与对象的种种因缘~

162 阅读10分钟

js语法补充

with

with语句可以形成自己的作用域
var obj = {
  name: 'zsf'
}
with(obj) {
  console.log(name)
}

现在已经不推荐了

eval

全局函数

var str = 'console.log(123)'
eval(str)//123

不建议:

  • 可读性差
  • 可能会被修改内容,被攻击
  • 必须经过js解释器,不能js引擎优化,执行效率低

严格模式

es5提出

严格模式的限制:

  • 通过抛出错误来消除一些原有的静默(silent)错误
  • js引擎执行代码时可以进行更多的优化
  • 禁用ECMAScript未来版本可能会定义的语法

开启

  • js文件
  • 某个函数中

js文件

文件顶部写上 use strict

某个函数中

function () {
    'use strict'
    ...
}

严格模式常见限制

  • 意外创建全局变量
  • 不允许函数有相同参数名称
  • 静默错误
  • 不允许使用原先的8进制格式
  • 不允许with语句
  • eval函数不会向上引用变量
  • 独立函数(自执行)的this指向undefined
  • setTimeout的this指向依然是window

面向对象

创建对象

创建对象方式:

  • 字面量
  • new Object

字面量

var obj = {
    ...
}

new

var obj = new Object()
obj.name = 'zsf'

创建多个对象的方案

工厂模式

function Person(name, age) {
  var p = {}
  p.name = name
  p.age = age
  p.run = function run() {
    console.log('跑步')
  }
  return p
}
var p1 = new Person('zsf', 18)
var p2 = new Person('hhh', 19)

console.log(p1)
console.log(p2)

缺点:

打印对象时,都是Object类型,获取不到对象真实的类型

构造函数(构造器)

当使用new调用函数时,和普通调用函数有什么区别?

new调用函数时,发生了什么?

  1. 在内存中创建一个新对象(空)
  2. 构造函数显式原型prototype赋值给前面创建出来的对象隐式原型proto
  3. 构造函数内部的this,会指向创建出来的新对象
  4. 执行函数代码
  5. 如果构造函数没有返回非空对象,这返回创建出来的新对象

用代码表示

function Person() {
  
}
var p = new Person()

内部原理

function Person() {
  var moni = {}
  this = moni
  this.__proto__ = Person.prototype
  执行代码
  return moni
}
var p = new Person()

moni对象变成了 { __proto__: Person.prototype }

function Person(name, age) {
  this.name = name
  this.age = age

}
var p1 = new Person('zsf', 18)
console.log(p1)

弥补了工厂模式的不足

但是也有缺点

当对象内某个属性的值是函数的时候,相当于重复的创建

原型和和构造函数结合

function Person(name, age) {
  this.name = name
  this.age = age
}
// 函数放原型
Person.prototype.eat = function () {
  console.log(this.name + '吃东西')
}

var p1 = new Person('zsf', 18)
var p2 = new Person('hhh', 19)

p1.eat()
p2.eat()

对操作属性的控制

需求:对一个属性进行比较精准的操作控制

需要使用属性修饰符

  • 可以精准地添加或修改对象的属性
  • 使用Object.definedProperty添加或修改属性

Object.definedProperty

Object.definedProperty()会直接在一个对象定义一个新属性,或者修改一个对象的现有属性,并返回此对象

可接收3个参数:

  • obj要操作的对象
  • prop要定义或修改的属性名称或Symbol
  • description要定义或修改的属性描述符

返回值

传递给函数的对象

var obj = {
  name: 'zsf',
  age: 18
}
// 属性描述符是一个对象
Object.defineProperty(obj, 'height', {
  value: 1.88
})

console.log(obj)

属性描述符

分2类:

  • 数据属性描述符
  • 存取属性描述符

image-20220305115927992.png

Configurable

是否可以通过delete删除属性,是否可以修改它的属性,或者是否可以将它修改成存取属性,默认false

Enumerable

是否可以通过for-inObject.keys返回该属性,默认false

Writable

是否可以修改属性的值,默认false

value

属性的值,默认undefined

var obj = {
  name: 'zsf',
  age: 18,
  _address: '广州市'
}
// 存取属性描述符
Object.defineProperty(obj, 'address', {
  enumerable: true,
  configurable: true,
  // 私有属性
  get: function() {
    return this._address
  },
   set: function(value) {
     this._address = value
   }
})

console.log(obj.address)

存取属性描述符应用场景:

  • 隐藏私有属性不希望被外界使用和赋值
  • 如果希望截获一个属性它访问和设置过程
var obj = {
  name: 'zsf',
  age: 18,
  _address: '广州市'
}
// 存取属性描述符
Object.defineProperty(obj, 'address', {
  enumerable: true,
  configurable: true,
  // 私有属性
  get: function() {
    foo()
    return this._address
  },
   set: function(value) {
     this._address = value
   }
})
function foo() {
    console.log('被使用了')
}
console.log(obj.address)

定义多个属性描述符

var obj = {
  
}
// 多个属性描述符
Object.defineProperties(obj, {
  name: {
    enumerable: true,
    configurable: true,
    value: 'zsf'
  },
  age: {
    value: 19
  }
})

console.log(obj)

对象的其它方法

  • preventExtends(obj) 阻止拓展(添加属性)
  • getOwnPropertyDescriptors 获取对象所有属性的描述符
  • seal 设置所有属性描述符为false

原型

js中每个对象都有一个__proto__属性(是个对象),这个对象叫对象的原型隐式原型),这个对象还会指向另一个对象(对应的原型对象

var obj = {
  name: 'zsf',
  __proro__: {}
}

对象原型(隐式原型)

隐式原型有什么用呢?

  1. 当前对象中去查找对应的属性,如果找到就直接使用
  2. 如果没有找到,那么会沿着他的原型去查找
  3. 为什么要把有些属性放进原型,而不放在对象里?为了继承,这样就不用每个对象都放,而是需要就去原型里面找,这也就解决了构造函数可能重复创建某些可能不需要的属性了
var obj = {
  name: 'zsf'
}
console.log(obj.age)// undefined
var obj = {
  name: 'zsf'
}
obj.__proto__.age = 10
console.log(obj.age)// 10

函数原型(显式原型)

函数原型有什么用呢?

new调用函数时,发生了什么?

  1. 在内存中创建一个新对象(空)
  2. 构造函数显式原型prototype赋值给前面创建出来的对象隐式原型proto
  3. 构造函数内部的this,会指向创建出来的新对象
  4. 执行函数代码
  5. 如果构造函数没有返回非空对象,这返回创建出来的新对象

第2步

function foo() {
  // 下面三行代码是内部自动操作的,不用写
  var moni = {}
  this = {}
  // 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
  this.__proto__ = foo.prototype
}

console.log(foo.prototype)

new foo()

构造函数的prototype

构造函数prototype属性指向了他的原型对象,该原型对象里面有constructor属性

image-20220305193122666.png

prototype.constructor指向构造函数本身

可以往prototype里添加的属性吗?

可以的

而且还可以添加多个

function foo() {
  
}

foo.prototype = {
  name: 'zsf',
  age: 18
}

var f1 = new foo()
console.log(f1.name, f1.age);

但是一定要加constructor,原来prototype有,不添加会影响原型链

function foo() {
  
}

foo.prototype = {
  constructor: foo,
  name: 'zsf',
  age: 18
}

var f1 = new foo()
console.log(f1.name, f1.age);

这样添加的constructor,可枚举为true,应该设置为false

开发中应该这样添加constructor(用对象的defineProperty)

function foo() {
  
}

foo.prototype = {
  name: 'zsf',
  age: 18
}
Object.defineProperty(foo.prototype, 'constructor', {
  enumerable: false,
  configurable: true,
  writable: true,
  value: foo
})

var f1 = new foo()
console.log(f1.name, f1.age);

对象的其它方法、操作符

hasOwnProperty()对象是否有某一个属于自己的属性(而不是原型上的属性)
in/for in 操作符判断某个属性是否在对象上或对象的原型上
instanceof检测构造函数的prototype,是否出现在某个实例对象的原型链上(stu instanceof Student)
isPrototypeOf()检测某个对象,是否出现某个实例对象的原型链

原型链

从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取

可观察到原型链的例子

var obj = {
  name: 'zsf',
  age: 18
}

obj.__proto__ = {

}

console.log(obj.address)// undefined

obj.__proto__.__proto__ = {
  address: '广州'
}

console.log(obj.address)// 广州

image-20220305202808053.png

Object的原型对象

原型链最顶层的原型对象--Object原型对象

image-20220305212629232.png

原型链是有尽头的(Object.prototype)

var obj = {
  name: 'zsf',
  age: 18
}

console.log(obj.__proto__)// 尽头
console.log(obj.__proto__.__proto__)// null 因为没了

new Object() 发生了什么,类似于new调用构造函数

var obj1 = new Object()

Object()内部

var moni = {}
this = moni
this.__proto__ = Object.prototype
执行代码
return moni

moni对象变成了 { __proto__: Object.prototype }

返回的moni对象会被obj1接收

也就是

obj1.__proto__ = Object.prototype

Object本质也是构造函数

既然是构造函数,那就有显式原型prototype

继承

为什么需要继承?

当多个类有很多重复的代码,应该把公共的部分抽离出来封装成父类,子类继承

function Student(name, age) {
  this.name = name
  this.age = age
}

Student.prototype.run = function () {
  console.log(this.name + '跑步')
}


function Teacher(name, age) {
  this.name = name
  this.age = age
}

Teacher.prototype.run = function () {
  console.log(this.name + '跑步')
}

直接使用原型链继承

继承例子

function Person(name, age) {
  this.name = 'zsf'
  this.age = age
}

Person.prototype.run = function () {
  console.log(this.name + '跑步')
}

function Teacher(name) {
  this.age = 't'
}

var p = new Person()
Teacher.prototype = p

Teacher.prototype.eat = function () {
  console.log(this.name + '吃')
}

var t1 = new Teacher()

console.log(t1.name)
t1.run()
console.log(p)

本来t1是没有name和run()的,加上

var p = new Person()
Teacher.prototype = p

之后,就有了

image-20220306085816944.png

弊端

这种继承有3种弊端

  • 打印t1对象,继承属性是看不到的
  • 当修改父类中的属性时,多个子类都会受到影响
  • 实现类的过程都没有传参数

借用构造函数继承

原理

image-20220306094103443.png

为了能复用age,name这些属性,借用了构造函数Person(),把它当成普通函数去调用,通过call绑定this与Student,这样就是给Student添加age,name这些属性,而不是给Person添加

image-20220306095412686.png

为了能复用running这些方法,new Person()时创建出来一个p对象 ,p对象的隐式原型__proto__又指向Person显式原型prototype,然后将Student的显式原型prototype指向p对象,这样就做到了Student的显式原型prototype指向Person显式原型prototype(但是不能直接Student的显式原型prototype指向Person显式原型prototype!!!)

这样,当stu.running()的时候就会沿着原型链找到这个方法,从而实现复用

如果直接 Student.prototype = Person.prototype

image-20220306101207414.png

那当Student.prototype.studying = function(){}的时候,就是给Person添加studying方法了,显然,本来是给子类添加方法现在把方法添加给了父类,这不符合面向对象的原则

function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.run = function () {
  console.log(this.name + '跑步')
}

function Teacher(name, age) {
  Person.call(this, name, age)
  this.tno = 102
}

var p = new Person()
Teacher.prototype = p

Teacher.prototype.eat = function () {
  console.log(this.name + '吃')
}

var t1 = new Teacher('zsf', 18)

console.log(t1)

image-20220306092215724.png

弊端

解决了原型链继承的弊端,但是这种也有2种弊端:

  • 父类构造函数至少被调用2次
  • t1原型对象(p)有多余的属性(子类已经有了)

寄生式继承

道格拉斯

原型式继承+工厂函数

var personObj = {
  running: function () {
    console.log('跑')
  }
} 

function createStudent(name) {
  var stu = Object.create(personObj)
  stu.name = name
  stu.stufying = function () {
    console.log('study')
  }
  
   return stu
}

var stuObj1 = createStudent('zsf')
var stuObj2 = createStudent('hhh')

这种方式有原型式继承和工厂函数的弊端

寄生组合式继承

function Person(name, age, friends) {
  this.name = name
  this.age = age
  this.friends = friends
}

Person.prototype.running = function () {
  console.log('running')
}

Person.prototype.eating = function () {
  console.log('eating')
}
// 创建出一个对象,并指向Person的显式原型(prototype),然后赋值给Student的prototype
Student.prototype = Object.create(Person.prototype)
// 修改准确描述符,不然打印出来的是Person类型,因为刚刚创建出来的对象没有constructor,所以会上去找Person的constructor,而constructor指向构造函数本身,因此是Person类
Object.defineProperty(Student.prototype, 'constructor', {
  enumerable: false,
  configurable: true,
  writable: true,
  value: Student
})

function Student(name, age, friends, sno, score) {
  Person.call(this, name, age, friends)
  this.sno = sno
  this.score = score
}

Student.prototype.studying = function () {
  console.log('studying')
}

这样不具有通用性

因此可以封装一个工具函数

// 当然,要是觉得Object.create太新,也可以自定义,换掉就行
function createObject(o) {
  function fn() {}
  fn.prototype = 0
  return new fn()
}

// 封装成工具类函数
function inheritPrototype(SubType, SuperType) {
  // 创建出一个对象,并指向Person的显式原型(prototype),然后赋值给Student的prototype
  SubType.prototype = Object.create(SuperType.prototype)
  // 修改准确描述符,不然打印出来的是Person类型,因为刚刚创建出来的对象没有constructor,所以会上去找Person的constructor,而constructor指向构造函数本身,因此是Person类
  Object.defineProperty(SubType.prototype, 'constructor', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: SubType
  })
}


function Person(name, age, friends) {
  this.name = name
  this.age = age
  this.friends = friends
}

Person.prototype.running = function () {
  console.log('running')
}

Person.prototype.eating = function () {
  console.log('eating')
}


function Student(name, age, friends, sno, score) {
  Person.call(this, name, age, friends)
  this.sno = sno
  this.score = score
}
// 寄生组合式继承
inheritPrototype(Student, Person)

Student.prototype.studying = function () {
  console.log('studying')
}

原型继承关系

image-20220306094616471.png

函数的特殊

  • foo是一个函数,有个显示原型prototype
  • foo也是一个对象,也会有一个隐式原型__proto__
  • 这两个原型不相等
  • prototype来自哪里?创建了一个函数 foo.prototype = {constructor: foo }
  • __proto__来自哪里? new Function(), foo.__proto__ = Function.prototype, 而 Function.prototype = { constructor: Function }
  • 特殊:Function.__proto__ = Function.prototype
function foo() {
  
}
// 等同于
var foo = new Function()