关于原型链:一文彻底搞懂原型链

前言原型和继承是js中十分重要的两大概念。深刻理解原型,也是学好继承的前提。 先来看一下构造函数、实例、原型对象之间的关系 「实例与原型对象之间有间接的分割,但实例与构造函数之间没有。」 两个概念js分为「函数对象」和「一般对象」,每个对象都有__proto__属性,然而只有函数对象且「非箭头函数」才有prototype属性。 属性__proto__是一个对象【实例通过__proto__隐式原型指向其原型对象】,它有两个属性,constructor和__proto__;原型对象有一个默认的constructor属性,用于记录实例是由哪个构造函数创立; 原型了解原型创立一个函数(非箭头函数),就会依照特定的规定为这个函数创立一个 prototype 属性(指向原型对象)。默认状况下,所有原型对象主动取得一个名为 constructor 的属性,指回与之关联的构造函数。在自定义构造函数时,原型对象默认只会取得 constructor 属性,其余的所有办法都继承自Object。每次调用构造函数创立一个新实例,这个实例的外部[[Prototype]]指针就会被赋值为构造函数的原型对象。脚本中没有拜访这个[[Prototype]]个性的规范形式,但 Firefox、Safari 和 Chrome会在每个对象上裸露__proto__属性,通过这个属性能够拜访对象的原型。 function Person() {}// 阐明:name,age,job这些本不应该放在原型上,只是为了阐明属性查找机制Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function() { console.log(this.name); };let person1 = new Person()let person2 = new Person()// 申明之后,构造函数就有了一个与之关联的原型对象console.log(Object.prototype.toString.call(Person.prototype)) // [object Object]console.log(Person.prototype) // {constructor: ƒ}// 构造函数有一个 prototype 属性援用其原型对象,而这个原型对象也有一个constructor 属性,援用这个构造函数// 换句话说,两者循环援用console.log(Person.prototype.constructor === Person); // true// 构造函数、原型对象和实例是 3 个齐全不同的对象console.log(person1 !== Person); // true console.log(person1 !== Person.prototype); // true console.log(Person.prototype !== Person); // true// 实例通过__proto__链接到原型对象,它实际上指向暗藏个性[[Prototype]] // 构造函数通过 prototype 属性链接到原型对象,实例与构造函数没有间接分割,与原型对象有间接分割,前面将会画图再次阐明这个问题console.log(person1.__proto__ === Person.prototype); // true conosle.log(person1.__proto__.constructor === Person); // true// 同一个构造函数创立的两个实例,共享同一个原型对象 console.log(person1.__proto__ === person2.__proto__); // true// Object.getPrototypeOf(),返回参数的外部个性[[Prototype]]的值 ,用于获取原型对象,兼容性更好console.log(Object.getPrototypeOf(person1) == Person.prototype); // true复制代码如下图: ...

March 12, 2022 · 2 min · jiezi

搞懂proto与prototype

一,前言对一个知识点是否完全把握,最好的校验方法就是能否用自己的语言将其表述出来。原型与原型链一直是学习 JS 绕不过的知识点,其中proto 与 prototype 最为让人头疼,这里简单的写下我自己的理解,从原型与原型链中拆解 proto 与 prototype ,希望能对大家有所帮助。 一,原型1,定义 在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype)注:prototype属性是函数特有,对象没有该属性原型(prototype) 是什么东西呢,里面又有哪些属性呢?来,让我们拿个具体例子看下: function fn() {};console.dir(fn) 这里我们可以看出 原型(prototype) 是一个对象,对于当前例子来说,里面有 constructor 与 proto两个属性。那这个原型对象有什么作用呢?来,让我们继续往下看~ 2,使用 现在我们知道了原型是一个对象,那原型对象有什么作用呢?实际上,原型是 ECMAScript 实现继承的过程中产生的一个概念。这里我们简单拿 ES5 的对象来举例子: function Person(name) { this.name = name;}Person.prototype.say = function() { console.log(`My name is ${this.name}`);}let person = new Person('小明');person.say(); // My name is 小明让我们来逐步解释下这个例子: 声明了一个构造函数 Person,其有一个属性 name在其原型上声明了一个函数 say实例一个 Person 类——person调用 person 的 say 方法我们发现,person可以调用其构造函数原型里的say方法,why?让我们看下 person 里都有什么: 实例 person 虽然自身没有 say 方法,但是通过 proto 属性访问到了其原型中的 say 方法。 ...

October 9, 2019 · 2 min · jiezi

JS原型解析

概述JS中原型是为了实现代码重用的一种仿类机制,不过它跟类又完全不同。它通过给对象添加原型关系(即给某个对象添加__proto__属性)实现一个关联。把共有的方法和属性放到这个关联上即实现了JS的继承。简单来说就是一种委托机制。 JS里面的原型关系真的很复杂也很绕,如果只是很枯燥的讲述各种关系,真的很难记住,在这里希望通过代码和思维导图组合的形式,对JS的原型进行一次深度的剖析和理解。 代码分析首先,我们给出下面的一组关系代码 function Test() {}var t1 = new Test()var t2 = {}var t3 = new Number(1)t1.__proto__ === Test.prototypeTest.__proto__ === Function.prototypeTest.prototype.__proto__ === Object.prototypet2.__proto__ === Object.prototypeNumber.__proto__ === Function. prototypeMath.__proto__ === Object.prototype t3.__proto__ === Number.prototypeNumber.prototype.__proto__=== Object.prototype导图理解下图是根据上述关系绘制的思维导图,方便大家理解。 总结引擎会创建两个对象Object.prototype和Function.prototype用__proto__将Function.prototype和Object.prototype关联非函数对象可以通过__proto__找到Object.prototype函数对象通过__proto__找到Function.prototype函数的 prototype 是一个对象,该对象的__proto__指向Object.prototype

July 9, 2019 · 1 min · jiezi

JavaScript进阶-1-原型和原型链的概念

JavaScript进阶 - 1. 原型和原型链的概念我们好多经常会被问道JavaScript原型和原型链的概念,还有关于继承,new操作符相关的概念。本文就专门整理了原型和原型链的概念,关于对象继承我们后边进行介绍。本文包含对应的示例代码和脑图。如有奇异,欢迎指正! 目录讲解1. 构造函数创建对象2. 相关的名词介绍 2.1 prototype2.2 proto2.3 constructor3. 实例和原型 原型和原型4. 原型链是怎么产生的(附有相关的关系图说明)1. 构造函数创建对象我们先使用构造函数的方式声明一个对象: function Person() {}let person = new Person()person.name = '小红'console.log(person.name) // 小红在上面的代码中。Person是构造函数, person是通过new方式创建的实例对象。现在开始进入一个环节 2. 相关的名词介绍prototype,constructor,__proto__是我们经常见到的几个概念,但是他们之间的关系具体是什么样的呢,让我们逐步开始了解。 2.1 prototype每个函数都有 prototype 属性,除了 Function.prototype.bind(),该属性指向原型, prototype: 指向实例对象的原型对象 function Person() {}Person.prototype.name = '小红'let person = new Person()console.log(person.name) // 小红Person这个函数有声明prototype属性,那么这个值指向的到底是哪儿,是原型对象吗? 其实prototype指向的是,调用当前构造函数创建实例对象的原型,也就是person的原型。 那么原型到底是什么呢?其实原型可以理解为,一个JavaScript对象(null除外)在创建的时候会关联另外一个对象,这个被关联的对象就是我们说的原型对象,实例对象会在创建的时候,从原型对象继承一些属性或者方法。 对应的关系图如下: 2.2 protoprototype讲解了构造函数和原型对象之间的关系,那么实例对象和原型对象之间的关系又是怎么样的呢?下面讲解。每个JavaScript对象(null除外),都会有个__proto__的属性,这个属性指向的就是原型对象 function Person() {}let person = new Person()console.log(person.__proto__ === Person.prototype) // true// ES5 通过实例对象获取原型对象的方法console.log(Object.getPrototypeOf(person) === Person.prototype) // true由此我们上面的管理系图谱可以补充为: ...

July 7, 2019 · 1 min · jiezi

JavaScript-之-原型和原型链

几个概念构造函数: 构造函数实际上是一个普通函数,通过new操作符,可以利用构造函数快速创建对象;prototype:每个构造函数自身上都会有一个prototype属性,该属性值为一个对象,这个对象具有一个constructor属性,constructor指向构造函数自身;实例: 通过构造函数创建的对象,可以看做是该构造函数的实例;__proto__:通过构造函数创建的每个对象上面,都会有一个__proto__属性,该属性指向构造函数的prototype;什么是原型在JavaScript中,每个对象会有一个原型对象。对象会从原型对象继承一些属性和方法。 什么是原型链在JavaScript中,访问一个对象的属性时,JS引擎会首先在该对象自身上线查找该属性。如果找到了,则直接返回该属性的值;如果没有找到,则会去改对象的原型上面继续查找。如果对象的原型上面也没有这个属性,则继续在原型对象的上原型上面查找,如此一级级继续往上,直到原型为null,此时JS引擎返回该对象的属性值为undefined。 继承 /** * two methods to implement inheritance; */ function Base(type){ this.type = type; } Base.prototype.base=function(){ console.log(`${this.type} is in base func`); } // method one function Sub(type){ this.type = type; } Sub.prototype = Object.create(new Base('base')); Sub.prototype.sub=function(){ console.log(`${this.type} is in sub func`); } // method two function Foo(type){ this.type = type; } Object.setPrototypeOf( Foo.prototype, new Sub('sub')); Foo.prototype.foo=function(){ console.log(`${this.type} is in foo func`); } let sub = new Sub('sub1'); sub.base(); sub.sub(); sub instanceof Sub; // true sub instanceof Base; // true let foo = new Foo('foo1'); foo.base(); foo.sub(); foo.foo(); foo instanceof Foo; // true foo instanceof Sub; // true foo instanceof Base; // true一些实例Object.getPrototype预测下面几个表达式的结果 ...

July 1, 2019 · 1 min · jiezi

JS中的prototypeproto与constructor

1. 寻找原型心法口诀:每个对象的原型(__proto__)都指向自身的构造函数(constructor)的prototype属性let b={}b.constructor === Object// trueb.__proto__ === Object.prototype// trueb.__proto__ === b.constructor.prototype// true所以想要知道某个对象的原型是什么,首先找到他的构造函数是什么9个终极类Array.constructor// ƒ Function() { [native code] }Boolean.constructor// ƒ Function() { [native code] }Date.constructor// ƒ Function() { [native code] }Number.constructor// ƒ Function() { [native code] }String.constructor// ƒ Function() { [native code] }Object.constructor// ƒ Function() { [native code] }RegExp.constructor// ƒ Function() { [native code] }Symbol.constructor// ƒ Function() { [native code] }1个究极类Function.constructor// ƒ Function() { [native code] }3中特殊数字对象Math.constructor// ƒ Object() { [native code] }// Math 对象并不像 Date 和 String 那样是对象的类,因此没有构造函数 Math()NaN.constructor// ƒ Number() { [native code] }Infinity.constructor// ƒ Number() { [native code] }2中bug类型undefined.constructor// VM25366:1 Uncaught TypeError: Cannot read property 'constructor' of undefined at <anonymous>:1:11null.constructor// VM25366:1 Uncaught TypeError: Cannot read property 'constructor' of null at <anonymous>:1:11... ...

June 26, 2019 · 1 min · jiezi

图解javascript原型原型链

我们在学习javascript时,经常会听到“万物皆对象”,但是呢,其实万物皆对象的对象也有区别。分为普通对象和函数对象。1.对象分为函数对象和普通对象    通过new Function()创建的对象都是函数对象,其他的都是普通对象。 2.构造函数而提到new关键字,我们不得不提到构造函数。构造函数又分为自定义构造函数及native构造函数(即构造器)2.1 自定义构造函数 function Person(name,age,job){        this.name = name;        this.age = age;        this.job = job;    }    var miya = new Person('miya',18,'engineer'); //miya是构造函数Person的实例;而实例的构造函数属性(constructor) 都指向构造函数    即:miya.constuctor = Person     2.2 构造器     js内置的构造器包括Number,Boolean,String,Object,Function,Array,RegExp,Error,Date等同样的,我们的构造器的实例也有一个constuctor指向它的构造器    let miya= new Array()    miya.constuctor ==Array;通过以上的分析,我们可以得到以下这张图    这里扩展一下,其实我们在做类型判断的时候习惯用typeof,typeof判断简单数据类型的时候其实是ok的。但是typeof有判断复杂数据类型不是很清晰,得到的基本是String或者Function比如一下图示判断但是,通过上述我们知道构造器的实例都有一个constructor的属性指向构造器。那其实我们直接用constructor便可以清晰地知道这个实例从哪里来。3.原型对象    每个函数对象都有一个prototype属性,这个属性指向函数的原型对象即prototype。    所有的prototype会有一个默认的constuctor属性,指向函数对象本身。     function Person(name,age,job){this.name = name; this.age = age; this.job = job;}Person.prototype.sayName = function () { console.log(this.name);};let miya = new Person('miya',18,'engineer');let tiffany = new Person('tiffany',18,'engineer');miya.sayName(); //miyatiffany.sayName(); //tiffanyconsole.log(miya.sayName == tiffany.sayName); //trueconsole.log(Person.prototype.constructor == Person); //true通过:Person.prototype.constuctor = Person ,因为构造函数的实例是有一个constructor的属性指向构造函数的,我们可以得到结论其实Person.prototype也是Person的实例    即:原型对象是构造函数的一个实例【原型对象(Function.prototype除外)是一个普通对象,而不是函数对象】 ...

June 15, 2019 · 1 min · jiezi

JavaScript系列浅析原型链与继承

