关于class:解析Class文件示例

筹备class文件是Java虚拟机惟一能够辨认的文件,依据Class文件咱们能够实现一个程序的运行,本节文章是自己解析一个根本Class文件的全过程,记录在此,心愿能提供给正在后退路上的同学作为辅助作用,上面是咱们要解析Class文件必要条件。 代码以下是一个简略的Java类,一个公有变量,一个公开办法。 public class TestClass { private int a; public int increment() { return a + 1; }}将这个类编译为Class文件 筹备文本编辑器自己应用Sublime Text关上咱们编译后的Class文件,次要目标是对照《Java虚拟机标准》中的Class文件的数据结构,上面是Sublime Text下载的网址。 Sublime Text 应用Sumlime关上Clas文件 《Java虚拟机标准》咱们须要依照Java虚拟机标准中的数据结构,进行Class文件的十六进制码进行解析,所以这里须要依照官网,进行解析,以下是PDF在线链接,本节应用Java8版本。Java虚拟机标准1.8 关上《Java虚拟机标准》 解析咱们须要晓得一个Class文件由哪些数据结构组成,并排列进去,一项数据占几个字节,这些咱们都要晓得。咱们首先须要记录,这个Class文件的大体构造,Class文件的数据结构及程序都是须要严格依照《Java虚拟机标准》生成的,找到《Java虚拟机标准》中的 "The Class File Format (Class文件格式)"中的Class构造项。 须要解释的是,这个ClassFile数据结构中的U2、u4别离代表两个字节、四个字节,其对应的左边的英文项,代表着其数据的常量项,Class文件是肯定依照这个构造进行构件的,《Java虚拟机标准》中也阐明了每一项的形容,自己联合官网整顿如下: 这个是自己整顿的Class文件的数据结构,有且只有这16项,具体的每一项,本文稍后都有解释。其实Class文件并不简单,只是Class中的援用比拟多,例如constant_pool_info中,这是一个常量池,池中记录着类、办法、字面量等描述符,是互相援用的形式。 javap查看class文件具体构造应用javap查看文件构造,次要是为了供咱们于Class二进制形式进行比对时的一个参照物,以阐明咱们解读class文件时得出的论断是正确的。其余咱们须要确定咱们常量池中的程序以及所对应的常量池名。 javap -v TestClass.class输入后果 依据javap所输入的数据结构,咱们失去了大抵的数据结构,剩下的咱们须要自行解析,并比对class中的十六进制数进行填写。这里采纳yaml文本格式进行填充,yaml格局能很直观的展现出一个对象的数据结构,并且采纳这种形式也能够很间接的将解析的值带入。针对咱们要解析的class文件,其中____是咱们要填充的数据,并且也进行了简略的正文阐明字段的含意及所占用的字节。构造如下: class: #魔数 u4 magic-number: ____ #小版本号 u2 minor-version: ____ #大版本号 u2 major-version: ____ #常量池 constant-pool: #常量池总数 u2 count: ____ #常量池(数组) constants: ____ #拜访标记 u2 access_flags: ____ #以后类 u2 this_class: ____ #父类 u2 super_class: ____ #接口 interface: # u2 count: ____ interfaces: #字段 field: # u2 count: ____ fields: ____ #办法 method: # u2 count: ____ methods: ____ #属性 attributes: #u2 count: ____ attributes: ____有了大抵的构造,咱们就能够开始解析之路啦!! ...

April 21, 2023 · 1 min · jiezi

关于class:初始JavaClass文件

