首页 > 其他 > 详细

实例-应用和机制

时间:2020-04-04 17:35:30      阅读:42      评论:0      收藏:0      [点我收藏+]

原型

原型是顺应人类自然思维的产物。中文中有个成语叫做“照猫画虎”,这里的猫看起来就是虎的原型,所以,由此我们可以看出,用原型来
描述对象的方法可以说是古已有之。

JavaScript 的原型

如果我们抛开 JavaScript 用于模拟 Java 类的复杂语法设施(如 new、Function Object、函数的 prototype 属性等),原
型系统可以说相当简单,我可以用两条概括:

  • 如果所有对象都有私有字段[[prototype]],就是对象的原型;
  • 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。

这个模型在 ES 的各个历史版本中并没有很大改变,但从 ES6 以来,JavaScript 提供了一系列内置函数,以便更为直接地访问操纵
原型。三个方法分别为:

  • Object.create 根据指定的原型创建新对象,原型可以是 null;
  • Object.getPrototypeOf 获得一个对象的原型;
  • Object.setPrototypeOf 设置一个对象的原型。

在早期版本的 JavaScript 中,“类”的定义是一个私有属性 [[class]],语言标准为内置类型诸如 Number、String、Date 等指
定了[[class]]属性,以表示它们的类。语言使用者唯一可以访问[[class]]属性的方式是 Object.prototype.toString。

以下代码展示了所有具有内置 class 属性的对象:

var o = new Object; 
var n = new Number; 
var s = new String; 
var b = new Boolean; 
var d = new Date; 
var arg = function (){ return arguments }(); 
var r = new RegExp; 
var f = new Function; 
var arr = new Array; 
var e = new Error;
console.log([o, n, s, b, d, arg, r, f, arr, e].map(v => Object.prototype.toString.call(v)));

new 运算接受一个构造器和一组调用参数,实际上做了几件事:

  • 以构造器的 prototype 属性(注意与私有字段[[prototype]]的区分)为原型,创建新对象;
  • 将 this 和调用参数传给构造器,执行;
  • 如果构造器返回的是对象,则返回,否则返回第一步创建的对象。

new 这样的行为,试图让函数对象在语法上跟类变得相似,但是,它客观上提供了两种方式,一是在构造器中添加属性,二是在构造器的prototype 属性上添加属性。

下面代码展示了用构造器模拟类的两种方法:

function c1 () { 
  this.p1 = 1; 
  this.p2 = function (){ 
    console.log(this.p1); 
  }
} 
var o1 = new c1;
o1.p2();
function c2 () {}
c2.prototype.p1 = 1;
c2.prototype.p2 = function (){ 
  console.log(this.p1);
}
var o2 = new c2;
o2.p2();

第一种方法是直接在构造器中修改 this,给 this 添加属性。

第二种方法是修改构造器的 prototype 属性指向的对象,它是从这个构造器构造出来的所有对象的原型。

原型

  • 每个class都有显示原型prototype
  • 每个实例都有隐式原型__proto__
  • 实例的隐式原型__proto__指向对应class的显示原型prototype
  • prototype中有一个constructor属性,用来引用它的构造函数。这是一种循环引用
    Person.prototype.constructor === Person

原型链

我们把这个由__proto__串起来的直到Object.prototype.__proto__为null的链叫做原型链。

对象在获取属性或方法的时候,先在自身的属性和方法中寻找,如果找不到则顺着隐式原型__proto__指向的原型链中一直向上去查找。

原型链关系

var zjh = new Person()
zjh.__proto__ === Person.prototype 
Person.prototype.__proto__ === Object.prototype 
Object.prototype.__proto__ === null 

Object.__proto__ === Function.prototype 
Function.__proto__ === Function.prototype 
Function.prototype.__proto__ === Object.prototype 
Object.prototype.__proto__ === null 

class People {}
class Student extends People {}
// Student的显示原型的隐式原型它正好等于People的显示原型
console.log(Student.prototype.__proto__ === People.prototype) // true
console.log(People.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__ === null) // true 原型链顶端

instanceof

instanceof是判断这个对象属于哪个class,或者哪个构造函数。因为父类也是参与了构建实例的一部分,所以 instanceof 父类
得到的也是true。Object是所有类的父类,因为所有对象都是继承自Object,原型链的末端就是Object。instanceof也是顺着原
型链往上找的。

继承

对象冒充继承

就是修改函数中的this值,就达到了用继承函数中的属性和方法。