一、前言继承是面向对象(OOP)语言中的一个最为人津津乐道的概念。许多面对对象(OOP)语言都支持两种继承方式::接口继承 和 实现继承 。 接口继承只继承方法签名,而实现继承则继承实际的方法。由于js中方法没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其 实现继承 主要是依靠原型链来实现的。 二、概念2.1简单回顾下构造函数,原型和实例的关系:每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针。 关系如下图所示: 2.2什么是原型链每一个对象拥有一个原型对象,通过__proto__指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层层的,最终指向null。这种关系成为原型链(prototype chain)。 假如有这样一个要求:instance实例通过原型链找到了Father原型中的getFatherValue方法. function Father(){ this.property = true;}Father.prototype.getFatherValue = function(){ return this.property;}function Son(){ this.sonProperty = false;}//继承 FatherSon.prototype = new Father();//Son.prototype被重写,导致Son.prototype.constructor也一同被重写Son.prototype.getSonVaule = function(){ return this.sonProperty;}var instance = new Son();console.log(instance.getFatherValue());//true注意: 此时instance.constructor指向的是Father,这是因为Son.prototype中的constructor被重写的缘故. 2.3确定原型和实例之间的关系使用原型链后, 我们怎么去判断原型和实例的这种继承关系呢? 方法一般有两种. 1、第一种是使用 instanceof 操作符。只要用这个操作符来测试实例(instance)与原型链中出现过的构造函数,结果就会返回true. 以下几行代码就说明了这点。 console.log(instance instanceof Object);//trueconsole.log(instance instanceof Father);//trueconsole.log(instance instanceof Son);//true由于原型链的关系, 我们可以说instance 是 Object, Father 或 Son中任何一个类型的实例. 因此, 这三个构造函数的结果都返回了true. 2、第二种是使用 isPrototypeOf() 方法,。同样只要是原型链中出现过的原型,isPrototypeOf() 方法就会返回true console.log(Object.prototype.isPrototypeOf(instance));//trueconsole.log(Father.prototype.isPrototypeOf(instance));//trueconsole.log(Son.prototype.isPrototypeOf(instance));//true 2.4原型链的问题原型链并非十分完美, 它包含如下两个问题: ...

June 14, 2019 · 2 min · jiezi

JavaScript 中的 new 到底干了什么,跟原型链又有一些什么联系?

原文:https://legacy.ofcrab.com/press/javascript-new.html如果按面向对象的思路去讲 JavaScript 的 new,还是很难去理解,我们可以从另一个方向去理解一下它。你这些人类我是一名程序员,也是一个人,我可能:有一个响亮亮的名称在某一天出生是个男人我能行走我还能跑步还能跳跃能说话我还能写代码那么,在 JavaScript 中,我们可能像下面这样表达我:const me = { name: ‘大胡子农同工潘半仙’, birth: ‘1988-08-08’, sex: ‘male’, walk: function (speed, direction, duration) { // 以 speed 的速度向 direction 方向行走 duration 长的时间 }, run: function (speed, direction, duration) { // 像跑步一样,速度 }, jump: function (high, direction, angle) { // 以 angle 角度向 direction 方向跳 high 高 }, speak: function (letters) { // 说出 letters 这些词 }, coding: function (language, line) { // 写程序呢 }}你们这些人类当然,这个世界上不可能只有我一个程序员,更不可能只有我一个人,就像我们这个小公司,就有七八百人,似乎所有这些人的数据都保存在数据库里面:namesexbirth潘韬male1988-08-08高超male1985-08-09春雨male1999-08-08我们从数据库中查询出上面这几条记录,在 JavaScript 可能表示为一个二维数据,然后要创建出这三个人来,可能是下面这样的:const people = DB.query()// people = [[‘潘韬’, ‘male’, ‘1988-08-08’], […], […]]for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name, sex, birth, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {} }}重复的资源占用上面大家已经发现,像上面这样去创建三个对象, walk、run、jump、speak、coding 这五件能做的事情(方法),其实做法都一样,但是我们却重复的去描述该怎么做了,其实就占用了很多资源,所以,我们可能会像下面这样改进一下:const walk = function walk () {}const run = function run () {}const jump = function jump () {}const speak = function speak () {}const coding = function coding () {}for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name, sex, birth, walk, run, jump, speak, coding }}不同的人共用相同的资源(方法)但是这个世界不止有人类对,人类相比于这个世界上的其它生物来讲,数量根本就值得一提,如果像上面这样,可能各种不同物种能做的事情都会要定义出不同的函数,蠕动肯定不是人类会去做的事情,但很多别的生物会做,那么为了代码管理方便,我们把人能做的所有事情都放在一个对象里面,这样就相当于有了一个命名空间了,不会再跟别的物种相冲突:const whatPeopleCanDo = { walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name, sex, birth, …whatPeopleCanDo }}原型但是,有的人可能我们并不知道他的 sex 信息是多少,有的也有可能不知道 birth 是多少,但是我们希望在创建这个人的时候,能给不知道的数据一些初始数据,所以, whatPeopleCanDo 并不能完全的表达出一个人,我们再改进:const peopleLike = { name: ‘’, sex: ‘unknown’, birth: ‘’, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { …peopleLike, name: name || peopleLike.name, sex: sex || peopleLike.sex, birth: birth || peopleLike.birth }}这样一来,我们就可以为不知道的属性加一些默认值,我们称 peopleLike 这个东东就为原型,它表示了像人类这样的物种有哪些属性,能干什么事情。原型链虽然上面已经比最开始的版本好得多了,但是还是能有很大的改进空间,我们现在像下面这样改一下:const peoplePrototype = { name: ‘’, sex: ‘unknown’, birth: ‘’, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name: name || peoplePrototype.name, sex: sex || peoplePrototype.sex, birth: birth || peoplePrototype.birth, proto: peoplePrototype }}我们不再把人类原型里面的所有方法都绑定到某个人身上,而是像上面这样,用一个特殊的字段 proto 来指定:我的原型是 peoplePrototype 这个对象,同时,我们还制定了一个规则:如果你想请求我的某个方法,在我自己身上没有,那就去我的原型上面找吧,如果我的原型上面没有,那就去我的原型的原型上面去找,直到某个位置,没有更上层的原型为止像上面这样创建的 people 对象,有自己的属性,但是当我们去访问 people.speak() 方法的时候,其实访问的是 people.proto.speak(),这是我们的规则。更优雅的创建新新人类我们总不能在需要创建新人的时候,都像上面这样,自己去写一个对象,然后再手工指定它的原型是什么,所以,我们可以创建一个函数,专门用来生成人类的:const peoplePrototype = { name: ‘’, sex: ‘unknown’, birth: ‘’, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}const makePeople = function makePeople(name, sex, birth) { let people = {} people.name = name || peoplePrototype.name people.sex = sex || peoplePrototype.sex people.birth = birth || peoplePrototype.birth people.proto = peoplePrototype return people}people = people.map(makePeople)现在这样我们只需要引入 makePeople 这个函数就可以随时随地创建新人了。更优雅一点的改进显然,上面这样并不是最好的办法,定义了一个原型,又定义了一个原型对象,我们可以把这两个合并到一起,所以,就可以有下面这样的实现了:const People = function People (name, sex, birth) { let people = {} people.name = name || People.prototype.name people.sex = sex || People.prototype.sex people.birth = birth || People.prototype.birth people.proto = People.prototype return people}People.prototype = { name: ‘’, sex: ‘unknown’, birth: ‘’, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}我们直接把创建人类的那个函数叫作 People,这个函数有一个属性叫 prototype,它表示用我这个函数创建的对象的原型是什么,这个函数做的事情还是以前那些事儿,创建临时对象,设置对象的属性,绑定一下原型,然后返回。神奇的 this我们除了人,还有别的动物,比如 Tiger、Fish等,按上面的方式,在 Tiger() 或者 Fish() 函数里面都会建立不同的 tiger 或者 fish 名称的临时对象,这样太麻烦,我们把这种函数创建出来的对象,都可以统一叫作“这个对象” ,也就是 this object,不在关心是人是鬼,统一把所有的临时对象都叫 thisObject 或者更简单的就叫作:这个,即 this。const People = function People (name, sex, birth) { let this = {} this.name = name || People.prototype.name this.sex = sex || People.prototype.sex this.birth = birth || People.prototype.birth this.proto = People.prototype return this}当然,上面的这一段代码是有问题的,只是假想一样,这样是不是可行。new到现在为止,我们发现了整个代码的演变,是时候引出这个 new 了,它来干什么呢?它后面接一个类似上面这种 People 的函数,表示我需要创建一个 People 的实例,它的发明就是为了解决上面这些所有重复的事情,有了 new 之后,我们不需要再每一次定义一个临时对象,在 new 的上下文关系中,会在 People 函数体内自动为创建一个临时变量 this,这个就表示即将被创建出来的对象。同时,对于使用 new 创建的实例,会自动的绑定到创建函数的 prototype 作为原型,还会自动为 People 创建一个 constructor 函数,表示这个原型的创建函数是什么,所以,我们可以改成下面这样的了:const People = function People (name, sex, birth) { this.name = name || People.prototype.name this.sex = sex || People.prototype.sex this.birth = birth || People.prototype.birth}People.prototype.name = ‘‘People.prototype.sex = ‘unknown’People.prototype.birth = ‘‘People.prototype.walk = function () {}People.prototype.run = function () {}People.prototype.jump = function () {}People.prototype.speak = function () {}People.prototype.coding = function () {}people = people.map(p => new People(…p))总结new 到底干了什么?当 new People() 的时候创建临时变量 this,并将 this 绑定到 People 函数体里执行 People.prototype.constructor = People执行 this.proto = People.prototype执行 People 函数体中的自定义返回新创建的对象 ...

April 18, 2019 · 3 min · jiezi

JavaScript

前言作为前端高频面试题之一,相信很多小伙伴都有遇到过这个问题。那么你是否清楚完整的了解它呢? 国际惯例,让我们先抛出问题:什么是原型、原型链它们有什么特点它们能做什么怎么确定它们的关系或许你已经有答案,或许你开始有点疑惑,无论是 get 新技能或是简单的温习一次,让我们一起去探究一番吧如果文章中有出现纰漏、错误之处,还请看到的小伙伴多多指教,先行谢过以下↓原型JavaScript是基于原型的我们创建的每个函数都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。简单来说,就是当我们创建一个函数的时候,系统就会自动分配一个 prototype属性,可以用来存储可以让所有实例共享的属性和方法用一张图来表示就更加清晰了:图解:每一个构造函数都拥有一个 prototype 属性,这个属性指向一个对象,也就是原型对象原型对象默认拥有一个 constructor 属性,指向指向它的那个构造函数每个对象都拥有一个隐藏的属性 prototype,指向它的原型对象function Person(){}var person = new Person();person.proto === Person.prototype // truePerson.prototype.constructor === Person // true那么,原型对象都有哪些特点呢原型特点function Person(){}Person.prototype.name = ’tt’;Person.prototype.age = 18;Person.prototype.sayHi = function() { alert(‘Hi’);}var person = new Person();var person1 = new Person();person.name = ‘oo’;person.name // ooperson.age // 18perosn.sayHi() // Hiperson1.age // 18person1.sayHi() // Hi从这段代码我们不难看出:实例可以共享原型上面的属性和方法实例自身的属性会屏蔽原型上面的同名属性,实例上面没有的属性会去原型上面找既然原型也是对象,那我们可不可以重写这个对象呢?答案是肯定的function Person() {}Person.prototype = { name: ’tt’, age: 18, sayHi() { console.log(‘Hi’); }}var person = new Person()只是当我们在重写原型链的时候需要注意以下的问题:function Person(){}var person = new Person();Person.prototype = { name: ’tt’, age: 18}Person.prototype.constructor == Person // falseperson.name // undefined一图胜过千言万语在已经创建了实例的情况下重写原型,会切断现有实例与新原型之间的联系重写原型对象,会导致原型对象的 constructor 属性指向 Object ,导致原型链关系混乱,所以我们应该在重写原型对象的时候指定 constructor( instanceof 仍然会返回正确的值)Person.prototype = { constructor: Person}注意:以这种方式重设 constructor 属性会导致它的 Enumerable 特性被设置成 true(默认为false)既然现在我们知道了什么是 prototype(原型)以及它的特点,那么原型链又是什么呢?原型链JavaScript 中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链同样的,我们使用一张图来描述所有原型链的终点都是 Object 函数的 prototype 属性Objec.prototype 指向的原型对象同样拥有原型,不过它的原型是 null ,而 null 则没有原型清楚了原型链的概念,我们就能更清楚地知道属性的查找规则,比如前面的 person 实例属性.如果自身和原型链上都不存在这个属性,那么属性最终的值就是 undefined ,如果是方法就会抛出错误class类ES6 提供了 Class(类) 这个概念,作为对象的模板,通过 class 关键字,可以定义类为什么会提到 class :ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return ‘(’ + this.x + ‘, ’ + this.y + ‘)’; }}// 可以这么改写function Point(x, y) { this.x = x; this.y = y;}Point.prototype.toString = function () { return ‘(’ + this.x + ‘, ’ + this.y + ‘)’;};class 里面定义的方法,其实都是定义在构造函数的原型上面实现实例共享,属性定义在构造函数中,所以 ES6 中的类完全可以看作构造函数的另一种写法除去 class 类中的一些行为可能与 ES5 存在一些不同,本质上都是通过原型、原型链去定义方法、实现共享。所以,还是文章开始那句话 JavaScript是基于原型的更多 class 问题,参考这里关系判断instanceof最常用的确定原型指向关系的关键字,检测的是原型,但是只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型function Person(){}var person = new Person();person instanceof Person // trueperson instanceof Object // truehasOwnProperty通过使用 hasOwnProperty 可以确定访问的属性是来自于实例还是原型对象function Person() {}Person.prototype = { name: ’tt’}var person = new Person();person.age = 15;person.hasOwnProperty(‘age’) // trueperson.hasOwnProperty(’name’) // false原型链的问题由于原型链的存在,我们可以让很多实例去共享原型上面的方法和属性,方便了我们的很多操作。但是原型链并非是十分完美的function Person(){}Person.prototype.arr = [1, 2, 3, 4];var person1 = new Person();var person2 = new Person();person1.arr.push(5) person2.arr // [1, 2, 3, 4, 5]引用类型,变量保存的就是一个内存中的一个指针。所以,当原型上面的属性是一个引用类型的值时,我们通过其中某一个实例对原型属性的更改,结果会反映在所有实例上面,这也是原型 共享 属性造成的最大问题另一个问题就是我们在创建子类型(比如上面的 person)时,没有办法向超类型( Person )的构造函数中传递参数后记鉴于原型的特点和存在的问题,所以我们在实际开发中一般不会单独使用原型链。一般会使用构造函数和原型相配合的模式,当然这也就牵扯出了 JavaScript 中另一个有意思的领域:继承那么,什么又是继承呢且听下回分解最后,推荐一波前端学习历程,不定期分享一些前端问题和有意思的东西欢迎 star 关注 传送门参考文档JavaScript高级程序设计ECMAScript6入门 ...

April 17, 2019 · 2 min · jiezi

关于构造函数、原型、原型链、多种方式继承