class文件的由来Java类文件是蕴含可在Java 虚拟机 (JVM)上执行的Java 字节码的文件(具备.class 文件扩展名)。Java 类文件通常由Java 编译器依据蕴含 Java 类的 Java 编程语言源文件(.java文件)生成(或者,其余JVM 语言也可用于创立类文件)。如果一个源文件有多个类,则每个类都被编译成一个独自的类文件。JVM 可用于许多平台,在一个平台上编译的类文件将在另一个平台的 JVM 上执行。这使得 Java 应用程序与平台无关。 class文件的组成Java类文件构造有10个根本局部 魔数:0XCAFEBABEMinor Version AND Major Version:类文件的主要版本和次要版本constant_pool: 常量池,类的常量池access_flags: 拜访标记,类是否是抽象类、动态类等this_class:以后类的名称super_class:超类的名称interfaces:类中的任何接口fields:类中的任何字段methods:类中的任何办法attributes:类中的任何属性(例如源文件的名称等)魔数类文件是由4字节的十六进制标识的,在class文件中为:CAFE BABE。 这个标识的由来是James Gosing在谈到帕洛阿尔托的一家餐厅时解释了这个神奇数字的历史: "咱们过来常去一个叫圣米迦勒巷的中央吃午饭。依据当地传说,在光明的过来,Grateful Dead(1964年组建的一只美国乐队)在他们成名之前已经在那里上演。这是一个十分时尚的中央,相对是一个 Grateful Dead Kinda Place,Jerry(乐队的主唱)死后,他们甚至建了一个佛教格调的小神社,咱们以前去那里的时候,咱们称这个中央为 Cafe Dead。沿线的某个中央留神到这是一个 HEX 数字。我正在从新批改一些文件格式代码,须要一些神奇的数字:一个用于长久对象文件,一个用于类。我应用 CAFEDEAD 作为对象文件格式,并在grepping对于适宜“CAFE”(这仿佛是一个很好的主题)之后的 4 个字符的十六进制单词,我偶尔发现了 BABE 并决定应用它。在那个时候,除了历史的垃圾桶之外,它仿佛并没有什么特地的重要或注定要去任何中央。所以 CAFEBABE 成为类文件格式,而 CAFEDEAD 成为长久对象格局。然而长久对象工具隐没了,随之而来的是 CAFEDEAD 的应用——它最终被RMI取代。" 总体布局因为类文件蕴含可变大小的我的项目并且不蕴含嵌入式文件偏移量,所以它通常是按程序解析的,从第一个字节到最初。在最低级别,文件格式依据一些根本数据类型进行形容: U1: 一个无符号的8位整数U2: 无符号16 位整数,大端字节程序u4: 一个无符号的32 位整数,大端字节程序table: 某种类型的可变长度我的项目的数组。表中的项目数由后面的计数编号标识(计数为 u2),但表的字节大小只能通过查看其每个我的项目来确定。而后依据上下文将其中一些根本类型从新解释为更高级别的值(例如字符串或浮点数)。没有强制执行字对齐,因而从未应用过填充字节。类文件的整体布局如下表所示。待更新。。。 扩大常识Grateful Dead

April 14, 2023 · 1 min · jiezi

关于class:探索-Class-底层原理

ECMAScript6 实现了 class ,实际上它是一个语法糖,然而它的呈现能使 JS 编码更清晰,更靠近 面向对象编程。 实现原理首先咱们来看 ES6 中 class 的实现和 ES5 构造函数的实现,两者相比拟不难看出 constructor 其实就是构造方法,指向 ES5 的构造函数,那么 class 自身指向的是构造函数,换言之底层仍旧是构造函数。 ES6class Person { constructor(name, age) { this.name = name; this.age = age; } static run() { console.log("run"); } say() { console.log("hello!"); }}ES5function Person(name, age) { this.name = name; this.age = age;}Person.prototype.say = function () { console.log("hello!");};Person.run = function () { console.log("run");};babel 编译剖析通过 babel 编译器将 ES6 代码 转换成 ES5 代码之后(代码转换能够试用 babel 官网在线工具),可失去这两个要害函数 _defineProperties 和 _createClass,当初咱们来一一解析阐明。 ...

May 12, 2021 · 3 min · jiezi

react之如何写一个管理自有状态的自定义组件

一、函数组件函数组件类似一个纯函数,接受外部传入的参数,生成并返回一个React元素(伪DOM)。例如,如下,Greeting作为一个组件,接受传入的参数name,并返回一个内容已填充的p标签。 function Greeting (props) { return ( <p> {props.name},how are you? </p> )}const element = <Greeting name="Agnes" />ReactDOM.render( element, document.getElementById('root'))二、class组件react中class组件的书写方式跟es6中类的书写方式非常接近,可以通过React.Compnent进行创建。与函数组件不同的是,该组件可以进行复杂逻辑的处理,既可以接受外部参数,也可以拥有自己的state,用于组件内的通信。 class HighGreeting extends React.Component { constructor(props){ super(props); this.state={ inputValue: this.props.name } this.handleInputChange = this.handleInputChange.bind(this); } render () { return ( <input type="text" onChange="handleInputChange"/> <p>{this.state.inputValue},how are you?</p> ) } handleInputChange(e){ let value = e.target.value; this.setState({ inputValue: value }) }} const element = <HighGreeting name="Agnes" />ReactDOM.render( element, document.getElementById('root'))上面的组件,接收props参数作为初始值,当用户输入时,会实时更新。 每次定义子类的构造函数时,都需要调用super方法,因此所有含有构造函数的React组件中,构造函数必须以super开头。调用super相当于初始化this.props。class组件内部可以定义state,相当于vue组件内的data,更改时需要调用this.setState,每次调用该方法时,都会执行render方法,自动更新组件。如上图,监听input的onchange事件,实时更改inputValue的值并展示。需要注意的是,props不仅可以传递值,还可以传递函数,甚至传递另一个组件。(这一点跟vue不一样,后面的文章会再细讲。)

