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

JS基础——原型 #14

Open
L-small opened this issue Apr 30, 2020 · 0 comments
Open

JS基础——原型 #14

L-small opened this issue Apr 30, 2020 · 0 comments

Comments

@L-small
Copy link
Owner

L-small commented Apr 30, 2020

原型和对象

原型

通过这个图就概括了
image

原型规则

  • 所有的引用类型都有对象的特性,可以自有扩展
  • 所有的引用类型都有一个__proto__属性(隐式原型),属性的值也是一个对象
  • 所有的函数都有一个prototype属性(显示原型),属性值也是一个对象
  • 所有的引用类型,其隐式原型指向其构造函数的显示原型
  • 当试图得到一个对象的某个属性,如果这个对象上没有,则会去它的__proto__上去找

原型链

访问对象的属性,从实例一直通过__proto__构成的链条则是原型链
image

获取原型和实例的关系

instanceof

实现原理:判断左侧的原型链上是否有右侧的原型

实现

function isInstanceOf(left, right) {
  while (true) {
    if (left.__proto__ === null ) {
      return false
    }
    if (left.__proto__ === right.prototype) {
      return true
    }
    left = left.__proto
  }
}

isPrototypeOf

测试一个对象是否在另一个对象的原型链上

var a = {}
Object.prototype.isPrototypeOf(a)  // true

构造函数

特点:

  • 函数名第一个字母大写
  • 函数体内使用this关键字,代表要生成的对象实例
  • 生成对象的时候必须要用new来调用构造函数

几种对象的创建

工厂模式

function createPerson(name, age) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.sayName = function() {
    alert(this.name) 
  }
  return o
}
var a = createPerson('zhang', 13);
var b = createPerson('lisi', 14)
a !== b

优点:解决了多个相似对象的问题
缺点:没有解决对象识别的问题,实例都指向Object的原型(不像Date,Array一样)

构造函数模式

function Person(name) {
  this.name = name;
  this.age = age;
  this.getName = getName
}
function getName(){
  alert(this.name)
}
var person1 = new Person('zhangsan', 13)

优点:可以识别对象类型,person1 instanceOf Person // true
缺点:没有封装性,内部方法暴露在全局作用域

原型模式

function Person() {}
Person.prototype.name = 'name'
Person.prototype.getName = function () {
  cnosole.log(this.name)
}

var person1 = new Person()

优点:实例可共享原型对象的方法和属性
缺点:修改原型对象引用类型值的时候导致实例值变化

组合模式

function Person(name){
  this.name = name;
}
Person.prototype = {
  constructor: Person,
  sayName = fucntion () {
    console.log(this.name)
  }
}

优点:结合了原型模式和构造函数模式的优点。目前使用最广泛的

寄生构造函数模式

fucntion SpecialArray() {
  var values = new Array()
  values.push(...arguments)
  values.toPipedString = function (){
    return this.join('|')
  }
  return values
}
var colors = new SpecialArray('green', ''red);
colors.toPipedString()  // green|red

优点:当为一些Array等添加方法和属性,不用修改Array的构造函数
缺点:其实和直接声明方法没啥差别

稳妥构造函数模式

没有实例属性,也不引用this对象

function Person (name) {
  var o = new Object()
  o.sayName = function() {
    console.log(name)
  }
  return name
}
var person = Person('张三')
person.name = '李四'
person.sayName()  // 张三

优点:适合一些安全环境
缺点:无法识别对象的原型

几种继承

继承主要靠原型链实现

原型链继承

function Parent() {
  this.name = 'zhangsan'
}
Parent.prototype.getName = function() {
  console.log(this.name)
}
function Child() {}

Child.prototype = new Parent();
// Child.prototype.constructor = Child   // 不修正的话后面实例给原型添加属性则是添加到了Parent原型上
var child1 = new Child()
child1.getName()  // zhangsan
child1.constructor.prototype

优点:原型上的方法和属性可以共享。实现简单
缺点:

  • 引用类型的值修改会让所有实例的值修改
  • 创建子类型的实例的时候,不能向父类型传递参数

借用构造函数

function Parent(name) {
  this.name = name
}
function Child(name) {
  Parent.call(this, name)
}
var person1 = new Child(['zhangsan', 'lisi', 'wang'])
person1.name.push('wang')
var person2 = new Child(['zhangsan', 'lisi'])
person2.name   // 'zhangsan', 'lisi'

优点:

  • 可以借助子类型向父类型传递参数
  • 避免引用类型的被所有实例共享

缺点:方法在构造函数中创建,每次都要创建。父类型原型上的方法子类型不能继承

组合继承

function Parent(name) {
  this.name = name
  this.colors = ['green']
}
Parent.prototype.getName = function() {
  console.log(name)
}
function Child(name) {
  Parent.call(this, name)
} 
Child.prototype = new Parent();
Child.prototype.constructor = Child; 
Child.prototype.sayColor = function () {
  console.log(this.colors);
}
var child1 = new Child('kevin', '18');

