关于prototype:讲解电脑文档表格联系人怎么一键导入手机通讯录

手机是人们日常沟通罕用的工具,所以天然就要用到手机外面的通讯录。因而咱们常要把他人的号码存入到手机通讯录外面,如果只是存五个十个那就动动手指就能够了。然而如果你想存几百个、几千个、几万个等数量级别大的时候,显然手动一个个来存入是不事实的。那么能够借助网上常见的便捷软件,金芝号码提取导入助手,代替你手动工作来疾速实现这个工作,批量存入,省事省时省力。上面做个操作过程的图文解说。 首先:你要筹备好你要存入的号码,根本的就是人铭和号码了。如果你没有人铭,只有号码也能够。那就用号码代替作为人铭,号码还是号码,也就是号码用两次。阐明:我下图的资料是为了演示过程,虚构进去的,并非实在资料,然而能让过程更直观。 接着:把你的人铭复制到软件“金芝号码提取导入助手”的第一个方框里,把你的号码复制到软件的第二个方框里。如果只有号码,没有铭字,那就把号码多复制一次放到第一个框。点下方的“转换通信禄”,就能够把你的资料转换成一个文件,把这个文件存到电脑桌面去,这样不便找和传输。 再接着:把方才电脑桌面上的文件,传输到手机上,传输的形式就是咱们平时用的办法,通过电脑某信或电脑某扣发送给你的手机某信或者手机某扣,这个过程是网络传输的,不须要把数据线连贯手机和电脑,不便多了。 最初:在手机上关上方才电脑上发过来的文件,顺着手机的提醒操作就能够,抉择“其余利用形式关上”。安卓手机就选“分割仁”或者“电括本”等形式,导入确定。苹果手机,选上面的“拷贝到通信禄”,存储,即可。而后稍等半分钟,去手机上看看就进去了。 面对着手里的一大堆号码,通过借助网上常见的便当软件来辅助本人是个聪慧的做法,金芝号码提取导入助手,能批量把电脑文档表格联系人一键导入手机通讯录,是你日常做这项工作的好办法。安卓手机和苹果iphone手机都能够采纳这个形式导入号码。

March 6, 2022 · 1 min · jiezi

ES5-的构造函数原型链继承

构造函数构造函数,就是专门用来生成实例对象的函数。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。 function Person(name){ this.name = name;}为了与普通函数区别,构造函数名字的第一个字母通常大写。 构造函数的特点有两个: 函数体内部使用了 this 关键字,代表了所要生成的对象实例。生成对象的时候,必须使用 new 命令。new 命令基本用法new 命令的作用,就是执行构造函数,返回一个实例对象。 let a = new Person('dora');a.name // doranew 命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号,但为了表明是函数调用,推荐使用括号表示更明确的语义。 new 命令的原理使用 new 命令时,它后面的函数依次执行下面的步骤: 创建一个空对象,作为将要返回的对象实例。let o = new Object();将这个空对象的原型,指向构造函数的prototype属性。 Object.setPrototypeOf(o,Foo.prototype);将构造函数的 this 绑定到新创建的空对象上。Foo.call(o);始执行构造函数内部的代码。如果构造函数内部有 return 语句,而且后面跟着一个对象,则 new 命令会返回 return 语句指定的对象;否则,就会不管 return 语句,返回 this 对象,且不会执行 return 后面的语句。 function Person(name) { this.name = name; if (name == undefined) { return {}; }else if(typeof name != 'string'){ return '姓名有误'; } console.log(111);}new Person(); // {}new Person(123); // {name: 123}new Person('dora'); // {name:'dora'}// 111new.target如果当前函数是 new 命令调用的,在函数内部的 new.target 属性指向当前函数,否则为 undefined。 ...

October 9, 2019 · 2 min · jiezi

fn1callcallfn2

描述function fn1(){ console.log(1);}function fn2(){ console.log(2);}fn1.call(fn2); // 输出1fn1.call.call(fn2); // 输出2问题看到这个题目,第一反应是蒙圈的。 fn1.call(fn2); 这个是理解的。fn1.call.call(fn2);这个蒙圈了。 理解有些绕,需要多念叨念叨琢磨琢磨。 call 方法是Function.prototype原型上天生自带的方法,所有的函数都可以调用的。 我觉得 call方法本身没有具体return什么出来,所以是undefined。 Function.prototype.call=function call(context){ // [native code] // call方法的功能 // 1. 把指定函数中的this指向context // 2. 把指定函数执行 // 那么call方法中的this,即为指定函数。也就是说 // 1. 把this中 的 this关键字指向context; // 2. 把指定函数执行this();};fn1.call(fn2);按照上面的理解 call 方法中的this是fn1把call方法中的this(fn1)中的this指向fn2调用 call方法中的this所以调用的是 fn1 ,此时fn1中的 this 指向的是 fn2。但是这个方法里面并没有使用this,而是直接输出了1。 fn1.call.call(fn2);按照上面的理解 call 方法中的 this 是 fn1.call【所有函数都可以调用call,调用的是原型上call方法】把call方法中的this (fn1.call) 中的this 指向fn2调用call方法中的this所以调用的是 fn2(这里有些绕,多念叨念叨琢磨琢磨),此时fn1.call中的this指向的是fn2。它改变了call方法(Function.prototype原型上的call)的this指向。此处调用了call方法中的this,即调用了fn2,输出了2。