this值是通过函数调用的时候确定的,所以就用继承的对象来调用函数。这个对象就变成了要继承的函数的this。也就继承了函数的属性和方法

对象冒充内部实现原理

因为构造函数也是一个函数,所以可以使函数ClassA成为函数ClassB的方法,然后调用它。ClassB就会收到ClassA
构造函数中定义的属性和方法。

function ClassA(sColor) {
  this.color = sColor
  this.sayColor = function () {
    alert(this.color)
  }
}
function ClassB(sColor) {
  this.newMethod = ClassA
  this.newMethod(sColor)
  delete this.newMethod 
  // 以上三步就是call和apply内部实现的核心步骤
}

call,apply,bind

call和apply可以用来重新定义函数的this指向。

call和apply可以实现多重继承,就是一个子类能够继承多个父类。F1可以同时从F2,F3...等继承。

call apply bind方法的内部实现

call和apply改变了函数的this,并且执行了该函数,而bind是改变了函数的this,但bind并不会立即执行函数,而是返回
一个绑定了this的新函数。

call

Function.prototype.call_ = function (obj) {
  //判断是否为null或者undefined,同时考虑传递参数不是对象情况
  obj = obj ? Object(obj) : window
  var args = []
  // 注意i从1开始
  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push("arguments[" + i + "]")
  };
  obj.fn = this // 此时this就是函数fn
  var result = eval("obj.fn(" + args + ")") // 执行fn
  delete obj.fn // 删除fn
  return result
}
// es6
Function.prototype.call_ = function (obj) {
  obj = obj ? Object(obj) : window
  obj.fn = this
  // 利用拓展运算符直接将arguments转为数组
  let args = [...arguments].slice(1)
  let result = obj.fn(...args)
  delete obj.fn
  return result
}

apply

Function.prototype.apply_ = function (obj, arr) {
  obj = obj ? Object(obj) : window
  obj.fn = this
  var result
  if (!arr) {
    result = obj.fn()
  } else {
    var args = []
    // 注意这里的i从0开始
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]")
    }
    result = eval("obj.fn(" + args + ")") // 执行fn
  }
  delete obj.fn //删除fn
  return result
}
// es6
Function.prototype.apply_ = function (obj, arr) {
  obj = obj ? Object(obj) : window
  obj.fn = this
  let result
  if (!arr) {
    result = obj.fn()
  } else {
    result = obj.fn(...arr)
  }
  delete obj.fn
  return result
}

bind

Function.prototype.bind_ = function (obj) {
  if (typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  };
  var args = Array.prototype.slice.call(arguments, 1);
  var fn = this;
  //创建中介函数
  var fn_ = function () {};
  var bound = function () {
    var params = Array.prototype.slice.call(arguments);
    //通过constructor判断调用方式,为true this指向实例,否则为obj
    fn.apply(this.constructor === fn ? this : obj, args.concat(params));
    console.log(this);
  };
  fn_.prototype = fn.prototype;
  bound.prototype = new fn_();
  return bound;
}
// es6
Function.prototype.bind = function(thisArg) {
  if(typeof this !== ‘function‘){
    throw new TypeError(this + ‘must be a function‘)
  }
  // 存储函数本身
  const _this  = this
  // 去除thisArg的其他参数 转成数组
  const args = [...arguments].slice(1)
  // 返回一个函数
  const bound = function() {
    // 可能返回了一个构造函数,我们可以 new F(),所以需要判断
    if (this instanceof bound) {
      return new _this(...args, ...arguments)
    }
    // apply修改this指向,把两个函数的参数合并传给thisArg函数,并执行thisArg函数,返回执行结果
    return _this.apply(thisArg, args.concat(...arguments))
  }
  return bound
}

call,apply应用场景

检验数据类型:

function type(obj) {
    var regexp = /\s(\w+)\]/
    var result =  regexp.exec(Object.prototype.toString.call(obj))[1]
    return result
}
console.log(type([123]))      // Array
console.log(type(‘123‘))      // String
console.log(type(123))        // Number
console.log(type(null))       // Null
console.log(type(undefined))  // Undefined

数组取最大/小值:

var arr = [11, 1, 0, 2, 3, 5];
// 取最大
var max1 = Math.max.call(null, ...arr)
var max2 = Math.max.apply(null, arr)
// 取最小
var min1 = Math.min.call(null, ...arr)
var min2 = Math.min.apply(null, arr)

伪数组转数组:

var fn = function () {
    var arr = Array.prototype.slice.call(arguments)
    console.log(arr) // [1, 2, 3, 4]
};
fn(1, 2, 3, 4);

