2020-05-12 JavaScript基础-原型与原型链

面向对象

面向对象编程(Object Oriented Programming,缩写OOP):将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟,其有三个特征:封装、继承和多态

对象

  • 对象是单个实物的抽象
  • 对象是一个容器,封装了属性(property)和方法(method)

构造函数:就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构,为了与普通函数区别,构造函数名字的第一个字母通常大写

有两个特点:

  • 函数体内部使用了this关键字,代表了所要生成的对象实例
  • 生成对象的时候,必须使用new命令

new命令运行的基本原理:

  1. 创建一个空对象,作为将要返回的实例对象
  2. 将这个空对象的原型,指向构造函数的prototype属性
  3. 将空对象赋值给函数内部的this关键字
  4. 开始执行构造函数内部的代码

提示:如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象

new命令的内部运行代码:

function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
  // 将 arguments 对象转为数组
  var args = [].slice.call(arguments);
  // 取出构造函数
  var constructor = args.shift();
  // 创建一个空对象,继承构造函数的 prototype 属性
  var context = Object.create(constructor.prototype);
  // 执行构造函数
  var result = constructor.apply(context, args);
  // 如果返回结果是对象,就直接返回,否则返回 context 对象
  return (typeof result === 'object' && result != null) ? result : context;
}

// 实例
function Person(name, age) {
  this.name = name;
  this.age = age;
}
var actor = _new(Person, '张三', 28);

使用new.target属性可以判断函数调用的时候,是否使用new命令

Object.create() 创建实例对象
构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()方法,继承了前者的属性和方法

this关键字

简单说,this就是属性或方法“当前”所在的对象

  • 它总是返回一个对象
  • 由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即this的指向是可变的
  • 它的设计目的就是在函数体内部,指代函数当前的运行环境
使用场合
  1. 事件调用环境——谁触发事件,指向谁
  2. 全局环境——window(浏览器环境)、module.exports(node环境),严格模式都是undefined
  3. 构造函数——构造函数中的this,指的是实例对象
  4. 对象的方法——如果对象的方法里面包含thisthis的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向
  5. 在箭头函数中——指的是函数定义生效时所在的对象,而不是使用时所在的对象