child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('daisy', '20');

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

优点:集成了借用构造函数模式和原型链模式的优点。最常用的模式
缺点:调用两次构造函数,生成了两份实例

原型继承

function createObj(o) {
  function F() {}
  F.prototype = o
  return new F();
}

Object.create的实现
优点:不用创建自定义类型
缺点:包含引用类型的属性值始终都会共享相应的值(对传入的对象进行了一次浅复制)

寄生式继承

function createObj(o){
  var clone = Object.create(o)
  clone.sayName = function() {
    console.log('hi')
  }
  return clone
}

缺点:跟借用构造函数一样,每次创建对象都要创建一次方法

寄生组合式继承

function Parent (name) {
  this.name = name;
  this.color = []
}
Parent.prototype.getName = function(){
  console.log(this.name)
}
function Child(name, age) {
  Parent.call(this, name)
  this.age = age
}

// 减少一次调用构造函数
function createObj(o) {
  function F() {}
  F.prototype = o
  return new F();
}
function inheritPrototype(subType, superType) {
  var prototype = createObj(superType.prototype)
  prototype.constructor = subType;
  subType.prototype = prototype;
}
inheritPrototype(Child, Parent)

或者
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

优点:只调用了一次构造函数,减少在Parent.prototype上添加不必要的属性,原型链保持不变。可正常使用instanceof等

类的继承

class Child extends Parent {
  constructor() {
    super()
  }
}

super相当于Parent.call(this)
extends相当于原型继承

总结:

创建对象:

  • 构造函数模式:有私有变量和方法,可创建自定义类型
  • 原型模式:可以创建共有变量和方法,相当于浅复制
    继承:
  • 借用构造函数模式:有私有变量
  • 原型链继承:创建共有的变量和方法,相当于浅复制
  • 原型继承:不用构建构造函数实现继承,相当于执行给定对象浅复制,然后改造后返回
  • 寄生继承:基于给的对象创建个新的对象,并加强,然后返回创建的对象

new

当用new操作符调用构造函数时,经历了如下的过程:

  1. 创建一个新的对象
  2. 将构造函数的作用域给新对象
  3. 执行构造函数中的代码
  4. 返回新对象
    其实就是一个继承,返回的一个构造函数的实例,Object.create其实也是。

模拟实现

由于new是关键字,所以通过objectFactory来实现。

functino Parent(xxx) {}
var child = objectFactory(Parent, xxx)

function objectFactory(){
  // 1 创建新对象
  var obj = Object.create();
  var _constructor = [].shift.call(arguments);
  // 2 继承
  obj.__proto__ = _constructor.prototype;
  // 3 执行代码,修改this指向到obj,obj可以访问构造函数的属性
  var ret = _constructor.apply(obj, arguments);
  // 4 构造函数如果没有返回值,则实例可以访问构造函数属性,如果有返回值则不会,所以要判断一下
  return typeof ret === 'object' ? ret : obj
}
实现二
Function.prototype.newFn = function() {
  var obj = Object.create(this.prototype);
  var ret = this.apply(obj, arguments)
  return typeof ret === 'object' ? ret : obj
}

判断是否是对函数应用了new

如果应用了new关键字,根据原理,我们在执行构造函数代码的时候会进行this绑定,所以this其实就是构造函数自己,所以会返回true。如果用函数调用的话this其实是指向window的,所以会返回False。

function Person() {
  console.log(this instanceof Person) 
  // true则是用了new 
  // false则是函数调用
}
var a = Person()   // false
var b = new Person()  // true

对象属性类型

数据属性

所有通过new Object或者对象字面量为对象添加属性都是数据属性

var obj = {
  a: 1,
  b: function () {}
}
等于
Object.defineProperties(obj, {
  a: {
    value: 1
    // configurable,enumeurable,writable默认为true
  },
  b: {
    value: function() {}
    // configurable,enumeurable,writable默认为true
  }
})

访问器属性

必须通过Object.defineProperty等定义的。可以用来实现双向绑定

var book = { 
  hideYear: 2004, 
  edition: 1 
}; 
Object.defineProperty(book, "year", { 
  get: function(){ 
    return this.hideYear; 
  }, 
  set: function(newValue){ 
    if (newValue > 2004) { 
      this.hideYear = newValue; 
      this.edition += newValue - 2004; 
    } 
  } 
}); 

读取属性配置

getOwnPropertyDescriptor()

扩展

隐形声明全局变量导致configurable属性为false

var a = 1   // configurable false,let 和 const、function等一样,只要声明过就是false
delete a   // false不会删除

var a = 1
a = 3    // 由于已经声明过了所以还是false
delete a   // false 不会删除

b = 2   // configurable true
delete b   // true 会删除

JS对象的数据属性和访问器属性

class

ES6的class的特点:
1、类中定义的方法都是不可枚举的,而ES中原型上的方法是可以枚举的
2、方法或者属性前加上static表示静态的,不被实例继承
3、必须要new调用
4、可以使用get和set

### 创建
class Person {
  constructor(name) {
    this.name = name
  }
  