构造函数与实例对象又是这个经典的问题,嗯,我先来写个构造函数,然后实例化一个对象看看。function Person(name) { this.name = name}Person.prototype.eat = () => {console.log(’eat’)}Person.prototype.play = () => {console.log(‘play’)}let Han = new Person(‘Han’)通过一系列打印发现了这样的关系:原型链 – 原型(prototype)和隐式原型(proto)可以看出实例对象没有prototype(也就是原型),只有构造器才拥有原型。而所有的js对象都拥有__proto__(也就是隐式原型),这个隐式原型所指向的就是创造这个对象的构造器的原型。如实例Han的隐式原型指向了其构造函数(Person)的原型;Person的隐式原型指向了Function的原型;而原型自身也有隐式原型,指向了Object的原型。有点绕口,其实就是通过隐式原型可以向上找到是谁构造了自己,并且如果自己没有相应的属性或者方法,可以沿着这条原型链向上找到最近的一个属性或方法来调用。如Han.eat(),实际上是调用了Han.proto.eat(),把构造器Person的原型的eat方法给拿来用了;再如Han.hasOwnProperty(’name’),实际上是调用了Han.proto.proto.hasOwnProperty(’name’),因为Han自己没hasOwnProperty这方法,就通过隐式原型向上找到了Person的原型,发现也没这方法,就只能再沿着Person的原型的隐式原型向上找到了Object的原型,嗯然后发现有这方法就拿来调用了。构造器constructor所有构造函数都有自己的原型(prototype),而原型一定有constructor这么个属性,指向构造函数本身。也就是告诉大家这个原型是属于本构造函数的。Function & Object可以看出Person这个构造函数是由Function创建出来的,而我们看下Function的隐式原型,竟然是指向了Function的原型,也就是Function也是由Function创建出来的。很绕是不是,我们先不管,继续溯源下去,再看下Function的原型的隐式原型,指向的是Object的原型,继续往上找Object的原型的隐式原型,嗯终于结束了找到的是null,也就是Object的原型是原型链上的最后一个元素了。接下来看下Object,Object是由Function创建出来的,而Function的隐式原型的隐式原型是Object的原型也就是Function通过原型链可以向上找到Object的原型,两者看起来是你生我我生你的关系,这里也就引用比较好懂的文章来解释下: 从Object和Function说说JS的原型链ObjectJavaScript中的所有对象都来自Object;所有对象从Object.prototype继承方法和属性,尽管它们可能被覆盖。例如,其他构造函数的原型将覆盖constructor属性并提供自己的toString()方法。Object原型对象的更改将传播到所有对象,除非受到这些更改的属性和方法将沿原型链进一步覆盖。FunctionFunction 构造函数 创建一个新的Function对象。 在 JavaScript 中, 每个函数实际上都是一个Function对象。—- 来自mozilla接下来说下构造函数实例化对象到底做了些啥,其实看也能看出来了。let Jan = {}Person.call(Jan, ‘Jan’)Jan.proto = Person.prototype1、创建一个空对象。2、将构造函数的执行对象this赋给这个空对象并且执行。3、把对象的隐式原型指向构造函数的原型。4、返回这个对象是的就是这样,next page!继承原型链继承function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss() {}Boss.prototype = new Person()let Han = new Boss()原理就是这样????子构造函数的原型指向了父构造函数的实例对象,因此子构造函数的实例对象可以通过原型链找到父构造函数的原型方法和类属性。优点:所有实例对象都可以共享父构造函数的原型方法。缺点:1、父构造函数的引用属性也被共享了,相当于所有的实例对象只要对自身的skills属性进行修改都会引发共振,因为其实修改的是原型链上的skills属性。当然对skills重新赋值可以摆脱这一枷锁,相当于自身新建了skills属性来覆盖了原型链上的。2、实例化时无法对父构造函数传参。3、子构造函数原型中的constructor不再是子类自身,而是通过原型链找到了父类的constructor。构造函数继承function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss(name) { Person.call(this, name)}let Han = new Boss(‘Han’)原理就是父构造函数把执行对象赋给子构造函数的实例对象后执行自身。优点:1、实例化时可以对父构造函数传参。2、父类的引用属性不会被共享。3、子构造函数原型中的constructor还是自身,原型没有被修改。缺点:每次实例化都执行了一次父构造函数,子类不能继承父类的原型,如果把父类原型上的方法写在父类的构造函数里,虽然子类实例对象可以调用父类的方法,但父类的方法是单独加在每个实例对象上,会造成性能的浪费。组合继承结合了原型链继承和构造函数继承两种方法。function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss(name, age) { Person.call(this, name) this.age = age}Boss.prototype = new Person()Boss.prototype.constructor = BossBoss.prototype.sleep = ()=> {console.log(‘sleep’)}let Han = new Boss(‘Han’, 18)看起来是完美解决了一切。但就是????实例化的对象实际上是用构造函数继承的方法往自己身上加属性从而覆盖原型链上的相应属性的,既然如此,为什么不直接那父构造器的原型加到子构造器的原型上呢?这样就不会出现那多余的父类实例化对象出来的属性了。function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss(name, age) { Person.call(this, name) this.age = age}Boss.prototype = Person.prototype //modifiedBoss.prototype.constructor = BossBoss.prototype.sleep = ()=> {console.log(‘sleep’)}let Han = new Boss(‘Han’, 18)看起来很是完美,反正效果是达到了,性能优化也是最佳。但问题就是这样一点继承关系都看不出来啊,父类和子类的原型完全融合在一块了,一点都不严谨。所以最优的继承方式应该是。。。寄生组合继承function Person(name) { this.name = name this.skills = [’eat’, ‘sleep’]}Person.prototype.say = ()=> {console.log(‘hi’)}function Boss(name, age) { Person.call(this, name) this.age = age}Boss.prototype = Object.create(Person.prototype)Boss.prototype.sleep = ()=> {console.log(‘sleep’)}Boss.prototype.constructor = Bosslet Han = new Boss(‘Han’, 18)先看图????其实跟组合继承有点像,构造函数继承部分和组合继承的一样就不说了。原型链那块和原型链继承有所不同的是原型链继承是直接拿了父类的实例对象来作为子类的原型,而这里是用以父类的原型为原型的构造函数实例化出来的对象作为子类的原型(Object.create做的事情),完美避开了不必要的父类构造函数里的东西。Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。相当于这样????function create(proto) { function F() {} F.prototype = proto return new F()}听说ES6的class extend也是这么做的,更多的继承细节可以看看这篇文章,本继承章节也参考了的????一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends ...

March 16, 2019 · 1 min · jiezi

深入学习js之——原型和原型链

开篇:在Brendan Eich大神为JavaScript设计面向对象系统的时候,借鉴了Self 和Smalltalk这两门基于原型的语言,之所以选择基于原型的面向对象系统,并不是因为时间匆忙,它设计起来相对简单,而是因为从一开始Brendan Eich就没打算在Javascipt中加入类的概念。以类为中心的面向对象的编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建而来,而在原型编程的思想中,类并不是必须的,对象未必需要从一个类中创建而来。JavaScript是一门完全面向对象的语言,如果想要更好地使用JavaScript的面向对象系统,原型和原型链就是个绕不开的话题,今天我们就一起来学习一下这方面的知识。理解三个重要的属性:prototype、proto、constructor见名知意,所谓的"链"描述的其实是一种关系,加上原型两个字,可以理解为原型之间的关系,既然是一种关系,就需要维系,就好比我们走亲访友,亲情就是一种纽带,类比在JavaScript当中——函数、对象实例、实例原型也有自身的联系,而他们之间的纽带就是下面这三个重要的属性:三个重要的属性:prototype、proto、constructorprototype我们先来看看第一个属性:prototype所谓属性,指的是一个事物的特征,就比如美女的一大特征是“大长腿”,那“大长腿"就是美女的属性,类比到JavaScript中函数,每一个函数都有一个prototype属性,这属性就是与生俱来的特质。这里需要特别强调一下,是函数,普通的对象是没有这个属性的,(这里为什么说普通对象呢,因为在JavaScript里面,一切皆为对象,所以这里的普通对象不包括函数对象)我们来看一个例子:function Person() {}// 虽然写在注释里面,但是需要注意的是// prototype 是函数才会有的属性 (哈哈哈,看来在JavaScript中函数果然是有特权的……)Person.prototype.name = “Kevin”;var person1 = new Person();var person2 = new Person();console.log(person1.name) // Kevinconsole.log(person2.name) // Kevin上面的代码中我们创建了一个构造函数Person,并且在实例原型上面添加了一个name属性赋值为"Kevin";然后分别创建了两个实例对象:person1、person2;当我们打印两个实例对象上name属性时均输出了Kevin(可以亲自试一下)。我们不禁疑惑,这个Person.prototype到底是什么,为什么在上面添加属性,在构造函数的实例化对象上都能访问到呢?其实 Person这个函数的prototype属性指向了一个对象,即:Person.prototype也是一个对象。(真是好多对象)这个对象正是调用该构造函数而创建的实例的原型。也就是这个例子中的person1和person2的原型。为了便于理解,我们将上面的这段话拆解一下:1.调用的构造函数: Person2.使用什么调用: new关键字3.得到了什么: 两个实例化对象person1、person24.实例化对象和原型是什么关系: person1和person2的原型就是 Person.prototype那什么是原型呢?可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另外一个对象,这个对象就是我们所说的原型,而每一个对象都会从原型"继承"属性。上面的代码中我们并没有直接在person1和person2中添加name属性 但是这两个对象却能够访问name属性,就是这个道理。我们用一张图表示构造函数和实例原型之间的关系:好了 构造函数和实例原型之间的关系我们已经梳理清楚了,那我们怎么表示实例与实例原型,也就是person1或者person2和Person.prototype 之间的关系呢。这时候需要请出我们理解原型链的第二个重要属性__proto____proto__这个属性有什么特征呢?其实这是每一个JavaScript对象(除了null)都具有的一个属性,叫__proto__,这个属性会指向该对象的原型,即作为实例对象和实例原型的之间的链接桥梁,这里强调,是对象,同样,因为函数也是对象,所以函数也有这个属性。我们看一个代码示例:function Person() {}var person = new Person();console.log(person.proto === Person.prototype); //true;有了第二个属性的帮助,我们就能更加全面的理解这张关系图了:通过上面的关系图我们可以看到,构造函数Person 和实例对象person 分别通过prototype和__proto__ 和实例原型Person.prototype进行关联,根据箭头指向我们不禁要有疑问:实例原型是否有属性指向构造函数或者实例呢?这时候该请出我们的第三个属性了:constructorconstructor实例原型指向实例的属性倒是没有,因为一个构造函数可能会生成很多个实例,但是原型指向构造函数的属性倒是有的,这就是我们的constructor——每一个原型都有一个constructor属性指向关联的构造函数。我们再来看一个示例:function Person() {}console.log(Person === Person.prototype.constructor); // true好了到这里我们再完善下关系图:通过对三个属性的介绍,我们总结一下:function Person() {}var person = new Person();console.log(person.proto == Person.prototype) // trueconsole.log(Person.prototype.constructor == Person) // true// 顺便学习一个ES5的方法,可以获得对象的原型console.log(Object.getPrototypeOf(person) === Person.prototype) // true上述代码中我们我们执行了以下操作:1.声明了构造函数 Person;2.使用new操作符调用 Person 实例化了一个person 对象;3.判断实例化对象通过__proto__是否指向实例原型;4.判断实例原型通过constructor是否能找到对应的构造函数;5.使用Object.getPrototypeOf方法传入一个对象 找到对应的原型对象;了解了构造函数。实例原型、和实例对象之间的关系,接下来我们讲讲实例和原型的关系:实例与原型当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。我们再举一个例子: function Person() { } Person.prototype.name = ‘Kevin’; var person = new Person(); person.name = ‘Daisy’; console.log(person.name) // Daisy delete person.name; console.log(person.name) // Kevin在上面这个例子中,我们给实例person添加了name 属性,当我们打印person.name的时候,结果自然为Daisy但是当我们删除了person下面的name属性后,读取person.name,依然能够成功输出Kevin,实际情况是从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。但是我们不禁有疑问,如果万一没有找到该怎么办?我们来看下一层的关系 原型的原型原型的原型我们前面提到过,原型也是一个对象,那么既然是对象,那肯定就有创建它的构造函数,这个构造函数就是Object(); var obj = new Object(); obj.name = ‘Kevin’; console.log(obj.name); // Kevin;其实原型对象就是通过Object构造函数生成的,结合之前我们所说的,实例__proto__指向构造函数的prototype 所以我们再丰富一下我们的关系图;到了这里我们对于 构造函数、实例对象、实例原型之间的关系又有了进一步的认识。说了这么多,终于可以介绍原型链了。原型链那Object.prototype 的原型呢?Object是根节点的对象,再往上查找就是null,我们可以打印: console.log(Object.prototype.proto === null) // true然而 null 究竟代表了什么呢?引用阮一峰老师的 《undefined与null的区别》 就是:null 表示“没有对象”,即该处不应该有值。所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。所以查找属性的时候查到 Object.prototype 就可以停止查找了。我们可以将null 也加入最后的关系图中,这样就比较完整了。上图中相互关联的原型组成的链状结构就是原型链,也就是红色的这条线补充最后,补充三点大家可能不会注意到的地方:constructor首先是constructor,我们看一个例子:function Person() {}var person = new Person();console.log(person.constructor === Person); // true当获取person.constructor时,其实 person 中并没有constructor 属性,当不能读取到constructor属性时,会从 person 的原型也就是 Person.prototype中读取,正好原型中有该属性,所以:person.constructor === Person.prototype.constructor__proto__其次是 proto ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。真的是继承吗?最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。参考:1、《Javascript设计模式与开发实践》2、JavaScript深入之从原型到原型链欢迎添加我的个人微信讨论技术和个体成长。欢迎关注我的个人微信公众号——指尖的宇宙,更多优质思考干货 ...

February 24, 2019 · 1 min · jiezi

Javascript 原型链