原型链继承

o.proto = F.prototype 对象继承类

F1.prototype.proto = F2.prototype 类继承类

混合方式继承

对象冒充可以传参数和继承构造函数内部属性,但是不能继承到原型,原型链可以继承原型,但是不能传参数也不能继承构造函数内部,所以要使用混合模式。

// ClassA
function ClassA(sColor) {
  this.color = sColor
}
ClassA.prototype.sayColor = function () {
  alert(this.color)
}
// ClassB继承ClassA
function ClassB(sColor, sName) {
  ClassA.call(this, sColor)
  this.name = sName
}
ClassB.prototype.__proto__ = ClassA.prototype
new和es6的extends实现的继承就是上面的这两个方式的结合。

new操作符的作用

new F(arguments)的时候js内部就会调用下面这个New函数

function New (F,arguments) {
  var o = {}
  o.__proto__ = F.prototype	 // 以构造器的 prototype 属性(注意与私有字段[[prototype]]的区分)为原型,创建新对象;
  F.apply(o, arguments)		   // 将 this 和调用参数传给构造器,执行;      
  return o                   // 如果构造器返回的是对象,则返回,否则返回第一步创建的对象。
} 

new继承

构造函数创建实例的过程本身就是一种继承,new的内部其实是做了继承里面的合体工作

ClassB.prototype = new ClassA()

原型应用

jquery和zepto的简单使用

var $p = $(‘p‘)
$p.css(‘font-size‘, ‘40px‘)
alert($p.html())
var $div1 = $(‘#div1‘)
$div1.css(‘color‘, ‘blue‘)
alert($div1.html())
// $p和$div1都有css和html等方法。当多个实例都可以共用一套方法的时候,就说明这些方法都是来自于一个构造函数的原型中的。

zepto是如何使用原型的

// 定义一个自执行的函数,避免全局变量的污染。
(function (window) {
  // 空对象
  var zepto = {}
  zepto.init = function (selector) {
    var slice = Array.prototype.slice
    var dom = slice.call(document.querySelectorAll(selector))
    return zepto.Z(dom, selector)
  }
  // 即使用zepto时候的$
  var $ = function (selector) {
    return zepto.init(selector)
  }
  window.$ = $ // 把这个$函数给它开放到window这个全局的变量中,就可以$(‘p‘)这样用了
  // 这就是构造函数
  function Z (dom, selector) {
    // 这些初始化操作就是给自己本身复制一些属性
    var i, len = dom ? dom.length : 0
    for (i = 0; i < len; i++) this[i] = dom[i] // 把dom的每个元素复制成它本身自己的属性
    this.length = len                          // 把dom的length复制成自己的length
    this.selector = selector || ‘‘             // 把selector给它传到自己的selector上
  }
  zepto.Z = function (dom, selector) {
    // 注意,出现了new关键字
    return new Z(dom, selector)
  }
  // 如果选择这么用的话$p = $(‘p‘),它返回的就是Z这个构造函数new出来的一个实例。
  // css html这些方法在什么地方呢,Z是构造函数,构造函数得有原型啊,原型是$.fn,$.fn赋值成了一个对象,这个对象中
  // 就有css和html这些方法。
  $.fn = {
    constructor: zepto.Z,
    css: function (key, value) {

    },
    html: function (value) {

    }
  }
  zepto.Z.prototype = Z.prototype = $.fn
})(window)

jquery是如何使用原型的

(function (window) {
  // jquery里面这个$,和jquery源码中的这个jQuery函数是一回事是一个。
  var jQuery = function (selector) {
    // 注意new关键字,第一步就找到了构造函数
    return new jQuery.fn.init(selector)
  }
  window.$ = jQuery
  jQuery.fn = {}
  // 定义构造函数
  var init = jQuery.fn.init = function (selector) {
    // 构造函数体的内容跟zepto的逻辑基本一样
    var slice = Array.prototype.slice
    var dom = slice.call(document.querySelectorAll(selector))
    var i, len = dom ? dom.length : 0
    for (i = 0; i < len; i++) this[i] = dom[i] 
    this.length = len                         
    this.selector = selector || ‘‘             
  }
  // 初始化jQuery.fn
  jQuery.fn = jQuery.prototype = {
    constructor: jQuery,
    css: function (key, value) {

    },
    html (value) {

    }
  }
  // 定义原型
  init.prototype = jQuery.fn
})(window)  

实例-应用和机制

原文:https://www.cnblogs.com/zhaoyang007/p/12632323.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!