  sayHello() {
    return 'hello, I am ' + this.name;
  }

  static onlySayHello() {
    return 'hello'
  }

  get name() {
    return 'kevin';
  }

  set name(newName) {
    console.log('new name 为:' + newName)
  }
}

等于ES5


function Person(name) {
  this.name = name;
}
Person.protoype = {
  constructor: Person,
  sayHello: function() {
    return 'hello, I am ' + this.name;
  },
  get name() {
    return 'kevin';
  },
  set name(newName) {
    console.log('new name 为:' + newName)
  }
}
Person.onlySayHello = function() {
  return 'hello'
}

Babel编译后

// 通用式判断是否是new调用
function _classCallCheck(instance, Constructor) {
  // 因为通过new调用就是创建实例,new的实现中,this指向的是新创建的实例对象,所以new调用则是true,如果是函数调用this指向window
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function")
  }
}

function _createClass = function() {
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var desciptor = props[i];
      descriptor.value = props.value;
      // 原型对象上的属性一般不能遍历
      descriptor.enumeurable = false;
      desciptor.configurable = true;
      // 判断是数据属性还是访问器属性,如果不存在直接用传入的get和set
      if ('value' in props[i]) desciptor.writable = true  
      Object.defineProperties(target, desciptor.key, desciptor)
    }
  }
  return function(Constructor, protoProps, staticProps) {
    // 没有加static的属性和方法则是在原型上添加属性和方法
    if (protoProps) defineProperties(Constructor.prototype, protoProps)
    // 添加static的属性和方法则是在构造函数上添加属性和方法,这样实例就访问不到了
    if (staticProps) defineProperties(Constructor, staticProps)
    return Constructor
  }
}()

var Person = function () {
  // 模块模式

  function Person() {
    _classCallCheck(this, Person)

    this.name = name
  }

  _createClass(Person, [{
    key: 'sayHello',
      value: function sayHello() {
        return 'hello, I am ' + this.name;
      }
    }, {
      key: 'name',
      get: function get() {
        return 'kevin';
      },
      set: function set(newName) {
        console.log('new name 为:' + newName);
      }
  }], [{
    key: 'onlySayHello',
    value: function onlySayHello() {
      return 'hello';
    }
  }])

  return Object
}()

继承

特点:

  • 通过extentds关键字实现继承,关键字后面可以是多种类型(只要有prototype属性的函数)比如null
  • super关键字表示父类的构造函数,相当于Parent.call(this)。必须在constructor中调用super,否则新建实例会报错。因为子类没有自己的this对象,而是继承父类的this对象,然后对其加工
  • 子类构造函数只有调用super之后才能使用this关键字
  • 子类可以继承父类的静态属性

关键字后面可以是多种类型

class A extends null {
}

console.log(A.__proto__ === Function.prototype); // true
console.log(A.prototype.__proto__ === undefined); // true

子类可以继承父类的静态属性
是由于class有两条继承链。

  • 子类的__proto__,指向父类。表示构造函数的继承
  • 子类prototype.proto,指向父类的prototype属性,表示方法的继承
    image
    相比寄生组合继承多了一个Object.setPrototypeOf(Child, Parent)。可以实现Child instanceof Parent
class Parent {
    constructor(name) {
        this.name = name;
    }
}

class Child extends Parent {
    constructor(name, age) {
        super(name); // 调用父类的 constructor(name)
        this.age = age;
    }
}

var child1 = new Child('kevin', '18');

console.log(child1);

Babel编译后

// 还是判断是否用new调用
funciton _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    return throw new TypeError("Cannot call a class as a function")
  }
}

// extends继承,多了子类继承父类构造函数
function _inherits(subClass, superClass) {
  // 如果extends右侧不是函数或者null的话则报错
  if(typeof superClass !== 'funciton' && superClass !== 'null') {
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  }
  // 子类原型继承父类,并将constructor修正
  subClass.prototype = Object.create(superClass.prototype, {
    constructor: {
      value: subClass,
      enumerable: false,
      configurable: true,
      writable: true
    }
  });
  // 子类构造函数继承父类构造函数
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

// 判断是否调用super,也就是是否Parent.call(this)
funciton _possibleConstructorReturn(self, call) {
  // 是否调用了super,调用了才有this
  if (!self) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }
  // 
  return (call && (typeof call === 'object' || typeof call === 'funciton')) ? call : self;
}


var Parent = funciton Parent(name) {
  _classCallCheck(this, Parent)
  this.name = name
}

var Child = funciton(_Parent) {
  // 建立原型链关系(原型链继承)
  _inherits(Child, _Parent)
  
  funciton Child(name, age) {
    _classCallCheck(this, Child)
    
    // 由于前面创建了两条继承链,构造函数Child的原型对象是Parent,所以Object.getPrototypeOf(Child)实际上是ParentObject.getPrototypeOf(Child).call(this, name)
    _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child).call(this, name)))

    _this.age = age
  }

  return Child
}(Parent)
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