先来一张图看看几个名词的关系 构造函数、原型、实例原谅我的狂草字体,我手写比用电脑画快。今天我们只说原型链,所以接下来我就围绕着原型链的几个部分说起。这个大家都很熟悉了,首字母大写的函数我们都可以作为构造函数,不是说小写的就不能new,也是可以的,暂时说成约定俗成吧! // 构造函数 function Fn() {} //原型对象 console.log(Fn.prototype) //new let fn = new Fn() //实例 console.log(fn)代码部分结束了,今天我们就用这4行代码描述一下上图也就是原型链的来龙去脉。每个函数都有一个属性prototype,借用Function.prototype 属性存储了 Function 的原型对象。验证了我草图构造函数.prototype指向原型对象完整的log看一下实例.__proto__也指向原型对象从log里也能看出来实例原型的constructor指向构造函数最后再说构造函数 new关键字生成实例手绘图说完了,我们说正题 原型链,为什么再说原型链之前先画了一个草图,为了帮助预热理解。每一个原型对象都有一个__proto__属性,这个是我们在代码中继承的关键,也是众多面试官所问的什么是原型链上图可以看到,第一次__proto__找到了原型对象,第二次__proto__找到了Object实例对象,第三次null查找结束。我们平时开发中用到了__proto__去查找链条中我们继承的方法和属性都在prototype(原型)上,所以不能在Fn.prototype = xxx操作,这样链条就会中断,只能在原型上扩展属性。明天继续聊继承!欢迎吐槽!

February 18, 2019 · 1 min · jiezi

javascript 面向对象 new 关键字 原型链 构造函数