July 3, 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

详解ES6中的class基本概念

用构造函数,生成对象实例: 使用构造函数,并且new 构造函数(), 后台会隐式执行new Object() 创建对象将构造函数的作用域给新对象,(即new Object() 创建出的对象),函数体内的this代表new Object() 出来的对象执行构造函数的代码返回新对象( 后台直接返回)function Person1(name, age) { this.name = name this.age = age}Person1.prototype.say = function () { return "My name is " + this.name + ", I'm " + this.age + " years old."}var obj = new Person1("Simon", 28);console.log(obj.say()); // My name is Simon, I'm 28 years old.用class改写上述代码: 通过class关键字定义类,使得在对象写法上更清晰,让javascript更像一种面向对象的语言在类中声明方法的时,不可给方法加function关键字class Person2 { // 用constructor构造方法接收参数 constructor(name, age) { this.name = name; // this代表的是实例对象 this.age = age; } // 类的方法,此处不能加function say() { return "My name is " + this.name + ", I'm " + this.age + " years old." }}var obj = new Person2("Coco", 26);console.log(obj.say()); // My name is Coco, I'm 26 years old.ES6中的类,实质上就是一个函数类自身指向的就是构造函数类其实就是构造函数的另外一种写法console.log(typeof Person2); // functionconsole.log(Person1 === Person1.prototype.constructor); // trueconsole.log(Person2 === Person2.prototype.constructor); // true构造函数的prototype属性,在ES6的class中依然存在:// 构造1个与类同名的方法 -> 成功实现覆盖Person2.prototype.say = function () { return "证明一下:My name is " + this.name + ", I'm " + this.age + " years old."}var obj = new Person2("Coco", 26);console.log(obj.say()); // 证明一下:My name is Coco, I'm 26 years old.// 通过prototype属性对类添加方法Person2.prototype.addFn = function () { return "通过prototype新增加的方法addFn"}var obj = new Person2("Coco", 26);console.log(obj.addFn()); // 通过prototype新增加的方法addFn通过Object.assign方法来为对象动态增加方法:Object.assign(Person2.prototype, { getName: function () { return this.name; }, getAge: function () { return this.age; }})var obj = new Person2("Coco", 26);console.log(obj.getName()); // Cococonsole.log(obj.getAge()); // 26constructor方法是类的构造函数的默认方法new生成对象实例时,自动调用该方法class Box { constructor() { console.log("自动调用constructor方法"); // 实例化对象时,该行代码自动执行 }}var obj = new Box();若没有定义constructor方法,将隐式生成一个constructor方法: ...

May 30, 2019 · 2 min · jiezi

javascript原型链及继承的理解