July 10, 2019 · 1 min · jiezi

乐字节Java反射之一反射概念与获取反射源头class

一、Java反射机制概念“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”,如Python, Ruby是动态语言;显然C++,Java,C#不是动态语言,但是JAVA有着一个非常突出 的动态相关机制:Reflection。 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方 法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以 及动态调用对象的方法的功能称为java语言的反射机制。如 /** * 入门级示例:通过对象获取 包名.类名 * @author Administrator */public class Simple { public static void main(String[] args) { Simple s=new Simple(); System.out.println(s.getClass().getName()); }}Java反射机制,可以实现以下功能: ①在运行时判断任意一个对象所属的类; ②在运行时构造任意一个类的对象; ③在运行时判断任意一个类所具有的成员变量和方法; ④在运行时调用任意一个对象的方法; ⑤生成动态代理。 相关的api为 二、 获取源头Class(重点)打开权限: add.setAccessible(true); 所有类的对象其实都是Class的实例。这个Class实例可以理解为类的模子,就是包含了类的结构信息,类似于图纸。我们日常生活中,需要创造一个产品,如想山寨一个iphone手机,怎么办? 有三种方式可以实现: ⑴买个iphone手机,拆的七零八落的,开始山寨; ⑵到iphone工厂参观,拿到iphone磨具,开始山寨; ⑶跑到美国盗取iphone的图纸,开始山寨,最后一种最暴力,最爽。 序列化:实现serializable接口, 反序列化 克隆:实现cloneable接口,重写clone()方法,修改权限为public New 反射 同理,获取类的class对象,也有三种方式: ①Class.forName(”包名.类名”)//一般尽量采用该形式 ②类.class ③对象.getClass() 示例如下: public class Source { public static void main(String[] args) { //第一种方式:对象.class Source s=new Source(); Class<?>c1=s.getClass(); //第二种方式:类.class Class<?>c2=Source.class; //第三种方式(推荐方式):Class.forName() Class<?>c3=null; try { c3=Class.forName("com.shsxt.ref.simple.Source"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println(c1.getName()); System.out.println(c2.getName()); System.out.println(c3.getName()); }}有了class对象,我们就有了一切,这就是反射的源头,接下来就是“庖丁解牛”。 ...

July 6, 2019 · 1 min · jiezi

es6之class类的理解