JavaScript面向对象JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构构造函数的首字母大写,区分一般函数。函数体内部使用了this关键字,代表了所要生成的对象实例。生成对象的时候,必须使用new命令。构造函数内部使用严格模式 ‘use strict’,防止当做一般函数调用,这样就会报错。function Person(name, age, sex) { ‘use strict’; this.name = name; this.age = age; this.sex = sex;}Person() 报错new Person(“zhangxc”, 29, “male”);1、new关键字 命令内部实现function _new(constructor, params) { // 接受个数不确定参数,第一个参数:构造函数;第二个到第n个参数:构造函数传递的参数。 // 1. 首先将参数组成一个数组 // 首先 .slice 这个方法在不接受任何参数的时候会返回 this 本身,这是一个 Array.prototype 下的方法,因此 this 就是指向调用 .slice 方法的数组本身。 var args = Array.prototype.slice.call(arguments); // arguments伪数组,获取函数的所有参数的伪数组。 // 等价于 // [].slice.call(arguments); // 2. 获取构造函数 var constructor = args.shift(); // shift()返回数组第一个元素 // 3. 使用构造函数原型创建一个对象。我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()方法。 var context = Object.create(constructor.prototype); // Object.create()参数是一个对象,新建的对象继承参数对象的所有属性 // 4. 将参数属性附加到对象上面 var result = constructor.apply(context, args); // 5. 返回一个对象 return (typeof result === ‘object’ && result != null) ? result : context;}function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex;}var args1 = _new(Person, “zhangxc”, 18, “male”);// {name: “zhangxc”, age: 18, sex: “male”}var args2 = new Person(“zhangxc”, 18, “male”);// {name: “zhangxc”, age: 18, sex: “male”}new.target属性如果当前函数是new命令调用,new.target指向当前函数(构造函数的名称),否则为undefined。function Test() { console.log(new.target === Test);}Test() // falsenew Test() // true2、this关键字…3、对象的继承… 待完善 ...

February 13, 2019 · 1 min · jiezi

深入学习js之——原型和原型链

开篇:在Brendan Eich大神为JavaScript设计面向对象系统的时候,借鉴了Self 和Smalltalk这两门基于原型的语言,之所以选择基于原型的面向对象系统,并不是因为时间匆忙,它设计起来相对简单,而是因为从一开始Brendan Eich就没打算在Javascipt中加入类的概念。以类为中心的面向对象的编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建而来,而在原型编程的思想中,类并不是必须的,对象未必需要从一个类中创建而来。JavaScript是一门完全面向对象的语言,如果想要更好地使用JavaScript的面向对象系统,原型和原型链就是个绕不开的话题,今天我们就一起来学习一下这方面的知识。理解三个重要的属性:prototype、proto、constructor见名知意,所谓的"链"描述的其实是一种关系,加上原型两个字,可以理解为原型之间的关系,既然是一种关系,就需要维系,就好比我们走亲访友,亲情就是一种纽带,类比在JavaScript当中——函数、对象实例、实例原型也有自身的联系,而他们之间的纽带就是下面这三个重要的属性:三个重要的属性:prototype、proto、constructorprototype我们先来看看第一个属性:prototype所谓属性,指的是一个事物的特征,就比如美女的一大特征是“大长腿”,那“大长腿"就是美女的属性,类比到JavaScript中函数,每一个函数都有一个prototype属性,这属性就是与生俱来的特质。这里需要特别强调一下,是函数,普通的对象是没有这个属性的,(这里为什么说普通对象呢,因为在JavaScript里面,一切皆为对象,所以这里的普通对象不包括函数对象)我们来看一个例子:function Person() {}// 虽然写在注释里面,但是需要注意的是// prototype 是函数才会有的属性 (哈哈哈,看来在JavaScript中函数果然是有特权的……)Person.prototype.name = “Kevin”;var person1 = new Person();var person2 = new Person();console.log(person1.name) // Kevinconsole.log(person2.name) // Kevin上面的代码中我们创建了一个构造函数Person,并且在实例原型上面添加了一个name属性赋值为"Kevin";然后分别创建了两个实例对象:person1、person2;当我们打印两个实例对象上name属性时均输出了Kevin(可以亲自试一下)。我们不禁疑惑,这个Person.prototype到底是什么,为什么在上面添加属性,在构造函数的实例化对象上都能访问到呢?其实 Person这个函数的prototype属性指向了一个对象,即:Person.prototype也是一个对象。(真是好多对象)这个对象正是调用该构造函数而创建的实例的原型。也就是这个例子中的person1和person2的原型。为了便于理解,我们将上面的这段话拆解一下:1.调用的构造函数: Person2.使用什么调用: new关键字3.得到了什么: 两个实例化对象person1、person24.实例化对象和原型是什么关系: person1和person2的原型就是 Person.prototype那什么是原型呢?可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另外一个对象,这个对象就是我们所说的原型,而每一个对象都会从原型"继承"属性。上面的代码中我们并没有直接在person1和person2中添加name属性 但是这两个对象却能够访问name属性,就是这个道理。我们用一张图表示构造函数和实例原型之间的关系:好了 构造函数和实例原型之间的关系我们已经梳理清楚了,那我们怎么表示实例与实例原型,也就是person1或者person2和Person.prototype 之间的关系呢。这时候需要请出我们理解原型链的第二个重要属性__proto____proto__这个属性有什么特征呢?其实这是每一个JavaScript对象(除了null)都具有的一个属性,叫__proto__,这个属性会指向该对象的原型,即作为实例对象和实例原型的之间的链接桥梁,这里强调,是对象,同样,因为函数也是对象,所以函数也有这个属性。我们看一个代码示例:function Person() {}var person = new Person();console.log(person.proto === Person.prototype); //true;有了第二个属性的帮助,我们就能更加全面的理解这张关系图了:通过上面的关系图我们可以看到,构造函数Person 和实例对象person 分别通过prototype和__proto__ 和实例原型Person.prototype进行关联,根据箭头指向我们不禁要有疑问:实例原型是否有属性指向构造函数或者实例呢?这时候该请出我们的第三个属性了:constructorconstructor实例原型指向实例的属性倒是没有,因为一个构造函数可能会生成很多个实例,但是原型指向构造函数的属性倒是有的,这就是我们的constructor——每一个原型都有一个constructor属性指向关联的构造函数。我们再来看一个示例:function Person() {}console.log(Person === Person.prototype.constructor); // true好了到这里我们再完善下关系图:通过对三个属性的介绍,我们总结一下:function Person() {}var person = new Person();console.log(person.proto == Person.prototype) // trueconsole.log(Person.prototype.constructor == Person) // true// 顺便学习一个ES5的方法,可以获得对象的原型console.log(Object.getPrototypeOf(person) === Person.prototype) // true上述代码中我们我们执行了以下操作:1.声明了构造函数 Person;2.使用new操作符调用 Person 实例化了一个person 对象;3.判断实例化对象通过__proto__是否指向实例原型;4.判断实例原型通过constructor是否能找到对应的构造函数;5.使用Object.getPrototypeOf方法传入一个对象 找到对应的原型对象;了解了构造函数。实例原型、和实例对象之间的关系,接下来我们讲讲实例和原型的关系:实例与原型当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。我们再举一个例子: function Person() { } Person.prototype.name = ‘Kevin’; var person = new Person(); person.name = ‘Daisy’; console.log(person.name) // Daisy delete person.name; console.log(person.name) // Kevin在上面这个例子中,我们给实例person添加了name 属性,当我们打印person.name的时候,结果自然为Daisy但是当我们删除了person下面的name属性后,读取person.name,依然能够成功输出Kevin,实际情况是从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。但是我们不禁有疑问,如果万一没有找到该怎么办?我们来看下一层的关系 原型的原型原型的原型我们前面提到过,原型也是一个对象,那么既然是对象,那肯定就有创建它的构造函数,这个构造函数就是Object(); var obj = new Object(); obj.name = ‘Kevin’; console.log(obj.name); // Kevin;其实原型对象就是通过Object构造函数生成的,结合之前我们所说的,实例__proto__指向构造函数的prototype 所以我们再丰富一下我们的关系图;到了这里我们对于 构造函数、实例对象、实例原型之间的关系又有了进一步的认识。说了这么多,终于可以介绍原型链了。原型链那Object.prototype 的原型呢?Object是根节点的对象,再往上查找就是null,我们可以打印: console.log(Object.prototype.proto === null) // true然而 null 究竟代表了什么呢?引用阮一峰老师的 《undefined与null的区别》 就是:null 表示“没有对象”,即该处不应该有值。所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。所以查找属性的时候查到 Object.prototype 就可以停止查找了。我们可以将null 也加入最后的关系图中,这样就比较完整了。上图中相互关联的原型组成的链状结构就是原型链,也就是红色的这条线补充最后,补充三点大家可能不会注意到的地方:constructor首先是constructor,我们看一个例子:function Person() {}var person = new Person();console.log(person.constructor === Person); // true当获取person.constructor时,其实 person 中并没有constructor 属性,当不能读取到constructor属性时,会从 person 的原型也就是 Person.prototype中读取,正好原型中有该属性,所以:person.constructor === Person.prototype.constructor__proto__其次是 proto ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。真的是继承吗?最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。参考:1、《Javascript设计模式与开发实践》2、JavaScript深入之从原型到原型链后记:欢迎大家关注我的微信公众号,更多优质文章第一时间推送: ...

January 30, 2019 · 1 min · jiezi

【前端面试】原型和原型链

1.题目如何准确判断一个变量是数组写一个原型链继承的例子继承实现的其他方式es6 实现继承的底层原理是什么描述new一个对象的过程zepto及其他源码中如何使用原型链2.知识点2.1 构造函数特点:以大写字母开头function Foo(name,age){ //var obj = {} //this = {} this.name = name; this.age = age; this.class = ‘class1’ // return this}var f1 = new Foo(’liming’,19);扩展var o = {} 是 var o = new Object() 的语法糖var a = [] 是 var a = new Array() 的语法糖function Foo(){} 相当于 var Foo = new Function(){}2.2 原型规则五条规则:1.所有引用类型(对象,数组,函数)都具有对象特性,即可以自由扩展属性2.所有引用类型(对象,数组,函数)都具有一个__proto__(隐式原型)属性,是一个普通对象3.所有的函数都具有prototype(显式原型)属性,也是一个普通对象4.所有引用类型(对象,数组,函数)proto__值指向它构造函数的prototype5.当试图得到一个对象的属性时,如果变量本身没有这个属性,则会去他的__proto__中去找for (var key in object) { //高级浏览器中已经屏蔽了来自原型的属性 //建议加上判断保证程序的健壮性 if (object.hasOwnProperty(key)) { console.log(object[key]); }}2.3 原型链obj. proto . proto . proto __ … Object.prototype === nullinstanceof 用于判断引用类型属于哪个构造函数obj instanceob Foo实际意义:判断 Foo.prototype 在不在 obj的原型链上3.题目解答3.1 如何准确判断一个变量是数组arr instanceof Array3.2 写一个原型链继承的例子封装dom查询function Elem(id){ this.elem = document.getElementById(id);};Elem.prototype.html = function(val){ var elem = this.elem; if (val) { elem.innerHTML = val; return this; }else{ return elem.innerHTML; }}Elem.prototype.on = function(type,fun){ var elem = this.elem; elem.addEventListener(type,fun); return this;}var div1 = new Elem(‘id1’);div1.html(“test”).on(‘click’,function(){ console.log(‘点击’);})3.3 继承实现的其他方式3.3.1 原型继承 var obj = { 0:‘a’, 1:‘b’, arr:[1] } function Foo(arr2){ this.arr2 = [1] } Foo.prototype = obj; var foo1 = new Foo(); var foo2 = new Foo(); foo1.arr.push(2); foo1.arr2.push(2); console.log(foo2.arr); //[1,2] console.log(foo2.arr2); //[1]优点:实现简单缺点:1.无法向父类构造函数传参2.同时new两个对象时改变一个对象的原型中的引用类型的属性时,另一个对象的该属性也会修改。因为来自原型对象的引用属性是所有实例共享的。3.3.2 构造继承 function Super(b){ this.b = b; this.fun = function(){} } function Foo(a,b){ this.a = a; Super.call(this,b); } var foo1 = new Foo(1,2); console.log(foo1.b);优点:可以向父类传参,子类不会共享父类的引用属性缺点:无法实现函数复用,每个子类都有新的fun,太多了就会影响性能,不能继承父类的原型对象。3.3.3 组合继承function Super(){ // 只在此处声明基本属性和引用属性 this.val = 1; this.arr = [1];}// 在此处声明函数Super.prototype.fun1 = function(){};Super.prototype.fun2 = function(){};//Super.prototype.fun3…function Sub(){ Super.call(this); // 核心 // …}Sub.prototype = new Super(); 优点:不存在引用属性共享问题,可传参,函数可复用缺点:父类的属性会被实例化两次,获取不到真正实例父类(无法区分实例是父类创建还是父类创建的)优化: function Super(b){ this.b = b; this.fun = function(){} } Super.prototype.c = function(){console.log(1111)} function Foo(a,b){ this.a = a; Super.call(this,b); } Foo.prototype = Super.prototype; //修复构造函数: var foo1 = new Foo(1,2);缺点:无法区分实例是父类创建还是子类创建的3.3.4 寄生组合继承 function Super(b){ this.b = b; } Super.prototype.c = function(){console.log(1111)} function Foo(a,b){ this.a = a; Super.call(this,b); } var f = new Function(); f.prototype = Super.prototype; Foo.prototype = new f(); //等同于 Foo.prototype = Object.create(Super.prototype); var foo1 = new Foo(1,2);对父类的prototype进行一次寄生,即包装成一个空对象的prototype,再把这个对象实例化出来作为子类的peototype。缺点:无法区分实例是父类创建还是子类创建的可以添加以下代码:Foo.prototype.constructor = Foo这种解决方法不能用于上面的组合优化方法,因为子类父类引用的是同一个原型对象,修改会同时修改。总结:继承主要是实现子类对父类方法,属性的复用。来自原型对象的引用属性是所有实例共享的,所以我们要避免从原型中继承属性。在构造函数中通过call函数可以继承父类构造函数的属性和方法,但是通过这种方式实例化出来的实例会将父类方法多次存储,影响性能。通过组合继承我们使用call继承属性,使用原型继承方法,可以解决以上两个问题,但是通过这种方式实例化出来的对象会存储两份父类构造函数中的属性。用父类的原型构造一个新对象作为子类的原型,就解决了多次存储的问题,所以最终的寄生组合继承就是最佳继承方式,它的缺点就是书写起来比较麻烦。3.3.6 node源码中的继承实现function inherits(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } });}; function Stream(){ //…}function OutgoingMessage() { Stream.call(this); //…}inherits(OutgoingMessage, Stream);OutgoingMessage.prototype.setTimeout = …以上是寄生组合继承的一个实例。1.在OutgoingMessage构造函数中通过call继承Stream构造中的属性。2.调用inherits方法继承Stream原型中的属性。3.扩展OutgoingMessage自身原型的函数。inherits方法中使用了Object.create方法,该方法的作用是通过指定的原型对象和属性创建一个新的对象。ctor.prototype=Object.create(superCtor.prototype,{…..});该方法实际上就做了我们上面寄生组合继承中的工作var f = new Function();f.prototype =superCtor.prototype;return new f();后面的参数是给原型对象添加属性,可选属性(非必填),即把自身作为新创建对象的构造函数。value: 表示constructor 的属性值;writable: 表示constructor 的属性值是否可写;[默认为: false]enumerable: 表示属性constructor 是否可以被枚举;[默认为: false]configurable: 表示属性constructor 是否可以被配置,例如 对obj.a做 delete操作是否允许;[默认为: false]3.4 es6继承的实现方式参考我这篇文章:https://segmentfault.com/a/11…3.5 描述new一个对象的过程创建一个对象{}.proto = 构造函数.prototypethis指向这个对象执行代码即对this赋值返回this3.6 zepto及其他源码中如何使用原型链var Zepto = (function(){ var $,zepto = {} // …省略N行代码… $ = function(selector, context){ return zepto.init(selector, context) } zepto.init = function(selector, context) { var dom // 针对参数情况,分别对dom赋值 // 最终调用 zepto.Z 返回的数据 return zepto.Z(dom, selector) } fnction Z(dom, selector) { var i, len = dom ? dom.length : 0 for (i = 0; i < len; i++) this[i] = dom[i] this.length = len this.selector = selector || ’’ } zepto.Z = function(dom, selector) { return new Z(dom, selector) } $.fn = { // 里面有若干个工具函数 } zepto.Z.prototype = Z.prototype = $.fn // …省略N行代码… return $})()window.Zepto = Zeptowindow.$ === undefined && (window.$ = Zepto) ...

January 8, 2019 · 3 min · jiezi

JavaScript类型判断

JS(ES6)中的基本数据类型:1.数值型(Number):包括整数、浮点数、2.布尔型(Boolean)、3.字符串型(String)、4.数组(Array)、5.空值(Null) 、6.未定义(Undefined),基本数据类型是按值访问的,因为可以直接操作保存在变量中的实际值。引用类型:Object 、Array 、Function 、Data,引用数据类型是保存在堆内存中的对象1.typeofvar a;console.log(“1:” + typeof a);var b = null;console.log(“2:” + typeof b);var c = undefined;console.log(“3:” + typeof c);var d = new Object;console.log(“4:” + typeof d);var e = function() {};console.log(“5:” + typeof e);var f = {};console.log(“6:” + typeof f);var g = ‘’;console.log(“7:” + typeof g);var h = [];console.log(“8:” + typeof h);var i = true;console.log(“9:” + typeof i);var j = 123;console.log(“10:” + typeof j);var k = NaN;console.log(“11:” + typeof k);var l = /^[-+]?\d+$/;console.log(“12:” + typeof l);打印结果如下总结:typeof对null、undefined、NaN、数组、正则、Object的类型都为object2.constructorconstructor 用于判断一个变量的原型,constructor 属性返回对创建此对象的数组函数的引用.当一个函数 F被定义时,JS引擎会为F添加 prototype 原型,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用,当执行 var f = new F() 时,F 被当成了构造函数,f 是F的实例对象,此时 F 原型上的 constructor 传递到了 f 上,因此 f.constructor === Fvar F = function(){}console.log(F.prototype);var f = new F();console.log(f.constructor===F) //true不难看出,F 利用原型对象上的 constructor 引用了自身,当 F 作为构造函数来创建对象时,原型上的 constructor 就被遗传到了新创建的对象上, 从原型链角度讲,构造函数 F 就是新对象的类型。这样做的意义是,让新对象在诞生以后,就具有可追溯的数据类型,也就是说对象的constructor属性指向他的构造函数所以内置对象在内部构建时阔以这样做出判断注:null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断constructor属性并非一定指向构造函数,他也是可以修改、变更的(当把F.prototype = {}改写后,会默认把constructor覆盖掉)instanceofinstanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性及的构造函数有这些基础类型:String、Number、Boolean、Undefined、Null、Symbol(ES6引入了一种新的原始数据类型Symbol,表示独一无二的值);复杂类型:Array,Object;其他类型:Function、RegExp、Date。var obj = new Object()obj instanceof Object // true注意左侧必须是对象(object),如果不是,直接返回false,列如:var num = 1num instanceof Number // falsenum = new Number(1)num instanceof Number // true可以看出都是num,而且都是1,只是因为第一个不是对象,是基本类型,所以直接返回false,而第二个是封装成对象,所以true。这里要严格注意这个问题,有些说法是检测目标的__proto__与构造函数的prototype相同即返回true,这是不严谨的,检测的一定要是对象才行,如:基础类型var num = 1num.proto === Number.prototype // truenum instanceof Number // falsenum = new Number(1)num.proto === Number.prototype // truenum instanceof Number // truenum.proto === (new Number(1)).proto // true上面例子可以看出,1与new Number(1)几乎是一样的,只是区别在于是否封装成对象,所以instanceof的结果是不同的,string、boolean等,这些基础类型一样的。new String(1) // String {“1”}String(1) // “1"new String(1)与String(1)是不同的,new是封装成对象,而没有new的只是基础类型转换,还是基础类型其他基础类型一样的。复杂类型,比如数组与对象,甚至函数等,与基础类型不同。复杂类型var arr = []arr instanceof Array // truearr instanceof Object // trueArray.isArray(arr) // true复杂类型从字面量是直接生成构造函数的,所以不会像基本类型一样两种情况。但是上面那个问题,当然,基础类型也会有这个问题,就是与Object对比。没办法,Object在原型链的上层,所以都会返回true,如下:(new Number(1)) instanceof Object // true由于从下往上,比如你判断是Number,那就没必要判断是不是Object了,因为已经是Number了……其他类型var reg = new RegExp(//)reg instanceof RegExp // truereg instanceof Object // truevar date = new Date()date instanceof Date // truedate instanceof Object // true除了Function,都一样,具体Function如下:function A() {}var a = new A()a instanceof Function // falsea instanceof Object // trueA instanceof Function // true这里要注意,function A() {}相当于var A; A = function() {},然后分析:a是new出来,所以是经过构造,因此已经是对象,不再是函数,所以falsea是经过构造的对象,返回ture没问题A是个函数,这没什么问题{}.toString.call(obj)用法如下console.log({}.toString.call(1));console.log({}.toString.call(“11”));console.log({}.toString.call(/123/));console.log({}.toString.call({}));console.log({}.toString.call(function() {}));console.log({}.toString.call([]));console.log({}.toString.call(true));console.log({}.toString.call(new Date()));console.log({}.toString.call(new Error()));console.log({}.toString.call(null));console.log({}.toString.call(undefined));console.log(String(null));console.log(String(undefined));返回如下注意:必须通过 call 或 apply 来调用,而不能直接调用 toString , 从原型链的角度讲,所有对象的原型链最终都指向了 Object, 按照JS变量查找规则,其他对象应该也可以直接访问到 Object 的 toString方法,而事实上,大部分的对象都实现了自身的 toString 方法,这样就可能会导致 Object 的 toString 被终止查找,因此要用 call/apply 来强制调用Object 的 toString 方法jQuery中的方法$.type(),就是用到了toStringtype: function( obj ) { if ( obj == null ) { return obj + “”; } // Support: Android<4.0, iOS<6 (functionish RegExp) return typeof obj === “object” || typeof obj === “function” ? class2type[ toString.call(obj) ] || “object” : typeof obj;},分析源代码:typeof obj === “object” || typeof obj === “function” ? class2type[ toString.call(obj) ]通过判断传入类型,如果是object或者function类型就直接返回class2type 中键值是对的结果,如果不是,那么一定就是基本类型, 通过 typeof 就可以class2type[ toString.call(obj) ] || “object"这是为了防止一些未知情况的,如果未取到,就返回object,保证了程序可用性参考文章:JS类型判断,typeof/constructor/instanceof的区别js中的constructor和prototypeJS类型判断jquery源码 揭开js之constructor属性的神秘面纱 ...

January 4, 2019 · 2 min · jiezi

Js高级编程笔记--面向对象的程序设计

理解对象属性类型1.数据属性特性:Configurable : 表示能否通过 delete 删除属性,能否修改属性特性,能否把属性改为访问器属性Enumerable : 表示能否通过 for in 循环返回Writable : 表示能否修改属性的值Value : 包含属性的值,读取或写入实际上操作的是这个值2.访问器属性特性:Configurable : 表示能否通过 delete 删除属性,能否修改属性特性,能否把属性改为访问器属性Enumerable : 表示能否通过 for in 循环返回Get : 读取时调用的参数.默认值为 undefinedSet : 写入时调用的参数。 默认值为 undefined3.注意:访问器属性不能直接定义,必须使用 Object.defineProperty()定义。修改属性默认的特性,必须使用 Object.defineProperty()方法get,set,并不一定要定义,只定义 get 为只读,只定义 set 为只写不可读。定义多个属性可以使用 Object.defineProperties()方法读取属性的特性,使用 Object.getOwnPropertyDescriptor()创建对象1.工厂模式定义一个方法接受参数,用于创建对象,并将其返回function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; return o;}var person1 = createPerson(‘andy_chen’, 18);var person2 = createPerson(‘andy_chen’, 18);工厂模式可以创建多个相似对象的问题,却没解决对象识别的问题。例如person1的类型是什么2.构造函数模式 :function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { alert(this.name); };}var person1 = new Person(‘andy_chen’, 18);var person2 = new Person(‘andy_chen’, 18);person1.sayName();person2.sayName();使用 new 操作符。实际上有以下 4 个步骤:创建一个新对象将构造函数的作用域赋给对象(使 this 指向新对象)执行构造方法(为这个对象添加属性)返回新对象构造函数的问题在于,每个方法都要在每个实例中重新创建一遍。即例子中,person1和person2的sayName的不相等的。但是,完成同样的功能的方法,却每个实例都要创建一遍,这显然不合理,所以,又出现了下面的原型模式3.原型模式:理解原型对象一图胜千言:只要创建了一个新函数,就会根据一组特定规则为该函数创建一个 prototype,这个属性指向函数的对象原型。对象原型中,则默认有一个 constructor 属性,指向该新函数。通过新函数创建的实例,有一个[[prototype]]属性(在 chrome,firefox,safari 中该属性即为proto),指向了新函数的 prototype。注意:该属性仅仅是执行构造函数的 prototype,也即是说,他们与构造函数没有直接联系了读取某个对象的属性时,会先在实例上找,如果没找到,则进一步在实例上的 prototype 属性上找为实例添加属性的时候会屏蔽掉原型上属性。这个时候即使置为 null 也没法访问到原型上的属性,只有通过 delete 删掉之后才可以XXX.prototype.isPrototype(xxx), 可以用这个方法判定对象是否是该实例的原型对象Object.getPrototypeOf() 用这个可以获取实例对应的原型对象 (ES5 新增方法)in 操作符单独使用时: in 操作符 可以确定属性是否存在于对象上(无论是存在于实例上还是原型上)用于 for 循环中时,返回的是所有能够通过对象访问的,可枚举的属性。(IE8 中,如果开发者自定义 toString 类似的系统不可枚举的方法,浏览器还是不会将它遍历出来)ES5:Object.keys() 可以返回一个包含所有可枚举属性的字符串数组Object.getOwnPropertyNames() 可以返回所有实例属性,无论是否可枚举//原型模式的实现:function Person() {}Person.prototype.name = ‘andy chen’;Person.prototype.sayName = function() { alert(this.name);};更简单的原型语法重写整个 prototype,不过会导致 constructor 改变。所以需要重新指定 constructor.//更简单的原型语法function Person() {}Person.prototype = { constructor: Person, //因为这种写法会覆盖掉原来的Person.prototype,需要重新为constructor赋值 name: ‘andy chen’, sayName: function() { alert(this.name); }};var person1 = new Person();var person2 = new Person();原型模式的问题:所有实例都共享一个prototype,类似上面的例子,person1,person2的name属性是共享的。如果修改其中一个,会导致另一个也受影响。所以,才会出现下面构造函数与原型模式组合使用4.组合使用构造函数和原型模式创建自定义类型最常见的方式就是组合使用构造函数和原型模式构造函数定义实例属性,而原型模式用于定义方法和共享的属性. 所以,上面的例子可以改写成这样:function Person(name) { this.name = name;}Person.prototype = { constructor: Person, sayName: function() { alert(this.name); }};var person1 = new Person(‘andy chen’);var person2 = new Person(‘andy chen’);除了使用组合模式创建对象,还有以下几种方式,可以针对不同的情况选择。5.动态原型模式在构造方法中,判断是否是第一次进入使用构造方法,如果是,则添加一系列的方法到原型上6.寄生构造函数模式类基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码然后再返回新创建的对象。7.稳妥构造函数模式:稳妥对象指的是没有公共属性,而且其方法也不引用 this 对象。最适合用于一些安全的环境或者在防止数据被其他程序改动时使用稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:新创建的对象实例不引用 this.不使用 new 操作符调用构造函数继承OO 语言一般拥有两种继承方式:接口继承(只继承方法签名)以及实现继承(继承实际方法)ES 无法像其他 OO 语言一样支持接口继承,只能依靠原型链实现 实现继承1. 原型链要了解原型链的概念,先回顾一下构造函数,原型和实例之间的关系(参考图 6-1)每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针.每个实例都包含一个指向原型对象的内部指针的内部属性(在 chrome 中一般为proto属性)那么,如果我们有个新的构造函数,并让它的原型对象等于另一个类型的实例,结果会怎样.对于这个新的构造函数,它的原型对象就变成了另一个类型的实例,而这个实例中,又包含一个内部属性,指向了另一个原型对象(该原型对象内部 constructor 指向另一个构造函数),如果这个原型对象又是另一个类型的实例,则它又包含了一个内部属性,继续指向上层的原型对象。这样层层递进,就形成了原型链。如下图:特点在实例中搜索属性的时候,便是基于原型链来搜索的,先搜索实例,再在原型链上一层层往上搜,直到找到或者到原型链末端才会停下来由于所有引用类型都继承了 Object,所以原型链的最顶层是 Object使用原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链原型链实现继承的方式:function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { this.catName = ‘cat’;}Cat.prototype = new Animal();var cat1 = new Cat();var cat2 = new Cat();alert(cat1.getName()); //由于第10行,将Cat的原型指向Animal的实例,因为实例中有指向Animal.prototype的指针。所以,这里可以访问到getName()cat1.name = ‘changed name’;alert(cat2.getName());原型链的问题:使用原型链,由于是使用新的实例作为子类型的原型,实例中却包含了父类型的属性,所以原来父类型的属性,就都到了子类型的原型上了。这就会造成子类型的不同实例会共享同个属性.如上例子中,第 15 行,改变 cat1 实例的 name 属性影响到了 cat2 的 name 属性创建子类型的时候,不能向父类型传递参数2. 借用构造函数由于原型链存在问题,所以便出现了借用构造函数的方法在子类型的构造方法中,调用父类型的构造方法:SuperType.call(this); 将父类型的属性添加到子类型上,并且可以传递参数给父类型借用构造函数实现继承的方式:function Animal() { this.name = ‘animal’;}function Cat() { Animal.call(this);}var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.name); //changed namealert(cat2.name); //animal //借用构造函数的方式,各实例之间的属性便不会互相影响借用构造函数问题:类似创建对象单纯使用构造方法一样,也会造成公有的方法无法公用。所以一般也很少单独使用此方式3. 组合继承组合原型链以及借用构造函数使用原型链实现对原型属性和方法的继承借用构造函数来实现对实例中属性的继承。function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}Cat.prototype = new Animal(); //原型链方式Cat.prototype.constructor = Cat;//这里可以var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal组合继承的问题:父类的属性会存在于子类型的原型上,导致被不同实例共享。虽然由于借用构造函数之后,导致实例上又重写了这些属性,所以每个实例有各自的属性。另外,instanceof 和 isPrototypeOf 能够识别基于组合继承创建的对象组合继承,并不完美因为我们只需要继承父类型原型上的属性而已,不需要父类型实例的属性。还有更好的方法,但我们首先要先了解一下其他继承方式4. 原型式继承//如果o为某个对象的prototype,则object返回的 对象,包含了该对象原型上的所有方法function object(o) { function F() {} F.prototype = o; return new F();}Es5 新增的 Object.create() ,类似这样。 在没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过,包含引用类型值的属性始终会共享5. 寄生式继承在复制新对象后,继续以某种方式增强对象,即为寄生式继承。function createAnother(original) { var clone = object(original); clone.sayHi = function() { doSomeThing(); }; return clone;}在主要考虑对象而不是自定义类型和构造函数的时候,适合使用寄生式继承缺点: 类似单纯的构造函数模式使用,函数不能复用6. 寄生组合式继承通过原型式继承,继承父类的原型方法。再通过构造函数方法,继承父类的属性。function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}//原型继承方式function object(superProto) { function F() {} F.prototype = superProto; return new F();}Cat.prototype = object(Animal.prototype); //通过一个空的函数作为媒介,将空函数的原型指向父类型原型,并将子类型的原型指向这个空函数的实例。便只继承父类原型上的属性及方法Cat.prototype.constructor = Cat;//这里可以之后添加子类的方法Cat.prototype.run = function() { alert(‘cat run’);};var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal最后,寄生组合式继承是引用类型最理想的继承范式。上述代码还能再进一步优化。//原型继承方式function object(superProto) { function F() {} F.prototype = superProto; return new F();}//公用的继承方法function inheritPrototype(subType, superType) { subType.prototype = object(superType.prototype); subType.prototype.constructor = subType;}function Animal() { this.name = ‘animal’;}Animal.prototype.getName = function() { return this.name;};function Cat() { Animal.call(this); //借用构造函数}inheritPrototype(Cat, Animal); //调用此方法继承原型//这里可以之后添加子类的方法Cat.prototype.run = function() { alert(‘cat run’);};var cat1 = new Cat();var cat2 = new Cat();cat1.name = ‘changed name’;alert(cat1.getName()); //changed namealert(cat2.getName()); //animal小结这是 js 对象的创建以及继承,es6 中新增了关键字class和extend。方便我们进行面向对象的编程。但是理解背后的继承原理对我们编程过程中也是极有帮助的:)喜欢就收藏或者点个赞呗 !! ...