原型链及继承的理解定义函数function A(name) { // 构造内容(构造函数) this.name = name; /* // 也支持定义方法。但为了性能,不建议在构造里定义方法 this.fn = function(parmas){ // your code } */}// 原型链A.prototype.changeName = function(name) { this.name = name;}// 静态属性A.staticPF = ‘static12345’;继承继承构造函数function B(name) { // B 继承 A 的构造函数 也可用apply A.call(this, name);}// console.log(B)var b = new B(‘ccc’);console.log(b.name); // => ccc继承静态属性// B 继承 A 的静态属性,查找流程如下// B.staticPF = >B.proto.staticPF => A.staticPF;B.proto = A;console.log(B.staticPF); // => static12345继承原型链var b = new B(‘ccc’);// B 继承 A 的原型链, 3种方式,推荐第3种// B.prototype = A.prototype; // 需要前置在b对象实例化前// b.proto = A.prototype;// B.prototype.proto = A.prototype;B.prototype.proto = A.prototype;b.changeName(‘ddd’) // b实例最终继续了A的changeName方法console.log(b.name); // => ddd ...

April 3, 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

装饰器与元数据反射(2)属与类性装饰器

上一篇文章中,我们讨论了TypeScript源码中关于方法装饰器的实现,搞明白了如下几个问题:装饰器函数是如何被调用的?装饰器函数参数是如何传入的?__decorate函数干了些什么事情?接下来我们继续属性装饰器的观察。属性装饰器属性装饰器的声明标识如下:declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;如下我们为一个类的属性添加了一个名为@logProperty的装饰器class Person { @logProperty public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; }}上一篇解释过,当这段代码最后被编译成JavaScript执行时,方法__decorate会被调用,但此处会少最后一个参数(通过Object. getOwnPropertyDescriptor属性描述符)var Person = (function () { function Person(name, surname) { this.name = name; this.surname = surname; } __decorate( [logProperty], Person.prototype, “name” ); return Person;})();需要注意的是,这次TypeScript编译器并没像方法装饰器那样,使用__decorate返回的结果覆盖原始属性。原因是属性装饰器并不需要返回什么。Object.defineProperty(C.prototype, “foo”, __decorate( [log], C.prototype, “foo”, Object.getOwnPropertyDescriptor(C.prototype, “foo”) ));那么,接下来具体实现这个@logProperty装饰器function logProperty(target: any, key: string) { // 属性值 var _val = this[key]; // getter var getter = function () { console.log(Get: ${key} =&gt; ${_val}); return _val; }; // setter var setter = function (newVal) { console.log(Set: ${key} =&gt; ${newVal}); _val = newVal; }; // 删除属性 if (delete this[key]) { // 创建新的属性 Object.defineProperty(target, key, { get: getter, set: setter, enumerable: true, configurable: true }); }}实现过程首先声明了一个变量_val,并用所装饰的属性值给它赋值(此处的this指向类的原型,key为属性的名字)。接着声明了两个方法getter和setter,由于函数是闭包创建的,所以在其中可以访问变量_val,在其中可以添加额外的自定义行为,这里添加了将属性值打印在控制台的操作。然后使用delete操作符将原属性从类的原型中删除,不过需要注意的是:如果属性存在不可配置的属性时,这里if(delete this[key])会返回false。而当属性被成功删除,方法Object.defineProperty()将创建一个和原属性同名的属性,不同的是新的属性getter和setter方法,使用上面新创建的。至此,属性装饰器的实现就完成了,运行结果如下:var me = new Person(“Remo”, “Jansen”); // Set: name => Remome.name = “Remo H.”; // Set: name => Remo H.name;// Get: name Remo H.类装饰器类装饰器的声明标识如下:declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;可以像如下方式使用类装饰器:@logClassclass Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; }}和之前不同的是,经过TypeScript编译器编译为JavaScript后,调用__decorate函数时,与方法装饰器相比少了后两个参数。仅传递了Person而非Person.prototype。var Person = (function () { function Person(name, surname) { this.name = name; this.surname = surname; } Person = __decorate( [logClass], Person ); return Person;})();值得注意的是,__decorate的返回值复写了原始的构造函数,原因是类装饰器必须返回一个构造器函数。接下来我们就来实现上面用到的类装饰器@logClass:function logClass(target: any) { // 保存对原始构造函数的引用 var original = target; // 用来生成类实例的方法 function construct(constructor, args) { var c : any = function () { return constructor.apply(this, args); } c.prototype = constructor.prototype; return new c(); } // 新的构造函数 var f : any = function (…args) { console.log(“New: " + original.name); return construct(original, args); } // 复制原型以便intanceof操作符可以使用 f.prototype = original.prototype; // 返回新的构造函数(会覆盖原有构造函数) return f;}这里实现的构造器中,声明了一个名为original的变量,并将所装饰类的构造函数赋值给它。接着声明一个工具函数construct,用来创建类的实例。然后定义新的构造函数f,在其中调用原来的构造函数并将初始化的类名打印在控制台,当然我们也可以添加一些其他自定义的行为。原始构造函数的原型被复制给f的原型,以确保在创建一个Person的新实例时,instanceof操作符如愿以偿,具体原因可参考鄙人另一篇文章原型与对象。至此类装饰器的实现就完成了,可以验证下:var me = new Person(“Remo”, “Jansen”); // New: Personme instanceof Person; // true ...

February 2, 2019 · 2 min · jiezi

Node.js new 、 prototype 与 __proto__

一、构造一个Person对象(相当于Java中的有参构造函数)function person(name, sex, age, addr, salary) { this.name = name; this.sex = sex; this.age = age; this.addr = addr; this.salary = salary;}二、对象实例隐式传递this指针person.prototype.func_pro=function () { console.log(this);};let Sakura =new person(“Sakura”,“女”,16,“FateStayNight”,10000000000);let Illyasviel= new person(“Illyasviel “,“女”,14,“FateStayNight”,9999999999);Sakura.func_pro();Illyasviel.func_pro();console.log(”——————————————————-” + “\n\n”);三、new 与 prototype1、总结:console.log(“new 与 prototype”);//1、let variable ={};//2、nodejs中每个对象都有一个__proto__属性// 建立两个对象之间的关联:// 一个对象可以使用__proto__关联另外一个对象// proto(对象的内部原型的引用): prototype(对象的原型) 浅拷贝// __proto__与prototype指向同一个对象的引用//3、 对象实例作为this指针的指向 传递给后面的函数//4、 调用这个函数2、可以通过prototype.key=value 来扩充对象Elffunction Elf(name) { this.name =name; console.log(“Elf\t”+name);}console.log(“可以通过prototype.key=value 来扩充对象Elf”);Elf.prototype.love=function () { console.log("%s love ‘DATE A LIVE!’", this.name);};let Yuzuru = new Elf(“Yuzuru”);let Kaguya = new Elf(“Kaguya”);Yuzuru.love();Kaguya.love();console.log("——————————————————-" + “\n\n”);3、 实例.proto 与 方法.prototype指向 同一个对象的引用console.log(“实例.proto 与 方法.prototype指向 同一个对象的引用”);console.log(Yuzuru.proto);console.log(Elf.prototype);console.log("——————————————————-" + “\n\n”);let func_data =function(){ console.log(“func_data”);};func_data.prototype.func_test=function(){ console.log(“func_test”,this);};// 实例.proto 与 方法.prototype指向 同一个对象的引用console.log(“实例.proto 与 方法.prototype指向 同一个对象的引用”);console.log(Yuzuru.proto);console.log(Elf.prototype);console.log("——————————————————-" + “\n\n”);let func_data =function(){ console.log(“func_data”);};func_data.prototype.func_test=function(){ console.log(“func_test”,this);};4、实例.proto 与 方法.prototype 分别属于2个不同的字典表{}console.log(“实例.proto 与 方法.prototype指向 分别属于2个不同的字典表{}”);let data =new func_data();data.name=“Innocence”;data.proto.func_test();data.func_test();console.log("——————————————————-" + “\n\n”);5、可以将对象实例看做为一张字典表//可以将data看做1张表console.log(“可以将data看做1张表”);//隐式调用thisdata.func_test();//显示调用this 将data作为this传递给data._proto_对象里的函数test_funcdata.proto.func_test.call(data);console.log("——————————————————-" + “\n\n”);6、调用取值的顺序data.func_test=function () { console.log(“new func_test”,this);};data.func_test();//data.key_func 首先会到对象实例的表里搜索是否有没有这样的key 若没有再到其__proto__里面搜索 ...

January 17, 2019 · 1 min · jiezi

JS 总结之原型

在 JavaScript 中,函数也是属于对象,可以像其他对象一样访问属性,其中便有一个属性叫原型 prototype,值为一个对象,默认,原型有一个 constructor 属性,指向了构造函数本身。function Person () { return ‘hi’}Person.prototype // {constructor: ƒ}Person.prototype.constructor // ƒ Person() {}用图来表示:我们可以对这个属性进行操作,但这个属性,只有在对函数进行构造调用的时候才会起作用???? 为原型添加属性和方法function Person (name) { this.name = name}Person.prototype.smart = truePerson.prototype.run = function () { console.log(‘running’)}// 或者一次性添加Person.prototype = { smart: true, run() { console.log(‘running’) }}???? 使用// …let a = new Person(‘a’)a.name // aa.smart // truea.run() // running???? instanceof用来检测构造函数的原型是否存在于实例的原型链上// …let b = new Person(‘b’)b instanceof Person // trueb instanceof Object // true???? hasOwnPrototype该方法来判断是否自身属性,如:function Person () { this.name = ‘Jon’}Person.prototype.name = ‘people’Person.prototype.age = 18let jon = new Person()jon.hasOwnProperty(’name’) // truejon.hasOwnProperty(‘age’) // falseage 为原型上的属性,所以为 false???? isPrototypeOf该方法来判断对象是否是另一个对象的原型,如:let base = { name: ‘people’, age: 18}function Person () { this.name = ‘Jon’}Person.prototype = baselet jon = new Person()base.isPrototypeOf(jon) // true???? getPrototypeOf当不知道对象的原型具体是哪个的时候,可以使用该方法来判断,如:let base = { name: ‘people’, age: 18}function Person () { this.name = ‘Jon’}Person.prototype = baselet jon = new Person()Object.getPrototypeOf(jon) // { name: ‘people’, age: 18 }???? _ proto 引用《MDN _ proto _ 》 的解释:Object.prototype 的 _ proto _ 属性是一个访问器属性(一个 getter 函数和一个 setter 函数), 暴露了通过它访问的对象的内部原型 (一个对象或 null)。也就是说,每个对象都有一个该属性,便携访问原型对象,直指原型对象:let base = { name: ‘people’, age: 18}function Person () { this.name = ‘Jon’}Person.prototype = baselet jon = new Person()jon.proto // { name: ‘people’, age: 18 }用图来表示:与 prototype 不同的是, proto _ 是对象的属性,prototype 是构造函数的属性????【ES5】Object.create(..)该方法创建一个新对象,使用现有的对象来提供新创建的对象的 _ proto :let base = { name: ‘people’, age: 18}let jon = Object.create(base)jon.proto // { name: ‘people’, age: 18 }???? 原型链当访问对象的一个属性时,js 引擎会遍历自身对象的属性,如果找不到,便会去原型上查找该属性,如果还是找不到,便会继续查找原型的属性,直到到 Object 原型由于原型是一个对象,是对象便会有一个原型,有原型说明存在构造函数,如 Person 例子,查看原型的构造函数是啥:// …Person.prototype.proto.constructor // ƒ Object() { [native code] }Person.prototype.proto.constructor === Object // true说明 Object 是 Person 原型 的构造函数说明 Person 原型 的 _ proto _ 会指向 Object.prototype,因为 _ proto _ 能快捷访问原型:// …Object.getPrototypeOf(jon.proto) === Object.prototype // true// 或者jon.proto.proto === Object.prototype // true// 或者Person.prototype.proto === Object.prototype // true用图表示就是:举个例子:// …let a = new Person(‘a’)a.toString() // “[object Object]“Person 函数和原型上都没有 toString 方法,所以只能调用 Object 上的 toString 方法。注意:基于同一个构造函数生成的对象,共享函数的原型,如:// …let b = new Person(‘b’)b.name // bb.smart = falseb.smart // falsea.smart // falseb.proto === a.proto // true对 b 的 smart 属性进行修改,a 访问 smart 也有原先的 true 变为 false 了。???? Object.prototypeObject.prototype.proto // nullnull 是什么意思?此处无对象的意思,说明 Object.prototype 没有原型,查到这里就停止查找了,如果在找不到目标属性,就返回 undefined。???? 自身属性优先如果自身和原型上存在同名属性,会优先使用自身属性,例如:function Person () { this.name = ‘Jon’}Person.prototype.name = ‘people’let jon = new Person()jon.name // Jon???? 总结自:《JavaScript 深入之从原型到原型链》 by 冴羽《Object.create()》 by MDN《 proto _ 》 by MDN《JavaScript 面向对象编程指南(第 2 版)》第 5 章 原型《Node.js 开发指南》附录 A进击的小本本 更新时间为:周二,周四,周六,内容不定。如想查看最新未完成总结,可请查看:https://github.com/KaronAmI/blog如有启发,欢迎 star,如有错误,请务必指出,十分感谢。???? ...

December 22, 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

ES6 系列之我们来聊聊装饰器

Decorator装饰器主要用于:装饰类装饰方法或属性装饰类@annotationclass MyClass { }function annotation(target) { target.annotated = true;}装饰方法或属性class MyClass { @readonly method() { }}function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}Babel安装编译我们可以在 Babel 官网的 Try it out,查看 Babel 编译后的代码。不过我们也可以选择本地编译:npm initnpm install –save-dev @babel/core @babel/clinpm install –save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties新建 .babelrc 文件{ “plugins”: [ ["@babel/plugin-proposal-decorators", { “legacy”: true }], ["@babel/plugin-proposal-class-properties", {“loose”: true}] ]}再编译指定的文件babel decorator.js –out-file decorator-compiled.js装饰类的编译编译前:@annotationclass MyClass { }function annotation(target) { target.annotated = true;}编译后:var _class;let MyClass = annotation(_class = class MyClass {}) || _class;function annotation(target) { target.annotated = true;}我们可以看到对于类的装饰,其原理就是:@decoratorclass A {}// 等同于class A {}A = decorator(A) || A;装饰方法的编译编译前:class MyClass { @unenumerable @readonly method() { }}function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}function unenumerable(target, name, descriptor) { descriptor.enumerable = false; return descriptor;}编译后:var _class;function _applyDecoratedDescriptor(target, property, decorators, descriptor, context ) { /** * 第一部分 * 拷贝属性 / var desc = {}; Object“ke” + “ys”.forEach(function(key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if (“value” in desc || desc.initializer) { desc.writable = true; } /* * 第二部分 * 应用多个 decorators / desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc); /* * 第三部分 * 设置要 decorators 的属性 / if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object[“define” + “Property”](target, property, desc); desc = null; } return desc;}let MyClass = ((_class = class MyClass { method() {}}),_applyDecoratedDescriptor( _class.prototype, “method”, [readonly], Object.getOwnPropertyDescriptor(_class.prototype, “method”), _class.prototype),_class);function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}装饰方法的编译源码解析我们可以看到 Babel 构建了一个 _applyDecoratedDescriptor 函数,用于给方法装饰。Object.getOwnPropertyDescriptor()在传入参数的时候,我们使用了一个 Object.getOwnPropertyDescriptor() 方法,我们来看下这个方法:Object.getOwnPropertyDescriptor() 方法返回指定对象上的一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)顺便注意这是一个 ES5 的方法。举个例子:const foo = { value: 1 };const bar = Object.getOwnPropertyDescriptor(foo, “value”);// bar {// value: 1,// writable: true// enumerable: true,// configurable: true,// }const foo = { get value() { return 1; } };const bar = Object.getOwnPropertyDescriptor(foo, “value”);// bar {// get: /the getter function/,// set: undefined// enumerable: true,// configurable: true,// }第一部分源码解析在 _applyDecoratedDescriptor 函数内部,我们首先将 Object.getOwnPropertyDescriptor() 返回的属性描述符对象做了一份拷贝:// 拷贝一份 descriptorvar desc = {};Object“ke” + “ys”.forEach(function(key) { desc[key] = descriptor[key];});desc.enumerable = !!desc.enumerable;desc.configurable = !!desc.configurable;// 如果没有 value 属性或者没有 initializer 属性,表明是 getter 和 setterif (“value” in desc || desc.initializer) { desc.writable = true;}那么 initializer 属性是什么呢?Object.getOwnPropertyDescriptor() 返回的对象并不具有这个属性呀,确实,这是 Babel 的 Class 为了与 decorator 配合而产生的一个属性,比如说对于下面这种代码:class MyClass { @readonly born = Date.now();}function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}var foo = new MyClass();console.log(foo.born);Babel 就会编译为:// …(_descriptor = _applyDecoratedDescriptor(_class.prototype, “born”, [readonly], { configurable: true, enumerable: true, writable: true, initializer: function() { return Date.now(); }}))// …此时传入 _applyDecoratedDescriptor 函数的 descriptor 就具有 initializer 属性。第二部分源码解析接下是应用多个 decorators:/* * 第二部分 * @type {[type]} /desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc);对于一个方法应用了多个 decorator,比如:class MyClass { @unenumerable @readonly method() { }}Babel 会编译为:_applyDecoratedDescriptor( _class.prototype, “method”, [unenumerable, readonly], Object.getOwnPropertyDescriptor(_class.prototype, “method”), _class.prototype)在第二部分的源码中,执行了 reverse() 和 reduce() 操作,由此我们也可以发现,如果同一个方法有多个装饰器,会由内向外执行。第三部分源码解析/* * 第三部分 * 设置要 decorators 的属性 /if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined;}if (desc.initializer === void 0) { Object[“define” + “Property”](target, property, desc); desc = null;}return desc;如果 desc 有 initializer 属性,意味着当装饰的是类的属性时,会将 value 的值设置为:desc.initializer.call(context)而 context 的值为 _class.prototype,之所以要 call(context),这也很好理解,因为有可能class MyClass { @readonly value = this.getNum() + 1; getNum() { return 1; }}最后无论是装饰方法还是属性,都会执行:Object[“define” + “Property”](target, property, desc);由此可见,装饰方法本质上还是使用 Object.defineProperty() 来实现的。应用1.log为一个方法添加 log 函数,检查输入的参数:class Math { @log add(a, b) { return a + b; }}function log(target, name, descriptor) { var oldValue = descriptor.value; descriptor.value = function(…args) { console.log(Calling ${name} with, args); return oldValue.apply(this, args); }; return descriptor;}const math = new Math();// Calling add with [2, 4]math.add(2, 4);再完善点:let log = (type) => { return (target, name, descriptor) => { const method = descriptor.value; descriptor.value = (…args) => { console.info((${type}) 正在执行: ${name}(${args}) = ?); let ret; try { ret = method.apply(target, args); console.info((${type}) 成功 : ${name}(${args}) =&gt; ${ret}); } catch (error) { console.error((${type}) 失败: ${name}(${args}) =&gt; ${error}); } return ret; } }};2.autobindclass Person { @autobind getPerson() { return this; }}let person = new Person();let { getPerson } = person;getPerson() === person;// true我们很容易想到的一个场景是 React 绑定事件的时候:class Toggle extends React.Component { @autobind handleClick() { console.log(this) } render() { return ( <button onClick={this.handleClick}> button </button> ); }}我们来写这样一个 autobind 函数:const { defineProperty, getPrototypeOf} = Object;function bind(fn, context) { if (fn.bind) { return fn.bind(context); } else { return function autobind() { return fn.apply(context, arguments); }; }}function createDefaultSetter(key) { return function set(newValue) { Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: true, value: newValue }); return newValue; };}function autobind(target, key, { value: fn, configurable, enumerable }) { if (typeof fn !== ‘function’) { throw new SyntaxError(@autobind can only be used on functions, not: ${fn}); } const { constructor } = target; return { configurable, enumerable, get() { /* * 使用这种方式相当于替换了这个函数,所以当比如 * Class.prototype.hasOwnProperty(key) 的时候,为了正确返回 * 所以这里做了 this 的判断 */ if (this === target) { return fn; } const boundFn = bind(fn, this); defineProperty(this, key, { configurable: true, writable: true, enumerable: false, value: boundFn }); return boundFn; }, set: createDefaultSetter(key) };}3.debounce有的时候,我们需要对执行的方法进行防抖处理:class Toggle extends React.Component { @debounce(500, true) handleClick() { console.log(’toggle’) } render() { return ( <button onClick={this.handleClick}> button </button> ); }}我们来实现一下:function _debounce(func, wait, immediate) { var timeout; return function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } }}function debounce(wait, immediate) { return function handleDescriptor(target, key, descriptor) { const callback = descriptor.value; if (typeof callback !== ‘function’) { throw new SyntaxError(‘Only functions can be debounced’); } var fn = _debounce(callback, wait, immediate) return { …descriptor, value() { fn() } }; }}4.time用于统计方法执行的时间:function time(prefix) { let count = 0; return function handleDescriptor(target, key, descriptor) { const fn = descriptor.value; if (prefix == null) { prefix = ${target.constructor.name}.${key}; } if (typeof fn !== ‘function’) { throw new SyntaxError(@time can only be used on functions, not: ${fn}); } return { …descriptor, value() { const label = ${prefix}-${count}; count++; console.time(label); try { return fn.apply(this, arguments); } finally { console.timeEnd(label); } } } }}5.mixin用于将对象的方法混入 Class 中:const SingerMixin = { sing(sound) { alert(sound); }};const FlyMixin = { // All types of property descriptors are supported get speed() {}, fly() {}, land() {}};@mixin(SingerMixin, FlyMixin)class Bird { singMatingCall() { this.sing(’tweet tweet’); }}var bird = new Bird();bird.singMatingCall();// alerts “tweet tweet"mixin 的一个简单实现如下:function mixin(…mixins) { return target => { if (!mixins.length) { throw new SyntaxError(@mixin() class ${target.name} requires at least one mixin as an argument); } for (let i = 0, l = mixins.length; i < l; i++) { const descs = Object.getOwnPropertyDescriptors(mixins[i]); const keys = Object.getOwnPropertyNames(descs); for (let j = 0, k = keys.length; j < k; j++) { const key = keys[j]; if (!target.prototype.hasOwnProperty(key)) { Object.defineProperty(target.prototype, key, descs[key]); } } } };}6.redux实际开发中,React 与 Redux 库结合使用时,常常需要写成下面这样。class MyReactComponent extends React.Component {}export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);有了装饰器,就可以改写上面的代码。@connect(mapStateToProps, mapDispatchToProps)export default class MyReactComponent extends React.Component {};相对来说,后一种写法看上去更容易理解。7.注意以上我们都是用于修饰类方法,我们获取值的方式为:const method = descriptor.value;但是如果我们修饰的是类的实例属性,因为 Babel 的缘故,通过 value 属性并不能获取值,我们可以写成:const value = descriptor.initializer && descriptor.initializer();参考ECMAScript 6 入门core-decoratorsES7 Decorator 装饰者模式JS 装饰器(Decorator)场景实战ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。本文作者:冴羽阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 21, 2018 · 6 min · jiezi

