原型是顺应人类自然思维的产物。中文中有个成语叫做“照猫画虎”,这里的猫看起来就是虎的原型,所以,由此我们可以看出,用原型来
描述对象的方法可以说是古已有之。
如果我们抛开 JavaScript 用于模拟 Java 类的复杂语法设施(如 new、Function Object、函数的 prototype 属性等),原
型系统可以说相当简单,我可以用两条概括:
这个模型在 ES 的各个历史版本中并没有很大改变,但从 ES6 以来,JavaScript 提供了一系列内置函数,以便更为直接地访问操纵
原型。三个方法分别为:
在早期版本的 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 运算接受一个构造器和一组调用参数,实际上做了几件事:
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 属性指向的对象,它是从这个构造器构造出来的所有对象的原型。
我们把这个由__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是判断这个对象属于哪个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可以用来重新定义函数的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 F(arguments)的时候js内部就会调用下面这个New函数
function New (F,arguments) {
var o = {}
o.__proto__ = F.prototype // 以构造器的 prototype 属性(注意与私有字段[[prototype]]的区分)为原型,创建新对象;
F.apply(o, arguments) // 将 this 和调用参数传给构造器,执行;
return o // 如果构造器返回的是对象,则返回,否则返回第一步创建的对象。
}
构造函数创建实例的过程本身就是一种继承,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