January 2, 2019 · 3 min · jiezi

深入总结Javascript原型及原型链

本篇文章给大家详细分析了javascript原型及原型链的相关知识点以及用法分享,具有一定的参考价值,对此有需要的朋友可以参考学习下。如有不足之处,欢迎批评指正。我们创建的每个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个原型对象,而这个原型对象中拥有的属性和方法可以被所以实例共享function Person(){}Person.prototype.name = “Nicholas”;Person.prototype.age = 29;Person.prototype.sayName = function(){alert(this.name);};var person1 = new Person();person1.sayName(); //“Nicholas"var person2 = new Person();person2.sayName(); //“Nicholas"alert(person1.sayName == person2.sayName); //true//欢迎加入前端全栈开发交流圈一起学习交流:864305860一、理解原型对象无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262 第 5 版中管这个指针叫 [[Prototype]] 。虽然在脚本中没有标准的方式访问 [[Prototype]] ,但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性__proto__ ;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。以前面使用 Person 构造函数和 Person.prototype 创建实例的代码为例,图 6-1 展示了各个对象之间的关系。在此, Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person 。person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype ;换句话说,它们与构造函数没有直接的关系。可以调用 person1.sayName() 。这是通过查找对象属性的过程来实现的。(会先在实例上搜索,如果搜索不到就会继续搜索原型。)用isPrototypeOf()方法判断实例与原型对象之间的关系<br>alert(Person.prototype.isPrototypeOf(person1)); //truealert(Person.prototype.isPrototypeOf(person2)) //true<br><br>用Object.getPrototypeOf() 方法返回实例的原型对象<br>alert(Object.getPrototypeOf(person1) == Person.prototype); //true<br><br>使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是存在于原型中。<br>alert(person1.hasOwnProperty(“name”)); //false 来着原型<br>person1.name = “Greg”;<br>alert(person1.name); //“Greg”——来自实例<br>alert(person1.hasOwnProperty(“name”)); /true<br>//欢迎加入前端全栈开发交流圈一起学习交流:864305860二、更简单的原型语法前面例子中每添加一个属性和方法就要敲一遍 Person.prototype 。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。function Person(){}Person.prototype = { name : “Nicholas”, age : 29, job: “Software Engineer”, sayName : function () { alert(this.name); }//欢迎加入前端全栈开发交流圈一起学习交流:864305860};在上面的代码中,我们将 Person.prototype 设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外: constructor 属性不再指向 Person 了。前面曾经介绍过,每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。var friend = new Person();alert(friend instanceof Object); //truealert(friend instanceof Person); //truealert(friend.constructor == Person); //falsealert(friend.constructor == Object); //true//欢迎加入前端全栈开发交流圈一起学习交流:864305860在此,用 instanceof 操作符测试 Object 和 Person 仍然返回 true ,但 constructor 属性则等于 Object 而不等于 Person 了。如果 constructor 的值真的很重要,可以像下面这样特意将它设置回适当的值。function Person(){}Person.prototype = { constructor : Person, name : “Nicholas”, age : 29, job: “Software Engineer”, sayName : function () { alert(this.name); }//欢迎加入前端全栈开发交流圈一起学习交流:864305860};三、原生对象的原型所有原生引用类型( Object 、 Array 、 String ,等等)都在其构造函数的原型上定义了方法。例如,在 Array.prototype 中可以找到 sort() 方法,而在 String.prototype 中可以找到substring() 方法。尽管可以这样做,但不推荐修改原生对象的原型。四、原型对象的问题原型模式的最大问题是由其共享的本性所导致的。 修改其中的一个,另一个也会受影响。function Person(){}Person.prototype = {constructor: Person,name : “Nicholas”,age : 29,job : “Software Engineer”,friends : [“Shelby”, “Court”],sayName : function () {alert(this.name);}//欢迎加入前端全栈开发交流圈一起学习交流:864305860};var person1 = new Person();var person2 = new Person();person1.friends.push(“Van”);alert(person1.friends); //“Shelby,Court,Van"alert(person2.friends); //“Shelby,Court,Van"alert(person1.friends === person2.friends); //true五、原型链其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。然后层层递进,就构成了实例与原型的链条,这就是所谓原型链的基本概念。function SuperType(){ this.property = true;}//欢迎加入前端全栈开发交流圈一起学习交流:864305860SuperType.prototype.getSuperValue = function(){ return this.property;};function SubType(){ this.subproperty = false;}//欢迎加入前端全栈开发交流圈一起学习交流:864305860//继承了 SuperTypeSubType.prototype = new SuperType();SubType.prototype.getSubValue = function (){ return this.subproperty;};//欢迎加入前端全栈开发交流圈一起学习交流:864305860var instance = new SubType();alert(instance.getSuperValue()); //trueproperty 则位于 SubType.prototype 中。这是因为 property 是一个实例属性,而 getSuperValue() 则是一个原型方法。既然 SubType.prototype 现在是 SuperType的实例,那么 property 当然就位于该实例中了结语感谢您的观看,如有不足之处,欢迎批评指正。 ...

December 13, 2018 · 2 min · jiezi

美化select下拉框