传统的javascript中只有对象,没有类的概念。它是基于原型的面向对象语言。原型对象特点就是将自身的属性共享给新对象。这样的写法相对于其他传统面向对象语言来讲,很有一种独树一帜的感觉,非常容易让人困惑!如果要生成一个对象实例,需要先定义一个构造函数,然后通过new操作符来完成。构造函数示例: //函数名和实例化构造名相同且大写(非强制,但这么写有助于区分构造函数和普通函数)function Person(name,age){ this.name = name this.age = age}Person.prototype.say = function(){ return "我的名字叫"+this.name+"今年"+this.age+"岁了"}var obj = new Person("hebei",23) //通过构造函数创建对象,必须使用new运算符console.log(obj.say())//我的名字叫hebei今年23岁了构造函数生成实例的执行过程: 1、当使用了构造函数,并且new 构造函数(),后台会隐式执行new Object()创建对象2、将构造函数的作用域给新对象,即 new Object()创建出来的对象,而函数体内的this就代表new Object()出来的对象3、执行构造函数的代码4、返回新对象(后台直接返回)es6引入了Class(类)这个概念,通过class关键字可以定义类。该关键字的出现使得其在对象写法上更加清晰,更像是一种面向对象的语音。如果将之前的代码该写文es6的写法就会是这样子: class Person{ //定义了一个名字为Person的类 constructor(name,age){ //constructor是一个构造方法,用来接收参数 this.name = name this.age = age } say(){// 这是一个类的方法,注意千万不要加上function return "我的名字叫"+this.name+"今年"+this.age+"岁了" }}var obj = new Person("hebei",23)console.log(obj.say())//我的名字叫hebei今年23岁了注意项:1、在类中声明方法的时候,千万不要给该方法加上function关键字2、方法之间不要用逗号分隔,否则会报错 由下面代码可以看出类实质上就是一个函数。类自身指向的就是构造函数。所以可以认为es6中的类其实就是构造函数的另外一种写法! console.log(typeof Person)//functionconsole.log(Person === Person.prototype.constructor)//true以上代码说明构造函数的prototype属性,在es6的类中依然存在着实际上类的所有方法都定义在类的prototype属性上。代码证明下: Person.prototype.say=function(){//定义与类中相同名字的方法,成功实现了覆盖! return "我是来证明的,你叫"+this.name+"今年"+this.age+"岁了"}var obj = new Person("hebei",23)console.log(obj.say())//我是来证明的,你叫hebei今年23岁了当然也可以通过prototype属性对类添加方法。如下: Person.prototype.addFn = function (){ return "我是通过prototype新增加的方法,名字叫做addFn"}var obj = new Person("hebei",23)console.log(obj.addFn())//我是通过prototype新增加的方法,名字叫做hebei还可以通过Object.assign方法来为对象动态增加方法 ...

June 27, 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

ES6专题-class与面向对象编程

在ES5中,我们经常使用方法或者对象去模拟类的使用,并基于原型实现继承,虽然可以实现功能,但是代码并不优雅,很多人还是倾向于用 class 来组织代码,很多类库、框架创造了自己的 API 来实现 class 的功能。 ES6 时代终于有了 class (类)语法,能让我们可以用更简明的语法实现继承,也使代码的可读性变得更高,同时为以后的JavaScript语言版本添加更多的面向对象特征打下基础。有了ES6的class 以后妈妈再也不用担心我们的代码乱七八糟了,这简直是喜大普奔的事情。ok,我们看看神奇的class. 一、 类的定义 1.1 ES5 模拟类定义 function Person( name , age ) { this.name = name; this.age = age;}Person.prototype.say = function(){ return '我叫' + this.name + ',今年' + this.age + '岁';}var p = new Person('大彬哥',18); // Person {name: "大彬哥", age: 18}p.say() //"我叫大彬哥,今年18岁"使用ES5语法定义了一个Person类,该类有name和age两个属性和一个原型say方法。 这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大。接下来我们看下ES6 类的写法,这个就很接近于传统面向对象语言了。如果你想了解传统面向对象语言,这里是一个好切入点。 1.2 ES6 class类定义 class Person { constructor( name , age ) { this.name = name; this.age = age; } say() { return '我叫' + this.name + ',今年' + this.age + '岁'; }}var p = new Person('大彬哥',18); // Person {name: "大彬哥", age: 18}p.say() //"我叫大彬哥,今年18岁"上面代码定义了一个同样的Person类,constructor方法就是构造方法,而this关键字则代表实例对象,这更接近传统语言的写法。 ...

May 10, 2019 · 2 min · jiezi

为什么强烈禁止开发人员使用isSuccess作为变量名

在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。 关于这个"本次请求是否成功"的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,作者在很久之前就遇到过类似的问题,本文就来围绕这个简单分析一下。到底该如何定一个布尔类型的成员变量。 一般情况下,我们可以有以下四种方式来定义一个布尔类型的成员变量: boolean successboolean isSuccessBoolean successBoolean isSuccess以上四种定义形式,你日常开发中最常用的是哪种呢?到底哪一种才是正确的使用姿势呢? 通过观察我们可以发现,前两种和后两种的主要区别是变量的类型不同,前者使用的是boolean,后者使用的是Boolean。 另外,第一种和第三种在定义变量的时候,变量命名是success,而另外两种使用isSuccess来命名的。 首先,我们来分析一下,到底应该是用success来命名,还是使用isSuccess更好一点。 success 还是 isSuccess到底应该是用success还是isSuccess来给变量命名呢?从语义上面来讲,两种命名方式都可以讲的通,并且也都没有歧义。那么还有什么原则可以参考来让我们做选择呢。 在阿里巴巴Java开发手册中关于这一点,有过一个『强制性』规定:  那么,为什么会有这样的规定呢?我们看一下POJO中布尔类型变量不同的命名有什么区别吧。 class Model1 { private Boolean isSuccess; public void setSuccess(Boolean success) { isSuccess = success; } public Boolean getSuccess() { return isSuccess; } }class Model2 { private Boolean success; public Boolean getSuccess() { return success; } public void setSuccess(Boolean success) { this.success = success; }}class Model3 { private boolean isSuccess; public boolean isSuccess() { return isSuccess; } public void setSuccess(boolean success) { isSuccess = success; }}class Model4 { private boolean success; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; }}以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现以下规律: ...