this的五种绑定规则(除了箭头函数,this总是指向调用该函数的对象)
  1. 默认绑定(严格/非严格模式):指向全局对象或者undefined
  2. 隐式绑定:上下文对象
  3. 显示绑定:使用callapplybind
  4. new绑定
  5. 箭头函数绑定:
    - 箭头函数不绑定this,箭头函数中的this相当于普通变量,指向外层作用域,对象没有单独的作用域
    - 箭头函数的this寻值行为与普通变量相同,在作用域中逐级寻找
    - 箭头函数的this无法通过bindcallapply来直接修改(可以间接修改)
    - 改变作用域中this的指向可以改变箭头函数的this
    - eg. function closure(){()=>{//code }},在此例中,我们通过改变封包环境closure.bind(another)(),来改变箭头函数this的指向
使用注意点
  • 避免多层 this,由于this的指向是不确定的,所以切勿在函数中包含多层的this,使用一个变量固定this的值,然后内层函数调用这个变量,是非常常见的做法
  • 避免数组处理方法中的 this,数组的mapforeach方法,允许提供一个函数作为参数。这个函数内部不应该使用this,解决这个问题的一种方法,就是前面提到的,使用中间变量固定this;另一种方法是将this当作foreach方法的第二个参数,固定它的运行环境
  • 避免回调函数中的 this,回调函数中的this往往会改变指向,最好避免使用,为了解决这个问题,可以采用下面的一些方法对this进行绑定,也就是使得this固定指向某个对象,减少不确定性
绑定 this的方法

this的动态切换,固然为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。有时,需要把this固定下来,避免出现意想不到的情况。JavaScript 提供了callapplybind这三个方法,来切换/固定this的指向

  1. Function.prototype.call():函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数,call方法的参数,应该是一个对象。如果参数为空、nullundefined,则默认传入全局对象,如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法,形式如func.call(thisValue, arg1, arg2, ...)
  2. Function.prototype.apply()apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下:func.apply(thisValue, [arg1, arg2, ...]),利用这一点,可以做一些有趣的应用:
    - 找出数组最大元素
    - 将数组的空元素变为undefined
    - 转换类似数组的对象
    - 绑定回调函数的对象
  3. Function.prototype.bind()bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数,bind()方法有一些使用注意点:
    - 每一次返回一个新函数
    - 结合回调函数使用
    - 结合call()方法使用

原型与原型链

prototype属性:每个函数都有一个prototype属性,指向一个对象,原型对象的所有属性和方法,都能被实例对象共享

function f(){}
f.prototype // f {}
typeof f.prototype // 'object'

函数默认具有prototype属性,对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象
原型链:JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOftoString方法的原因,因为这是从Object.prototype继承的
那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是nullnull没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null

constructor属性prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数

function P(){}
P.prototype.constructor === P // Function: P

由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承

function P() {}
var p = new P();

p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false

constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的
修改原型对象时,一般要同时修改constructor属性的指向
如果不能确定constructor属性是什么函数,还有一个办法:通过name属性,从实例得到构造函数的名称

function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"

__proto__属性:这个属性是每个实例对象特有的(当然构造函数也有),指向函数的prototype原型对象,让实例找到自己的原型对象,查找原型链用的

function P(){}
var p = new P();
p.__proto__ === P.prototype // P {}
function A(){}
var a = new A();
A.prototype // A {}
Function.prototype // [Function]
Object.prototype // {}

a.__proto__ // A {}
a.__proyo__.__proto__ // {}
a.__proto__.__proto__.__proto__ // null

Object对象相关方法

  • Object.getPrototypeOf():返回参数对象的原型。这是获取原型对象的标准方法

    var F = function () {};
    var f = new F();
    Object.getPrototypeOf(f) === F.prototype // true
    

    下面是几种特殊对象的原型

    // 空对象的原型是 Object.prototype
    Object.getPrototypeOf({}) === Object.prototype // true
    
    // Object.prototype 的原型是 null
    Object.getPrototypeOf(Object.prototype) === null // true
    
    // 函数的原型是 Function.prototype
    function f() {}
    Object.getPrototypeOf(f) === Function.prototype // true
    
  • Object.setPrototypeOf():为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象

    var a = {};
    var b = {x: 1};
    Object.setPrototypeOf(a, b);
    
    Object.getPrototypeOf(a) === b // true
    a.x // 1
    
  • Object.create():该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性

    // 原型对象
    var A = {
      print: function () {
        console.log('hello');
      }
    };
    
    // 实例对象
    var B = Object.create(A);
    
    Object.getPrototypeOf(B) === A // true
    B.print() // hello
    B.print === A.print // true
    
  • Object.prototype.isPrototypeOf():实例对象的isPrototypeOf方法,用来判断该对象是否为参数对象的原型

    var o1 = {};
    var o2 = Object.create(o1);
    var o3 = Object.create(o2);
    
    o2.isPrototypeOf(o3) // true
    o1.isPrototypeOf(o3) // true
    
  • Object.prototype.__proto__:实例对象的__proto__属性(前后各两个下划线),返回该对象的原型

  • 获取原型对象方法的比较:

    • obj.__proto__
    • obj.constructor.prototype
    • Object.getPrototypeOf(obj) 推荐
  • Object.getOwnPropertyNames:返回一个数组,成员是参数对象本身的所有属性的键名,不包含继承的属性键名

  • Object.prototype.hasOwnProperty():返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上

  • Object.keys():用来遍历对象的属性,参数是一个对象,返回一个数组;该数组的成员都是该对象自身而不是继承的所有属性名

  • Object.getOwnPropertyNames():返回包括不可枚举的所有属性名

  • Object.prototype.valueOf():返回一个对象的‘值’,默认情况下返回对象本身

  • Object.prototype.toString():返回一个对象的字符串形式,默认情况下返回类型字符串,其主要应用是判断数据类型

参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值