ES6 系列之 Babel 是如何编译 Class 的(下)

前言在上一篇 《 ES6 系列 Babel 是如何编译 Class 的(上)》,我们知道了 Babel 是如何编译 Class 的,这篇我们学习 Babel 是如何用 ES5 实现 Class 的继承。ES5 寄生组合式继承function Parent (name) { this.name = name;}Parent.prototype.getName = function () { console.log(this.name)}function Child (name, age) { Parent.call(this, name); this.age = age;}Child.prototype = Object.create(Parent.prototype);var child1 = new Child(‘kevin’, ‘18’);console.log(child1);原型链示意图为:关于寄生组合式继承我们在 《JavaScript深入之继承的多种方式和优缺点》 中介绍过。引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。ES6 extendClass 通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。以上 ES5 的代码对应到 ES6 就是:class Parent { constructor(name) { this.name = name; }}class Child extends Parent { constructor(name, age) { super(name); // 调用父类的 constructor(name) this.age = age; }}var child1 = new Child(‘kevin’, ‘18’);console.log(child1);值得注意的是:super 关键字表示父类的构造函数,相当于 ES5 的 Parent.call(this)。子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。子类的 proto在 ES6 中,父类的静态方法,可以被子类继承。举个例子:class Foo { static classMethod() { return ‘hello’; }}class Bar extends Foo {}Bar.classMethod(); // ‘hello’这是因为 Class 作为构造函数的语法糖,同时有 prototype 属性和 proto 属性,因此同时存在两条继承链。(1)子类的 proto 属性,表示构造函数的继承,总是指向父类。(2)子类 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的 prototype 属性。class Parent {}class Child extends Parent {}console.log(Child.proto === Parent); // trueconsole.log(Child.prototype.proto === Parent.prototype); // trueES6 的原型链示意图为:我们会发现,相比寄生组合式继承,ES6 的 class 多了一个 Object.setPrototypeOf(Child, Parent)的步骤。继承目标extends 关键字后面可以跟多种类型的值。class B extends A {}上面代码的 A,只要是一个有 prototype 属性的函数,就能被 B 继承。由于函数都有 prototype 属性(除了 Function.prototype 函数),因此 A 可以是任意函数。除了函数之外,A 的值还可以是 null,当 extend null 的时候:class A extends null {}console.log(A.proto === Function.prototype); // trueconsole.log(A.prototype.proto === undefined); // trueBabel 编译那 ES6 的这段代码:class Parent { constructor(name) { this.name = name; }}class Child extends Parent { constructor(name, age) { super(name); // 调用父类的 constructor(name) this.age = age; }}var child1 = new Child(‘kevin’, ‘18’);console.log(child1);Babel 又是如何编译的呢?我们可以在 Babel 官网的 Try it out 中尝试:‘use strict’;function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return call && (typeof call === “object” || typeof call === “function”) ? call : self;}function _inherits(subClass, superClass) { if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.proto = superClass;}function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}var Parent = function Parent(name) { _classCallCheck(this, Parent); this.name = name;};var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 调用父类的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child;}(Parent);var child1 = new Child(‘kevin’, ‘18’);console.log(child1);我们可以看到 Babel 创建了 _inherits 函数帮助实现继承,又创建了 _possibleConstructorReturn 函数帮助确定调用父类构造函数的返回值,我们来细致的看一看代码。_inheritsfunction _inherits(subClass, superClass) { // extend 的继承目标必须是函数或者是 null if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function, not " + typeof superClass); } // 类似于 ES5 的寄生组合式继承,使用 Object.create,设置子类 prototype 属性的 proto 属性指向父类的 prototype 属性 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); // 设置子类的 proto 属性指向父类 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.proto = superClass;}关于 Object.create(),一般我们用的时候会传入一个参数,其实是支持传入两个参数的,第二个参数表示要添加到新创建对象的属性,注意这里是给新创建的对象即返回值添加属性,而不是在新创建对象的原型对象上添加。举个例子:// 创建一个以另一个空对象为原型,且拥有一个属性 p 的对象const o = Object.create({}, { p: { value: 42 } });console.log(o); // {p: 42}console.log(o.p); // 42再完整一点:const o = Object.create({}, { p: { value: 42, enumerable: false, // 该属性不可写 writable: false, configurable: true }});o.p = 24;console.log(o.p); // 42那么对于这段代码:subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });作用就是给 subClass.prototype 添加一个可配置可写不可枚举的 constructor 属性,该属性值为 subClass。_possibleConstructorReturn函数里是这样调用的:var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, name));我们简化为:var _this = _possibleConstructorReturn(this, Parent.call(this, name));_possibleConstructorReturn 的源码为:function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return call && (typeof call === “object” || typeof call === “function”) ? call : self;}在这里我们判断 Parent.call(this, name) 的返回值的类型,咦?这个值还能有很多类型?对于这样一个 class:class Parent { constructor() { this.xxx = xxx; }}Parent.call(this, name) 的值肯定是 undefined。可是如果我们在 constructor 函数中 return 了呢?比如:class Parent { constructor() { return { name: ‘kevin’ } }}我们可以返回各种类型的值,甚至是 null:class Parent { constructor() { return null }}我们接着看这个判断:call && (typeof call === “object” || typeof call === “function”) ? call : self;注意,这句话的意思并不是判断 call 是否存在,如果存在,就执行 (typeof call === “object” || typeof call === “function”) ? call : self因为 && 的运算符优先级高于 ? :,所以这句话的意思应该是:(call && (typeof call === “object” || typeof call === “function”)) ? call : self;对于 Parent.call(this) 的值,如果是 object 类型或者是 function 类型,就返回 Parent.call(this),如果是 null 或者基本类型的值或者是 undefined,都会返回 self 也就是子类的 this。这也是为什么这个函数被命名为 _possibleConstructorReturn。总结var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 调用父类的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child;}(Parent);最后我们总体看下如何实现继承:首先执行 _inherits(Child, Parent),建立 Child 和 Parent 的原型链关系,即 Object.setPrototypeOf(Child.prototype, Parent.prototype) 和 Object.setPrototypeOf(Child, Parent)。然后调用 Parent.call(this, name),根据 Parent 构造函数的返回值类型确定子类构造函数 this 的初始值 _this。最终,根据子类构造函数,修改 _this 的值,然后返回该值。ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。本文作者:冴羽阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 16, 2018 · 4 min · jiezi