May 8, 2019 · 3 min · jiezi

达文西,用JS写个兼容IE8浏览器的类选择器

基于某些考虑,有时我们项目中会尽量使用原生js,这种情况下连最简单的类选择器可能都要进行兼容性处理。getElementsByClassName是后来引入的,历史不如getElementById和getElementsByTagName。越是新的特性,浏览器的兼容相对就越差。虽然这3个选择器都并不是百分百兼容所有浏览器,比如getElementById和getElementsByTagName在IE上只支持>=5.5,不过谁还用低于5.5的IE呢?但getElementsByClassName就不同了,它在IE上只支持>=9,所以就存在兼容性的问题。兼容的方式,就是利用getElementsByTagName来获取所有的标签,然后判断每个标签有没有class,以及class里面的值是不是等于我们要找的。《JavaScript DOM编程艺术(第2版)》第42页有一个简单实现,但因为作者只是想说明原理,所以没有完善,用了indexOf去判断我们要的类名在不在标签的类名中,这会导致假如我们要找nam的话会把类名叫name的都找出来。所以网上有很多的实现,大致如下,并且下面的实现还考虑了标签的类名可能有多个类的情况。<div id=“app”> <p>zero</p> <p class=“name name-one”>one</p> <p class=“name name-one name-two”>two</p> <p class=“name name-one name-two name-three”>three</p></div><script> function getElementsByClassName(node, className){ // 如果支持原生getElementsByClassName就直接使用并返回结果 if (node.getElementsByClassName){ return node.getElementsByClassName(className); } // 这是最终返回的结果数组 var results = new Array(); // 先获取node节点下所有的标签 var elements = node.getElementsByTagName("*"); // 循环遍历获得的所有标签 for (var i = 0; i < elements.length; i++){ // 获取循环中的标签 var ele = elements[i]; // 获取该标签的类名 var cName = ele.className; // 如果类名为空,也就是没有class,那么这个标签肯定不是,所以继续循环下一次标签 if (cName === “”){ continue; } // 如果是多个class,那么就分别获得这几个class var cNames = cName.split(" “); // 循环遍历标签中的几个class,只要有一个class和我们要的className相等,说明就是匹配的标签 for (var j = 0; j < cNames.length; j++){ if (cNames[j] === className){ results[results.length] = ele; break; } } } return results; } // 使用自定义的类选择器 var nodes = getElementsByClassName(document.getElementById(“app”), “name-three”); for (var i = 0; i < nodes.length; i++){ console.log(nodes[i].innerText); }</script>如果在网络上找类似的实现的话,基本上就是到上面这一步。但上面的实现仍然存在一个缺陷,比如要选择类名既包括name又包括name-three的标签就没法实现。var nodes = getElementsByClassName(document.getElementById(“app”), “name name-three”);但原生的getElementsByClassName是支持多个类名选择的,既然要写一个兼容的自定义类选择器代替原生的,那么这个功能说什么也要上啊。和上面的变化,主要在于我们不仅要处理每个标签可能有多个类名的情况,也要处理我们传入的类名参数可能也是多个类名组成的情况,所以用两层循环可以实现,这里只给出与上面不同的代码部分。// 标签:如果是多个class,那么就分别获得这几个classvar cNames = cName.split(” “);// 我们要找的类名:如果是多个class,那么就分别获得这几个classvar classNames = className.split(” “);// 设置一个标记,默认为true,如果在循环判断中发现有条件不满足,设置为falsevar flag = true;// 先循环我们要找的每一个类名for (var j = 0; j < classNames.length && flag; j++){ // 看看我们的这个类名在不在这个标签的所有类名中 for (var k = 0; k < cNames.length; k++){ if (classNames[j] === cNames[k]){ break; }else if(classNames[j] !== cName[k] && k === cNames.length - 1){ // 循环到标签最后一个类名了,还不相等,就说明不匹配 flag = false; break; } }}// 如果符合条件,就加入结果集然后返回if (flag){ results[results.length] = ele;}至此,就可以用我们自定义的类选择器查找多个类都匹配的标签了。如果还要完善的话,至少还需要判断用户传入的类名参数是否为空这种情况。如果还要加强功能的话,可以考虑实现一个多级选择器的功能,比如jQuery中如下的语句,甚至还可以优化循环遍历的写法等。// 选择id为app下的所有class名有name的标签$("#app .name”)实现一个功能简单,做成一个产品很难。不过话说回来,如果要自定义太复杂的功能,我们当初在选择原生js时就会更加慎重了。 ...

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

JS 总结之class

class 是 ES6 的新特性,可以用来定义一个类,实际上,class 只是一种语法糖,它是构造函数的另一种写法。class Person {}typeof Person // “function"Person.prototype.constructor === Person // true???? 使用用法和使用构造函数一样,通过 new 来生成对象实例class Person {}let jon = new Person()???? constructor每个类都必须要有一个 constructor,如果没有显示声明,js 引擎会自动给它添加一个空的构造函数:class Person {}// 等同于class Person { constructor () { }}???? 实例属性和方法,原型属性和方法定义于 constructor 内的属性和方法,即定义在 this 上,属于实例属性和方法,否则属于原型属性和方法。class Person { constructor (name) { this.name = name } say () { console.log(‘hello’) }}let jon = new Person()jon.hasOwnPrototype(’name’) // truejon.hasOwnPrototype(‘say’) // false???? 属性表达式let methodName = ‘say’class Person { constructor (name) { this.name = name } [methodName] () { console.log(‘hello’) }}???? 静态方法不需要通过实例对象,可以直接通过类来调用的方法,其中的 this 指向类本身class Person { static doSay () { this.say() } static say () { console.log(‘hello’) }}Person.doSay() // hello静态方法可以被子类继承// …class Sub extends Person {}Sub.doSay() // hello可以通过 super 对象访问// …class Sub extends Person { static nice () { return super.doSay() }}Sub.nice() // hello???? 严格模式不需要使用 use strict,因为只要代码写在类和模块内,就只能使用严格模式。???? 提升class 不存在变量提升。new Person() // Uncaught ReferenceError: Person is not definedclass Person {}???? name 属性name 属性返回了类的名字,即紧跟在 class 后面的名字。class Person {}Person.name // Person???? this默认指向类的实例。???? 取值函数(getter)和存值函数(setter)class Person { get name () { return ‘getter’ } set name(val) { console.log(‘setter’ + val) }}let jon = new Person()jon.name = ‘jon’ // setter jonjon.name // getter???? class 表达式如果需要,可为类定义一个类内部名字,如果不需要,可以省略:// 需要在类内部使用类名const Person = class Obj { getClassName () { return Obj.name }}// 不需要const Person = class {}立即执行的 Class:let jon = new class { constructor(name) { this.name = name } sayName() { console.log(this.name) }}(‘jon’)jon.sayName() //jon???? 参考《ECMAScript 6 入门》Class 的基本语法 by 阮一峰 ...

December 23, 2018 · 2 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 8, 2018 · 4 min · jiezi

手摸手教你使用WebSocket[其实WebSocket也不难]

在本篇文章之前,WebSocket很多人听说过,没见过,没用过,以为是个很高大上的技术,实际上这个技术并不神秘,可以说是个很容易就能掌握的技术,希望在看完本文之后,马上把文中的栗子拿出来自己试一试,实践出真知。游泳、健身了解一下:博客、前端积累文档、公众号、GitHubWebSocket解决了什么问题:客户端(浏览器)和服务器端进行通信,只能由客户端发起ajax请求,才能进行通信,服务器端无法主动向客户端推送信息。当出现类似体育赛事、聊天室、实时位置之类的场景时,客户端要获取服务器端的变化,就只能通过轮询(定时请求)来了解服务器端有没有新的信息变化。轮询效率低,非常浪费资源(需要不断发送请求,不停链接服务器)WebSocket的出现,让服务器端可以主动向客户端发送信息,使得浏览器具备了实时双向通信的能力,这就是WebSocket解决的问题一个超简单的栗子:新建一个html文件,将本栗子找个地方跑一下试试,即可轻松入门WebSocket:function socketConnect(url) { // 客户端与服务器进行连接 let ws = new WebSocket(url); // 返回WebSocket对象,赋值给变量ws // 连接成功回调 ws.onopen = e => { console.log(‘连接成功’, e) ws.send(‘我发送消息给服务端’); // 客户端与服务器端通信 } // 监听服务器端返回的信息 ws.onmessage = e => { console.log(‘服务器端返回:’, e.data) // do something } return ws; // 返回websocket对象}let wsValue = socketConnect(‘ws://121.40.165.18:8800’); // websocket对象上述栗子中WebSocket的接口地址出自:WebSocket 在线测试,在开发的时候也可以用于测试后端给的地址是否可用。webSocket的class类:当项目中很多地方使用WebSocket,把它封成一个class类,是更好的选择。下面的栗子,做了非常详细的注释,建个html文件也可直接使用,websocket的常用API都放进去了。下方注释的代码,先不用管,涉及到心跳机制,用于保持WebSocket连接的class WebSocketClass { /** * @description: 初始化实例属性,保存参数 * @param {String} url ws的接口 * @param {Function} msgCallback 服务器信息的回调传数据给函数 * @param {String} name 可选值 用于区分ws,用于debugger / constructor(url, msgCallback, name = ‘default’) { this.url = url; this.msgCallback = msgCallback; this.name = name; this.ws = null; // websocket对象 this.status = null; // websocket是否关闭 } /* * @description: 初始化 连接websocket或重连webSocket时调用 * @param {*} 可选值 要传的数据 */ connect(data) { // 新建 WebSocket 实例 this.ws = new WebSocket(this.url); this.ws.onopen = e => { // 连接ws成功回调 this.status = ‘open’; console.log(${this.name}连接成功, e) // this.heartCheck(); if (data !== undefined) { // 有要传的数据,就发给后端 return this.ws.send(data); } } // 监听服务器端返回的信息 this.ws.onmessage = e => { // 把数据传给回调函数,并执行回调 // if (e.data === ‘pong’) { // this.pingPong = ‘pong’; // 服务器端返回pong,修改pingPong的状态 // } return this.msgCallback(e.data); } // ws关闭回调 this.ws.onclose = e => { this.closeHandle(e); // 判断是否关闭 } // ws出错回调 this.onerror = e => { this.closeHandle(e); // 判断是否关闭 } } // heartCheck() { // // 心跳机制的时间可以自己与后端约定 // this.pingPong = ‘ping’; // ws的心跳机制状态值 // this.pingInterval = setInterval(() => { // if (this.ws.readyState === 1) { // // 检查ws为链接状态 才可发送 // this.ws.send(‘ping’); // 客户端发送ping // } // }, 10000) // this.pongInterval = setInterval(() => { // this.pingPong = false; // if (this.pingPong === ‘ping’) { // this.closeHandle(‘pingPong没有改变为pong’); // 没有返回pong 重启webSocket // } // // 重置为ping 若下一次 ping 发送失败 或者pong返回失败(pingPong不会改成pong),将重启 // console.log(‘返回pong’) // this.pingPong = ‘ping’ // }, 20000) // } // 发送信息给服务器 sendHandle(data) { console.log(${this.name}发送消息给服务器:, data) return this.ws.send(data); } closeHandle(e = ’err’) { // 因为webSocket并不稳定,规定只能手动关闭(调closeMyself方法),否则就重连 if (this.status !== ‘close’) { console.log(${this.name}断开,重连websocket, e) // if (this.pingInterval !== undefined && this.pongInterval !== undefined) { // // 清除定时器 // clearInterval(this.pingInterval); // clearInterval(this.pongInterval); // } this.connect(); // 重连 } else { console.log(${this.name}websocket手动关闭) } } // 手动关闭WebSocket closeMyself() { console.log(关闭${this.name}) this.status = ‘close’; return this.ws.close(); }}function someFn(data) { console.log(‘接收服务器消息的回调:’, data);}// const wsValue = new WebSocketClass(‘ws://121.40.165.18:8800’, someFn, ‘wsName’); // 这个链接一天只能发送消息50次const wsValue = new WebSocketClass(‘wss://echo.websocket.org’, someFn, ‘wsName’); // 阮一峰老师教程链接wsValue.connect(‘立即与服务器通信’); // 连接服务器// setTimeout(() => {// wsValue.sendHandle(‘传消息给服务器’)// }, 1000);// setTimeout(() => {// wsValue.closeMyself(); // 关闭ws// }, 10000)栗子里面我直接写在了一起,可以把class放在一个js文件里面,export出去,然后在需要用的地方再import进来,把参数传进去就可以用了。WebSocket不稳定WebSocket并不稳定,在使用一段时间后,可能会断开连接,貌似至今没有一个为何会断开连接的公论,所以我们需要让WebSocket保持连接状态,这里推荐两种方法。WebSocket设置变量,判断是否手动关闭连接:class类中就是用的这种方式:设置一个变量,在webSocket关闭/报错的回调中,判断是不是手动关闭的,如果不是的话,就重新连接,这样做的优缺点如下:优点:请求较少(相对于心跳连接),易设置。缺点:可能会导致丢失数据,在断开重连的这段时间中,恰好双方正在通信。WebSocket心跳机制:因为第一种方案的缺点,并且可能会有其他一些未知情况导致断开连接而没有触发Error或Close事件。这样就导致实际连接已经断开了,而客户端和服务端却不知道,还在傻傻的等着消息来。然后聪明的程序猿们想出了一种叫做心跳机制的解决方法:客户端就像心跳一样每隔固定的时间发送一次ping,来告诉服务器,我还活着,而服务器也会返回pong,来告诉客户端,服务器还活着。具体的实现方法,在上面class的注释中,将其打开,即可看到效果。关于WebSocket怕一开始就堆太多文字性的内容,把各位吓跑了,现在大家已经会用了,我们再回头来看看WebSocket的其他知识点。WebSocket的当前状态:WebSocket.readyState下面是WebSocket.readyState的四个值(四种状态):0: 表示正在连接1: 表示连接成功,可以通信了2: 表示连接正在关闭3: 表示连接已经关闭,或者打开连接失败我们可以利用当前状态来做一些事情,比如上面栗子中当WebSocket链接成功后,才允许客户端发送ping。if (this.ws.readyState === 1) { // 检查ws为链接状态 才可发送 this.ws.send(‘ping’); // 客户端发送ping}WebSocket还可以发送/接收 二进制数据这里我也没有试过,我是看阮一峰老师的WebSocket教程才知道有这么个东西,有兴趣的可以再去谷歌,大家知道一下就可以。二进制数据包括:blob对象和Arraybuffer对象,所以我们需要分开来处理。 // 接收数据ws.onmessage = function(event){ if(event.data instanceof ArrayBuffer){ // 判断 ArrayBuffer 对象 } if(event.data instanceof Blob){ // 判断 Blob 对象 }}// 发送 Blob 对象的例子let file = document.querySelector(‘input[type=“file”]’).files[0];ws.send(file);// 发送 ArrayBuffer 对象的例子var img = canvas_context.getImageData(0, 0, 400, 320);var binary = new Uint8Array(img.data.length);for (var i = 0; i < img.data.length; i++) { binary[i] = img.data[i];}ws.send(binary.buffer);如果你要发送的二进制数据很大的话,如何判断发送完毕:webSocket.bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去:var data = new ArrayBuffer(10000000);socket.send(data);if (socket.bufferedAmount === 0) { // 发送完毕} else { // 发送还没结束}上述栗子出自阮一峰老师的WebSocket教程WebSocket的优点:最后再吹一波WebSocket:双向通信(一开始说的,也是最重要的一点)。数据格式比较轻量,性能开销小,通信高效协议控制的数据包头部较小,而HTTP协议每次通信都需要携带完整的头部更好的二进制支持没有同源限制,客户端可以与任意服务器通信与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器结语看了本文之后,如果还是有点迷糊的话,一定要把文中的两个栗子,新建个html文件跑起来,自己鼓捣鼓捣一下。不然读多少博客/教程都没有用,实践才出真知,切勿纸上谈兵。希望看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励。博客、前端积累文档、公众号、GitHub以上2018.10.22参考资料:WebSocket 教程理解WebSocket心跳及重连机制WebSocket协议:5分钟从入门到精通 ...

October 25, 2018 · 3 min · jiezi