在写示例的时候,用到了下拉框,但是原生的下拉框是在是有点难看,然后模仿着写了点,一个是直接在写好的Dom上进行美化,一个是用js生成,然后定义类名及相应的事件来处理1.效果图2.直接是在Dom上美化html文件<div class=“root”> <div id=“selectedItem”> <div id=“promptText”><span id=“spanText”>请选择你喜欢的文字</span><img src="../images/arrowup.png" id=“arrows” /></div> <ul class=“choiceDescription”> <li class=“item”>万水千山,陪你一起看</li> <li class=“item”>万水千山,陪你一起看1</li> <li class=“item”>万水千山,陪你一起看2</li> <li class=“item”>万水千山,陪你一起看3</li> <li class=“item”>万水千山,陪你一起看4</li> </ul> </div></div>css文件ul{ margin: 0; padding: 0; list-style: none;}/* 下拉框包含层 /#selectedItem{ width: 240px; cursor: pointer;}/ 已选中的选项 /#promptText{ position: relative; padding-left: 10px; width: 230px; height: 30px; line-height: 30px; border: 1px solid #d3d3d3; border-radius: 4px; background: #fff; color: #999; font-size: 14px;}/ 图标 /#arrows{ position: absolute; top: 0; right: 0; width: 30px; height: 30px; vertical-align: middle;}#arrows:focus{ outline: none;}/ 下拉可选项包含层 /.choiceDescription{ position: absolute; display: none; /overflow: hidden;/ margin-top: 2px; width: 240px; border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 1px 6px rgba(0, 0, 0, .1); background: #fff;}.show{ display: block;}/ 下拉可选项 /.item{ height: 30px; line-height: 30px; padding-left: 10px; font-size: 15px; color: #666;}.item:hover{ color: #fff; background: rgba(49, 255, 195, 0.67);}js文件(function() { let choiceDescription = document.getElementsByClassName(‘choiceDescription’)[0]; let arrows = document.getElementById(‘arrows’); / 用于判断是否是下拉 / let isDown = false; let selectedItem = document.getElementById(‘selectedItem’); / 对点击下拉进行监听 / selectedItem.addEventListener(‘click’, function() { isDown = !isDown; if(isDown) { / 如果是下拉状态,则显示下拉的选项,并把图标显示为向下的图标 / choiceDescription.className += ’ show’; arrows.src = ‘../images/arrowdown.png’; } else { choiceDescription.className = ‘choiceDescription’; arrows.src = ‘../images/arrowup.png’; } }); choiceDescription.addEventListener(‘click’, function(e) { let promptText = document.getElementById(‘spanText’); let selectElement = e.target; / 判断是否点击的是li标签,防止点击了li标签以外的空白位置 / while(selectElement.tagName !== ‘LI’) { / 如果点中的是当前容器层 / if(selectElement == choiceDescription) { selectElement = null; break; } / 若果不是,则再找父级容器 / selectElement = selectElement.parentNode; } / innerText、innerHTML、value * innerText 是指html标签里的文字信息,单纯的文本,不会有html标签,存在兼容性 * innerHTML 是指包含在html标签里的所有子元素,包括空格、html标签 * value 表单里的元素属性值 * / if(selectElement) { promptText.innerHTML = e.target.innerHTML; } });})()在已有的Dom节点上对Dom绑定事件,我这里的宽度是固定死的,相对来说不是很友好3.js自动生成进行美化html文件<div id=“select” class=“select”></div><script src=“autoGenerateSelect.js”></script><script> (function() { / 当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了 * 当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash / document.addEventListener(‘DOMContentLoaded’,function(){ new $Selector({ elementSelector:’#select’, options:[ {name:‘选项1’,value:‘0’}, {name:‘选项2’,value:‘1’}, {name:‘选项3’,value:‘2’} ], defaultText:‘选项2’ }); }) })()</script>css文件html, body, ul{ margin: 0; padding: 0;}ul{ list-style: none;}#select{ padding: 30px 40px 0;}/ 下拉框 /.dropDown{ position: relative; display: inline-block; min-width: 120px; box-sizing: border-box; color: #515a6e; font-size: 14px;}/ 已选中的值包含层 /.selectedOption{ position: relative; box-sizing: border-box; outline: none; user-select: none; cursor: pointer; background: #fff; border-radius: 4px; border: 1px solid #dcdee2; transition: all .2s ease-in-out;}.selectedValue{ display: block; overflow: hidden; height: 28px; line-height: 28px; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; padding-left: 8px; padding-right: 24px;}/ 图标 /.arrowDown{ position: absolute; display: inline-block; top: 50%; right: 8px; margin-top: -7px; font-size: 14px; color: #808695; transition: all .2s ease-in-out; / 字体抗锯齿渲染 / -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}.arrowDown:before{ content: “”; display: block; width: 6px; height: 6px; background-color: transparent; border-left: 2px solid #808695; border-bottom: 2px solid #808695; transform: rotate(-45deg);}/ 所有选项的包含层 /.optionsContainer{ position: absolute; top: 30px; left: 0; min-width: 120px; max-height: 200px; margin: 5px 0; padding: 5px 0; background: #fff; box-sizing: border-box; border-radius: 4px; box-shadow: 0 1px 6px rgba(0, 0, 0, .2); z-index: 2; transform-origin: center top 0px; transition: all 0.3s; will-change: top, left; transform: scale(1, 0); opacity: 0;}/ 每个选项 /.optionsItem{ line-height: normal; padding: 7px 16px; color: #515a6e; font-size: 12px; white-space: nowrap; cursor: pointer; transition: background .2s ease-in-out;}.itemSelected, .optionsItem:hover{ color: #2d8cf0; background-color: #f3f3f3;}对下拉框初始化/ 私有方法:初始化下拉框 /_initSelector({ / 传入id,class,tag,用于挂载下拉框 / elementSelector = ‘’, / 传入的下拉框选项 / options = [{ name: ‘请选择你喜欢的颜色’, value: ‘0’ }], defaultText = ‘请选择你喜欢的颜色’}) { / 找到要挂载的Dom节点 / this.parentElement = document.querySelector(elementSelector) || document.body; this.options = options; this.defaultText = defaultText; / 下拉框的显示与隐藏状态 / this.downStatus = false; / 下拉框默认选中的值 / this.defaultValue = ‘’; this._createElement();},创建元素节点 / 创建Dom节点 /_createElement() { / 创建下拉框最外层 / let dropDown = document.createElement(‘div’); dropDown.className = ‘dropDown’; / 已选中的选项值 / let selectedOption = document.createElement(‘div’); selectedOption.className = ‘selectedOption’; / 选中的值 / let selectedValue = document.createElement(‘span’); selectedValue.className = ‘selectedValue’; / 先赋值为默认值 / selectedValue.innerText = this.defaultText; / 向下的图标 / let downIcon = document.createElement(‘i’); downIcon.className = ‘arrowDown’; / 将已选中的值的层添加到Dom节点中 / selectedOption.appendChild(selectedValue); selectedOption.appendChild(downIcon); / 创建选项的外层容器 / let optionsContainer = document.createElement(‘div’); optionsContainer.className = ‘optionsContainer’; / 用ul来包含选项层 / let ulOptionsList = document.createElement(‘ul’); ulOptionsList.className = ‘ulOptionsList’; / 循环创建每个选项 / this.options.forEach((item) => { let optionsItem = document.createElement(’li’); / 是否是选中状态 / if(item.name == this.defaultText) { optionsItem.className = ‘optionsItem itemSelected’; } else { optionsItem.className = ‘optionsItem’; } optionsItem.innerText = item.name; ulOptionsList.appendChild(optionsItem); }); / 添加到每个对应的元素里面 / optionsContainer.appendChild(ulOptionsList); dropDown.appendChild(selectedOption); dropDown.appendChild(optionsContainer); this.parentElement.appendChild(dropDown); / 设置Dom元素,挂载、绑定事件 / / 已选中的选项的包含层 / this.selectedOption = selectedOption; / 选中的值 / this.selectedValue = selectedValue; / 下拉框选项包含层 / this.optionsContainer = optionsContainer; this._handleShowOptions(this.parentElement); this._unifyWidth(selectedOption);},显示与隐藏相关事件/ 显示与隐藏事件 /_handleShowOptions(element) { element.addEventListener(‘click’, (e) => { let clickNode = e.target; this._unifyWidth(this.selectedOption); / 点击的是否是下拉框 / if(this._isOptionNode(clickNode, this.selectedOption)) { if(this.downStatus) { this._hiddenDropDown(); } else { this._showDropDown(); } } else if(clickNode.className == ‘optionsItem’) { this._handleSelected(clickNode); } else { this._hiddenDropDown(); } })},/ 判断是否是下拉框选项 /_isOptionNode(clickNode, target) { if (!clickNode || clickNode === document) return false; return clickNode === target ? true : this._isOptionNode(clickNode.parentNode, target);},/ 显示下拉框选项 /_showDropDown() { this.optionsContainer.style.transform = ‘scale(1, 1)’; this.optionsContainer.style.opacity = ‘1’; this.selectedOption.className = ‘selectedOption’; this.downStatus = true;},/ 隐藏下拉框选项 /_hiddenDropDown() { this.optionsContainer.style.transform = ‘scale(1, 0)’; this.optionsContainer.style.opacity = ‘0’; this.selectedOption.className = ‘selectedOption’; this.downStatus = false;},定义点击事件 / 对每个选项的点击事件 /_handleSelected(clickNode) { this.selectedValue.innerText = clickNode.innerText; clickNode.className = ‘optionsItem itemSelected’; this._siblingsDom(clickNode, function(clickNode) { if(clickNode) { clickNode.className = ‘optionsItem’; } }); this._hiddenDropDown();},/ 兄弟节点处理函数 /_siblingsDom(clickNode, callback) { / arguments 是一个对应于传递给函数的参数的类数组对象 * arguments对象是所有(非箭头)函数中都可用的局部变量 * 包含传递给函数的每个参数,第一个参数在索引0处 * arguments对象不是一个 Array,它类似于Array, * 但除了length属性和索引元素之外没有任何Array属性 * / (function (ele) { / arguments.callee * 指向当前执行的函数 * / callback(ele); if (ele && ele.previousSibling) { arguments.callee(ele.previousSibling); } })(clickNode.previousSibling); (function (ele) { callback(ele); if (ele && ele.nextSibling) { arguments.callee(ele.nextSibling); } })(clickNode.nextSibling);},判断宽度/ 判断宽度 /_unifyWidth(selectedOption) { / 找到所有的li标签 / let optionsItem = document.querySelectorAll(’.optionsItem’); let standardWidth = selectedOption.offsetWidth; / 对每个li标签设置宽度 / optionsItem.forEach((item) => { standardWidth = item.offsetWidth > standardWidth ? item.offsetWidth : standardWidth; item.style.width = standardWidth - 32 + ‘px’; selectedOption.style.width = standardWidth + ‘px’; });}(function() { / 定义selector下拉框 / let Selector = function(params) { / 初始化 / this._initSelector(params); }; Selector.prototype = { / 将上面的方法全部放在Selector原型上 / }; / 挂载到window上/ window.$Selector = Selector;})();关于原型与原型链,可以查看我记录的js面试使用js生成的话,相对来说,代码长点,我这里的话,对宽度有做判断,但是不完美关于DOMContentLoaded可以查看这篇文章DOMContentLoaded正在努力学习中,若对你的学习有帮助,留下你的印记呗(点个赞咯^_^)往期好文推荐:判断iOS和Android及PC端纯css实现瀑布流(multi-column多列及flex布局)实现单行及多行文字超出后加省略号微信小程序之购物车和父子组件传值及calc的注意事项 ...

December 10, 2018 · 5 min · jiezi

一文让你理解什么是 JS 原型

前言最近整理了一部分的JavaScript知识点,由于js高级阶段涉及知识点比较复杂,文章一直没更新,这里单独将原型部分的概念拎出来理解下。1.原型1.1 传统构造函数存在问题通过自定义构造函数的方式,创建小狗对象:function Dog(name, age) { this.name = name; this.age = age; this.say = function() { console.log(‘汪汪汪’); }}var dog1 = new Dog(‘哈士奇’, 1.5);var dog2 = new Dog(‘大黄狗’, 0.5);console.log(dog1);console.log(dog2);console.log(dog1.say == dog2.say); //输出结果为false画个图理解下:每次创建一个对象的时候,都会开辟一个新的空间,我们从上图可以看出,每只创建的小狗有一个say方法,这个方法都是独立的,但是功能完全相同。随着创建小狗的数量增多,造成内存的浪费就更多,这就是我们需要解决的问题。为了避免内存的浪费,我们想要的其实是下图的效果:解决方法:这里最好的办法就是将函数体放在构造函数之外,在构造函数中只需要引用该函数即可。function sayFn() { console.log(‘汪汪汪’);}function Dog(name, age) { this.name = name; this.age = age; this.say = sayFn();}var dog1 = new Dog(‘哈士奇’, 1.5);var dog2 = new Dog(‘大黄狗’, 0.5);console.log(dog1);console.log(dog2);console.log(dog1.say == dog2.say); //输出结果为true这样写依然存在问题:全局变量增多,会增加引入框架命名冲突的风险代码结构混乱,会变得难以维护想要解决上面的问题就需要用到构造函数的原型概念。1.2 原型的概念prototype:原型。每个构造函数在创建出来的时候系统会自动给这个构造函数创建并且关联一个空的对象。这个空的对象,就叫做原型。关键点:每一个由构造函数创建出来的对象,都会默认的和构造函数的原型关联;当使用一个方法进行属性或者方法访问的时候,会先在当前对象内查找该属性和方法,如果当前对象内未找到,就会去跟它关联的原型对象内进行查找;也就是说,在原型中定义的方法跟属性,会被这个构造函数创建出来的对象所共享;访问原型的方式:构造函数名.prototype。示例图:示例代码: 给构造函数的原型添加方法function Dog(name,age){ this.name = name; this.age = age;}// 给构造函数的原型 添加say方法Dog.prototype.say = function(){ console.log(‘汪汪汪’);}var dog1 = new Dog(‘哈士奇’, 1.5);var dog2 = new Dog(‘大黄狗’, 0.5);dog1.say(); // 汪汪汪dog2.say(); // 汪汪汪我们可以看到,本身Dog这个构造函数中是没有say这个方法的,我们通过Dog.prototype.say的方式,在构造函数Dog的原型中创建了一个方法,实例化出来的dog1、dog2会先在自己的对象先找say方法,找不到的时候,会去他们的原型对象中查找。如图所示:在构造函数的原型中可以存放所有对象共享的数据,这样可以避免多次创建对象浪费内存空间的问题。1.3 原型的使用1、使用对象的动态特性使用对象的动态属性,其实就是直接使用prototype为原型添加属性或者方法。function Person () {}Person.prototype.say = function () { console.log( ‘讲了一句话’ );};Person.prototype.age = 18;var p = new Person();p.say(); // 讲了一句话console.log(p.age); // 182、直接替换原型对象每次构造函数创建出来的时候,都会关联一个空对象,我们可以用一个对象替换掉这个空对象。function Person () {}Person.prototype = { say : function () { console.log( ‘讲了一句话’ ); },};var p = new Person();p.say(); // 讲了一句话注意:使用原型的时候,有几个注意点需要注意一下,我们通过几个案例来了解一下。使用对象.属性名去获取对象属性的时候,会先在自身中进行查找,如果没有,就去原型中查找;// 创建一个英雄的构造函数 它有自己的 name 和 age 属性function Hero(){ this.name=“德玛西亚之力”; this.age=18;}// 给这个构造函数的原型对象添加方法和属性Hero.prototype.age= 30;Hero.prototype.say=function(){ console.log(‘人在塔在!!!’);}var h1 = new Hero();h1.say(); // 先去自身中找 say 方法,没有再去原型中查找 打印:‘人在塔在!!!‘console.log(p1.name); // “德玛西亚之力"console.log(p1.age); // 18 先去自身中找 age 属性,有的话就不去原型中找了使用对象.属性名去设置对象属性的时候,只会在自身进行查找,如果有,就修改,如果没有,就添加;// 创建一个英雄的构造函数function Hero(){ this.name=“德玛西亚之力”;}// 给这个构造函数的原型对象添加方法和属性Hero.prototype.age = 18;var h1 = new Hero();console.log(h1); // {name:“德玛西亚之力”}console.log(h1.age); // 18h1.age = 30; // 设置的时候只会在自身中操作,如果有,就修改,如果没有,就添加 不会去原型中操作console.log(h1); // {name:“德玛西亚之力”,age:30}console.log(h1.age); // 30一般情况下,不会将属性放在原型中,只会将方法放在原型中;在替换原型的时候,替换之前创建的对象,和替换之后创建的对象的原型不一致!!!// 创建一个英雄的构造函数 它有自己的 name 属性function Hero(){ this.name=“德玛西亚之力”;}// 给这个构造函数的默认原型对象添加 say 方法Hero.prototype.say = function(){ console.log(‘人在塔在!!!’);}var h1 = new Hero();console.log(h1); // {name:“德玛西亚之力”}h1.say(); // ‘人在塔在!!!’// 开辟一个命名空间 obj,里面有个 kill 方法var obj = { kill : function(){ console.log(‘大宝剑’); }}// 将创建的 obj 对象替换原本的原型对象Hero.prototype = obj;var h2 = new Hero();h1.say(); // ‘人在塔在!!!‘h2.say(); // 报错h1.kill(); // 报错h2.kill(); // ‘大宝剑’画个图理解下:图中可以看出,实例出来的h1对象指向的原型中,只有say()方法,并没有kill()方法,所以h1.kill()会报错。同理,h2.say()也会报错。1.4 __proto__属性在js中以_开头的属性名为js的私有属性,以__开头的属性名为非标准属性。__proto__是一个非标准属性,最早由firefox提出来。1、构造函数的 prototype 属性之前我们访问构造函数原型对象的时候,使用的是prototype属性:function Person(){}//通过构造函数的原型属性prototype可以直接访问原型Person.prototype;在之前我们是无法通过构造函数new出来的对象访问原型的:function Person(){}var p = new Person();//以前不能直接通过p来访问原型对象2、实例对象的 proto 属性__proto__属性最早是火狐浏览器引入的,用以通过实例对象来访问原型,这个属性在早期是非标准的属性,有了__proto__属性,就可以通过构造函数创建出来的对象直接访问原型。function Person(){}var p = new Person();//实例对象的__proto__属性可以方便的访问到原型对象p.proto;//既然使用构造函数的prototype和实例对象的__proto__属性都可以访问原型对象//就有如下结论p.proto === Person.prototype;如图所示:3、__proto__属性的用途可以用来访问原型;在实际开发中除非有特殊的需求,不要轻易的使用实例对象的__proto__属性去修改原型的属性或方法;在调试过程中,可以轻易的查看原型的成员;由于兼容性问题,不推荐使用。3.5 constuctor属性constructor:构造函数,原型的constructor属性指向的是和原型关联的构造函数。示例代码:function Dog(){ this.name=“husky”;}var d=new Dog();// 获取构造函数console.log(Dog.prototype.constructor); // 打印构造函数 Dogconsole.log(d.proto.constructor); // 打印构造函数 Dog如图所示:获取复杂类型的数据类型:通过obj.constructor.name的方式,获取当前对象obj的数据类型。在一个的函数中,有个返回值name,它表示的是当前函数的函数名;function Teacher(name,age){ this.name = name; this.age = age;}var teacher = new Teacher();// 假使我们只知道一个对象teacher,如何获取它的类型呢?console.log(teacher.proto.constructor.name); // Teacherconsole.log(teacher.constructor.name); // Teacher实例化出来的teacher对象,它的数据类型是啥呢?我们可以通过实例对象teacher.proto,访问到它的原型对象,再通过.constructor访问它的构造函数,通过.name获取当前函数的函数名,所以就能得到当前对象的数据类型。又因为.__proto__是一个非标准的属性,而且实例出的对象继承原型对象的方法,所以直接可以写成:obj.constructor.name。1.6 原型继承原型继承:每一个构造函数都有prototype原型属性,通过构造函数创建出来的对象都继承自该原型属性。所以可以通过更改构造函数的原型属性来实现继承。继承的方式有多种,可以一个对象继承另一个对象,也可以通过原型继承的方式进行继承。1、简单混入继承直接遍历一个对象,将所有的属性和方法加到另一对象上。var animal = { name:“Animal”, sex:“male”, age:5, bark:function(){ console.log(“Animal bark”); }};var dog = {};for (var k in animal){ dog[k]= animal[k];}console.log(dog); // 打印的对象与animal一模一样缺点:只能一个对象继承自另一个对象,代码复用太低了。2、混入式原型继承混入式原型继承其实与上面的方法类似,只不过是将遍历的对象添加到构造函数的原型上。var obj={ name:‘zs’, age:19, sex:‘male’ }function Person(){ this.weight=50;}for(var k in obj){ // 将obj里面的所有属性添加到 构造函数 Person 的原型中 Person.prototype[k] = obj[k];}var p1=new Person();var p2=new Person();var p3=new Person();console.log(p1.name); // ‘zs’console.log(p2.age); // 19console.log(p3.sex); // ‘male’面向对象思想封装一个原型继承我们可以利用面向对象的思想,将面向过程进行封装。function Dog(){ this.type = ‘yellow Dog’;}// 给构造函数 Dog 添加一个方法 extendDog.prototype.extend = function(obj){ // 使用混入式原型继承,给 Dog 构造函数的原型继承 obj 的属性和方法 for (var k in obj){ this[k]=obj[k]; }}// 调用 extend 方法Dog.prototype.extend({ name:“二哈”, age:“1.5”, sex:“公”, bark:function(){ console.log(‘汪汪汪’); }});3、替换式原型继承替换式原型继承,在上面已经举过例子了,其实就是将一个构造函数的原型对象替换成另一个对象。function Person(){ this.weight=50;}var obj={ name:‘zs’, age:19, sex:‘male’}// 将一个构造函数的原型对象替换成另一个对象Person.prototype = obj;var p1=new Person();var p2=new Person();var p3=new Person();console.log(p1.name); // ‘zs’console.log(p2.age); // 19console.log(p3.sex); // ‘male’之前我们就说过,这样做会产生一个问题,就是替换的对象会重新开辟一个新的空间。替换式原型继承时的bug替换原型对象的方式会导致原型的constructor的丢失,constructor属性是默认原型对象指向构造函数的,就算是替换了默认原型对象,这个属性依旧是默认原型对象指向构造函数的,所以新的原型对象是没有这个属性的。解决方法:手动关联一个constructor属性function Person() { this.weight = 50;}var obj = { name: ‘zs’, age: 19, sex: ‘male’}// 在替换原型对象函数之前 给需要替换的对象添加一个 constructor 属性 指向原本的构造函数obj.constructor = Person;// 将一个构造函数的原型对象替换成另一个对象Person.prototype = obj;var p1 = new Person();console.log(p1.proto.constructor === Person); // true4、Object.create()方法实现原型继承当我们想把对象1作为对象2的原型的时候,就可以实现对象2继承对象1。前面我们了解了一个属性:proto,实例出来的对象可以通过这个属性访问到它的原型,但是这个属性只适合开发调试时使用,并不能直接去替换原型对象。所以这里介绍一个新的方法:Object.create()。语法: var obj1 = Object.create(原型对象);示例代码: 让空对象obj1继承对象obj的属性和方法var obj = { name : ‘盖伦’, age : 25, skill : function(){ console.log(‘大宝剑’); }}// 这个方法会帮我们创建一个原型是 obj 的对象var obj1 = Object.create(obj);console.log(obj1.name); // “盖伦"obj1.skill(); // “大宝剑"兼容性:由于这个属性是ECMAScript5的时候提出来的,所以存在兼容性问题。利用浏览器的能力检测,如果存在Object.create则使用,如果不存在的话,就创建构造函数来实现原型继承。// 封装一个能力检测函数function create(obj){ // 判断,如果浏览器有 Object.create 方法的时候 if(Object.create){ return Object.create(obj); }else{ // 创建构造函数 Fun function Fun(){}; Fun.prototype = obj; return new Fun(); }}var hero = { name: ‘盖伦’, age: 25, skill: function () { console.log(‘大宝剑’); }}var hero1 = create(hero);console.log(hero1.name); // “盖伦"console.log(hero1.proto == hero); // true2.原型链对象有原型,原型本身又是一个对象,所以原型也有原型,这样就会形成一个链式结构的原型链。2.1 什么是原型链示例代码: 原型继承练习// 创建一个 Animal 构造函数function Animal() { this.weight = 50; this.eat = function() { console.log(‘蜂蜜蜂蜜’); }}// 实例化一个 animal 对象var animal = new Animal();// 创建一个 Preson 构造函数function Person() { this.name = ‘zs’; this.tool = function() { console.log(‘菜刀’); }}// 让 Person 继承 animal (替换原型对象)Person.prototype = animal;// 实例化一个 p 对象 var p = new Person();// 创建一个 Student 构造函数function Student() { this.score = 100; this.clickCode = function() { console.log(‘啪啪啪’); }}// 让 Student 继承 p (替换原型对象)Student.prototype = p;//实例化一个 student 对象var student = new Student();console.log(student); // 打印 {score:100,clickCode:fn}// 因为是一级级继承下来的 所以最上层的 Animate 里的属性也是被继承的console.log(student.weight); // 50student.eat(); // 蜂蜜蜂蜜student.tool(); // 菜刀如图所示:我们将上面的案例通过画图的方式展现出来后就一目了然了,实例对象animal直接替换了构造函数Person的原型,以此类推,这样就会形成一个链式结构的原型链。完整的原型链结合上图,我们发现,最初的构造函数Animal创建的同时,会创建出一个原型,此时的原型是一个空的对象。结合原型链的概念:“原型本身又是一个对象,所以原型也有原型”,那么这个空对象往上还能找出它的原型或者构造函数吗?我们如何创建一个空对象? 1、字面量:{};2、构造函数:new Object()。我们可以简单的理解为,这个空的对象就是,构造函数Object的实例对象。所以,这个空对象往上面找是能找到它的原型和构造函数的。// 创建一个 Animal 构造函数function Animal() { this.weight = 50; this.eat = function() { console.log(‘蜂蜜蜂蜜’); }}// 实例化一个 animal 对象var animal = new Animal();console.log(animal.proto); // {}console.log(animal.proto.proto); // {}console.log(animal.proto.proto.constructor); // function Object(){}console.log(animal.proto.proto.proto); // null如图所示:2.2 原型链的拓展1、描述出数组[]的原型链结构// 创建一个数组var arr = new Array();// 我们可以看到这个数组是构造函数 Array 的实例对象,所以他的原型应该是:console.log(Array.prototype); // 打印出来还是一个空数组// 我们可以继续往上找 console.log(Array.prototype.proto); // 空对象// 继续console.log(Array.prototype.proto.proto) // null如图所示:2、扩展内置对象给js原有的内置对象,添加新的功能。注意:这里不能直接给内置对象的原型添加方法,因为在开发的时候,大家都会使用到这些内置对象,假如大家都是给内置对象的原型添加方法,就会出现问题。错误的做法:// 第一个开发人员给 Array 原型添加了一个 say 方法Array.prototype.say = function(){ console.log(‘哈哈哈’);}// 第二个开发人员也给 Array 原型添加了一个 say 方法Array.prototype.say = function(){ console.log(‘啪啪啪’);}var arr = new Array();arr.say(); // 打印 “啪啪啪” 前面写的会被覆盖为了避免出现这样的问题,只需自己定义一个构造函数,并且让这个构造函数继承数组的方法即可,再去添加新的方法。// 创建一个数组对象 这个数组对象继承了所有数组中的方法var arr = new Array();// 创建一个属于自己的构造函数function MyArray(){}// 只需要将自己创建的构造函数的原型替换成 数组对象,就能继承数组的所有方法MyArray.prototype = arr;// 现在可以单独的给自己创建的构造函数的原型添加自己的方法MyArray.prototype.say = function(){ console.log(‘这是我自己添加的say方法’);}var arr1 = new MyArray();arr1.push(1); // 创建的 arr1 对象可以使用数组的方法arr1.say(); // 也可以使用自己添加的方法 打印“这是我自己添加的say方法”console.log(arr1); // [1]2.3 属性的搜索原则当通过对象名.属性名获取属性是 ,会遵循以下属性搜索的原则:1-首先去对象自身属性中找,如果找到直接使用,2-如果没找到去自己的原型中找,如果找到直接使用,3-如果没找到,去原型的原型中继续找,找到直接使用,4-如果没有会沿着原型不断向上查找,直到找到null为止。 ...

November 30, 2018 · 4 min · jiezi

从一道题解读JS原型链

之前对js原型和原型链的理解一直觉得很绕,绕来绕去的,在看了《JavaScript高级程序设计》和各种文章之后,终于对原型和原型链有了初步的了解,可是还是没有很深入的了解,今次通过以前段时间遇到的一道题,分析一下,用自己的想法进行解读,加深自己对原型和原型链的理解。一、题目下面程序运行结果是什么?function Animal() { this.name = ‘Animal’;}Animal.prototype.changeName = function (name) { this.name = name;}function Cat() { this.name = ‘Cat’;}var animal = new Animal();Cat.prototype = animal;Cat.prototype.constructor = Cat;var cat = new Cat();animal.changeName(‘Tiger’);console.log(cat.name)A. AnimalB. Cat C. Tiger D. 都不是答案是 B Cat二、解读1. 原型对象无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。下面用图来说明function Animal() { this.name = ‘Animal’;}Animal.prototype.changeName = function (name) { this.name = name;}首先创建了一个Animal函数,Animal中含有一个prototype属性,指向Animal Prototype,而Animal.prototype.constructor指向Animal。这个时候由于name属性是在函数中定义的,所以不在Animal Prototype中,而changeName 函数是通过Animal.prototype.changeName定义的,所以我们可以通过这种方式,在实例化多个对象时,共享原型所保存的方法。同理,当创建了Cat函数时,也是一样。function Cat() { this.name = ‘Cat’;}2. 创建实例当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。在ECMA-262第5版中管这个指针叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性__proto__。明确重要的一点,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。// 将Cat的原型对象指向animal实例,获得animal中的属性,原有的属性丢失Cat.prototype = animal;这一部分相当于是把Cat的原型对象的指针指向了animal实例,所以原来Cat原型对象中的constructor属性丢失,替换成了animal实例中的属性,包括name属性以及__proto__内部属性,同时__proto__属性也指向Animal.prototype,因此Cat也可以通过原型链查找调用到Animal中的属性和方法。// 相当于重新创建了constructor,指向Cat构造函数Cat.prototype.constructor = Cat;这一部分相当于是重新在原型对象中创建了一个constructor属性,同时指向Cat构造函数。var cat = new Cat(); // 实例化一个Cat对象,跟实例化Animal相似3. 调用方法animal.changeName(‘Tiger’);当var animal = new Animal();实例化了一个Animal对象后,animal都包含一个内部属性,该属性指向了Animal.prototype;换句话说,animal与构造函数Animal没有直接的关系。可是,可以看到虽然在实例中不含changeName,但我们却可以调用animal.changeName(name),这是通过查找对象属性的过程来实现的,即:首先查找实例中实例中animal是否有changeName方法,如果没有则继续寻找,去到Animal.prototype寻找是否有changeName方法,如果有则调用,没有则继续寻找,到Object.prototype中寻找,最后没找到则会返回一个null。很明显,在这里实例animal中没有changeName方法,所以需要到Animal.prototype寻找changeName方法,并调用成功修改了实例animal中的name属性,为Tiger。这个时候由于Cat.prototype是指向实例animal的,因此Cat.prototype中的name属性也变为Tiger。console.log(cat.name) // Cat最后,获取cat.name,与查找方法同样,也是先去实例中cat查找是否含有name属性,在这里很明显是存在的,因此直接结束寻找,此时cat.name = ‘Cat’。三、总结通过这道题,加深了我对原型和原型链的理解,其实这道题也可以扩展到关于继承的知识点,在JavaScript中实现继承主要是依靠原型链来实现。之后等我再搞的更清楚一点再继续写吧。此文章是本人自己对于原型和原型链的一点小小的理解,中间可能存在偏差或者错误的,请多多指点!!! ...

October 19, 2018 · 1 min · jiezi