迷惑的知识点:
作用域
函数、闭包
面向对象
ES的新特性
其他知识
从图中可知 js、css文件并不是跟index.html文件一起下载下来的,而是需要时才会下载
浏览器内核来解析下载下来的文件
| 内存 | 使用 |
|---|---|
| Gecko | 早期被Netscape和Mozilla Firefox浏览器使用 |
| Trident | 微软开发,被IE4~IE11浏览器使用,但Edge转向使用Blink |
| Webkit | 苹果给予KHTML开发、开源的,用于Sfari,Google Chrome之前也在用 |
| Blink | Webkit的一个分支,Google开发,目前应用于Google Chrome、Edge、Opera等 |
| 。。。 | 。。。 |
浏览器内核常指浏览器的排版引擎
排版引擎(layout engine),也成为浏览器引擎(browser engine)、页面渲染引擎(rendering engine)或样板引擎
HTML Parser把HTML文件解析成DOM TreeDom进行操作从而修改Domt Tree,这执行Javascript代码就是js引擎CSS Parse解析成Style Rulescss规则Style Rules和Dom Tree结合(Attachment)到一起,生成渲染树(Render Tree)Painting)到界面上显示(Display)出来JavaScript代码通过JavaScript引擎来执行
| 引擎 | 使用 |
|---|---|
| SpiderMonkey | 第一款JavaScript引擎,有Brendan Eich开发 |
| Chakra | 微软开发,用于IE浏览器 |
| JavascriptCore | WebKit中的JavaScript引擎,由Apple公司开发 |
| V8 | Google开发的强大JavaScript引擎,也帮助Chrome从众多浏览器中脱颖而出 |
| 。。。 | 。。。 |
ECMAScript和WebAssembly,并在Windows7或更高版本,MacOS 10.12+和使用x64、IA-32、ARM或MIPS处理器的linux系统上运行
Parse解析Javascript源代码(包括词法分析和语法分析)成抽象语法树(AST)
AST可以通过V8引擎中的Ignition库转换成字节码(bytecode),不直接转换成机器码是为了根据运行环境做代码优化和环境适配等
V8引擎本身的源码非常复杂,大概有超过100w行C++代码,通过了解它的架构,我们可以知道他是如何对Javascript执行的
Parse模块会将Javascriptdiamagnetic转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码
Ignition是一个解释器,会将AST转换成ByteCode字节码
TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能真实运算)Ignition会执行解释执行ByteCodeIgnition的V8官方文档https://v8.dev/blog/ignition-interpreterTurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码
Blink是Chrome内核,在解析到JavaScript时将JS代码通过流(Stream)的方式传到V8引擎Scanner(扫描器,做词法分析)Scanner将代码转换成许多Tokens,再通过Parse解析转化成AST
Parser就是直接将Tokens转成AST树结构PreParser称之为与解析
Outer()内部定义了Inner()函数,那么Inner()函数就会进行预解析在Parse阶段时,V8引擎会自动创建GlobalObject对象,比如Stirng、Date、Number、SetTimeout()、window...等都是GlobalObject的成员属性,所以在JS代码中可以字节调用这些对象
// 测试代码
var name = "why";
console.log(num1);
var num1 = 10;
var num2 = 20;
var result = num1 + num2;
foo(1)
function foo(num){
var m = 10;
var n = 20;
console.log('foo');
}
GlobalObject对象var GlobalObject = {
String : "类",
Date : "类型",
setTimeOut : "函数",
name : undefined,
num1 : undefined,
num2 : undefined,
result : undefined
foo : 0xa00
}
String、Date、setTimeOut是
GlobalObject自带的对象
name、num1、num2、result是解析出来的变量并添加到GlobalObject中
因为此时只是解析阶段,所以name、num1、num2、result都是undefined的
foo存储的函数地址
这些上下文中存在一个VO(variable object)(变量对象),其中VO分为GO和AO两种,全局上下文中VO = GO,函数执行上下文中VO = AO
代码从上下往下依次执行,从VO中取出目标对象并为其赋值
foo存储函数空间中存在一个父级作用域(parent scope),可以获得父级作用域中的数据,当foo作用域中的使用的数据在foo作用域中没找到就往父级作用域(parent scope)中查找,如果一直没有最终会找到全局作用域
var message = "Hello Global";
function foo(){
console.log(message);
}
function bar(){
message = "Hello Bar";
foo();
}
bar(); // 输出 Hello Global
GlobalObject对象foo的父级作用域(parent scope)就是GlobalObject
bar的父级作用域(parent scope)也是GlobalObject
赋值。。。
function foo(){
m = 100;
}
console.log(m);
function foo1(){
var a = b = 10;
/*
等价于
var a = 10;
b = 10;
*/
}
foo1();
console.log(a);
console.log(b);
foo函数来说,如果没有用var或者let定义变量,则m会被直接定义到全局变量(GO)中,所以对于console.log(m)会输出100而不是报错foo1函数来说,var a = b = 10;会被理解为var a = 10; b = 10;,根据1的解释,b会被定义到全局变量中,而a还在foo1的AO中,所以最后console.log(a)会报错,而console.log(b)会输出10每一个执行上下文会关联到一个环境变量(Variable Environment)中,在执行代码中变量和函数的声明会作为环境记录(Environment Record)添加到变量环境中
对于函数来说,参数也会被作为环境记录添加到变量环境中
所以对于上面的解释来说,将不再是VO(变量环境),而是环境记录(VariableEnvironment),也就是说不一定是O(object),只要是记录都行(map或者其他可以记录的类型)
不管什么语言,在代码执行的过程中都需要分配内存,不同的是某些语言需要手动管理内存,某些编程语言可以帮助管理内存
一般而言,内存管理存在如下的生命周期
JavaScript会在定义变量时为我们分配内存
因为内存是有限的,所以当内存不再需要的时候,我们需要对其进行释放,以便腾出更多的内存空间
再手动管理内存的语言中,我们需要通过一些方式来释放不再需要的内存,比如free函数:
1. 手动管理的方式会**影响编写逻辑的代码的效率**
2. 对开发者**要求较高**,不小心就会产生**内存泄漏**
JS引用使用标记清除算法,V8引擎为了更好的优化,它在算法的实现细节上也会结合一些其他的算法
JS中函数是一等公民,函数可以作为参数传递、作为返回值
function foo(func){
func();
}
function bar(){
console.log("aaa");
}
function run(){
function rush(){
console.log("bbb");
}
return rush;
}
foo(bar);
var fn = run();
fn();
高阶函数:一个函数如果接受另外一个函数作为参数,或者该会将另外一个函数作为返回值,就被称为高阶函数
比如上述
run()就是高阶函数
当函数属于某个对象时,称该函数为对象的方法
闭包:词法闭包或函数闭包
闭包解释2
function run(){
var name = "run";
function rush(){
console.log(name);
}
return rush;
}
var fn = run();
fn();
函数第一公民 可以作为形式参数和返回值
副作用:在执行一个函数时,除了返回函数值以外,对调函数产生了附加的影响,比如修改了全局变量、修改参数或者改变外部的存储
副作用往往是产生BUG的温床
var names = ["avc", "cba", "eax", "fas"];
// 纯函数,确定输入确定输出,没有副作用(没有修改外部变量等,原来的数组name没有被修改)
var name2 = names.slice(0, 3);
// 非纯函数,调用之后原来的数组name被改变了
function foo1(num1, num2){ // 纯函数
return num1 + num2;
}
var name = "log";
function foo2(num1, num2) { // 非纯函数 修改了外界的值
name = "log1";
return num1 + num2;
}
function foo(m, n, x, y){
return m + n * 2 + x * 3 + y * y;
}
foo(1, 2, 3, 4);
function bar(m){
return function(n){
n = n * 2;
return function(x){
x = x * 3;
return function(y){
y = y * y;
return m + n + x + y;
}
}
}
}
bar(1)(2)(3)(4);
// 简易柯里化写法
var bar2 = m => n => x => y => m + n * 2 + x * 3 + y * y;
var bar2 = m => n => x => y => {
return m + n * 2 + x * 3 + y * y;
}
从
function foo()变function bar()的过程 称为柯里化
// 柯里化的代码复用
function MakeAddr(num){
return function Addr(count){
return count + num;
}
}
var addr = MakeAddr(5);
addr(1); // 5 + 1
addr(2); // 5 + 2
addr(3); // 5 + 3
// 普通写法
function Add(m , n){
return m + n;
}
Add(5, 1); // 5 + 1
Add(5, 2); // 5 + 2
Add(5, 3); // 5 + 3
如果需要频繁对一个数进行加减处理,使用柯里化的代码比普通写法的字母数更少(不用每次都写"5,")
// 函数的形参个数
function adddd(x, y, z){
}
console.log(adddd.length); // 输出3,即表示该函数有三个形参
// 自动柯里化函数
function hyCurring(fn){
function curried(...args){
if(args.length >= fn.length){
return fn.apply(this, args);
// return fn.call(this, ...args);
// return fn(...args);
}
else {
function curried2(...args){
return curried.apply(this, args.concat(args2));
}
}
}
return curried;
}
function double(num){
return num * 2;
}
function square(num){
return num ** 2;
}
var count = 10;
var result = square(double(count));
var count1 = 10;
var result1 = square(double(count));
var count2 = 10;
var result2 = square(double(count));
function composeFn(m, n){
return function(count){
n(m(count));
}
}
var newFn = composeFn(double, square);
count3 = 10;
result3 = newFn(count3); // 组合了 double和square的函数
function hyCompose(...fns){
val length = fns.length;
for(let i = 0; i < length; i++){
if(typeof fn[i] !== 'function'){
throw new TypeError("");
}
}
function compose(...args){
var index = 0;
var result = length ? fn[index].apply(this, args) : args;
while(index < length){
result = fns[index].call(this, result);
}
}
return compose;
}
var message = "VO : GO";
var obj = {name : "Y", message = "Obj message"};
function foo() {
function bar() {
with(obj){
console.log(message);
}
}
bar();
}
foo(); // 输出 Obj message
with() {}语句用于定义对象查找作用域
不建议使用with语句,存在兼容性问题
var jsString = 'var message = "hello world"; console.log(message);'
eval(jsString;)
通过
eval来将字符串翻译成js语句并执行
Google Chrome报错,不推荐在开发中使用eval可读性差
运行中可能被篡改
不能被js引擎优化,因为是eval去执行的不经过引擎
面向对象是现实的抽象方式
对象是JavaScript中一个非常重要的概念,因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物
// 创建对象 方式1 使用Object类和new关键字来创建对象
var obj2 = new Object();
obj.name = "y";
obj.age = 15;
obj.height = 180;
// 创建对象 方式2 通过 字面量 的方式
var obj = {
name : "y",
age : 15,
height : 180,
eat : function() {
console.log("在吃饭");
}
};
{}是字面量,可以立即求值,而new Object()本质上是方法(只不过这个方法是内置的)调用,既然是方法调用,就涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的堆栈信息,方法调用结束后,还要释放该堆栈
var obj = {
name : "y",
age : 16
};
console.log(obj.name); // 获取属性
obj.name = "j"; // 修改属性
delete obj.name; // 删除属性
for (var key in obj){ // 遍历属性
console.log(key);
}
上述对象的属性都是直接定义在对象内部,或者直接添加到对象内部的,这样做不能对这个属性进行一些限制:比如是否可以delete/被遍历等
为了对属性进行比较精准的操作控制,我们可以使用属性描述符,通过属性描述符可以精准的添加或修改对象的属性,属性描述符需要使用Object.defineProperty来对属性进行添加或修改
// obj : 对象、prop : 属性、descriptor : 属性描述符
Object.defineProperty(obj, prop, descriptor);
Object.defineProperty()会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
该方法会返回被修改的对象,原本的对象也会被修改,所以可以不用接收函数返回值,并且可以发现该函数并非纯函数
| configurable | enumerable | value | writable | get | set | |
|---|---|---|---|---|---|---|
| 数据描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
| 存取描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
configurable 属性是否可以通过delete删除属性,是否可以修改其特性或者修改为存取描述符
enumerable 属性是否可以通过for-in或者Object.keys()返回该属性
enumerable为trueenumerable为falsewritable 是否可以修改属性的值
writable为truewritable为falsevalue 的具体值,读取属性时返回该值,修改属性时修改该值
value是undefined的get/get,为获得和设置使用的函数
存取属性描述符:只能设置configurable,enumerable,get,set
数据属性描述符,只能设置configurable,enumerable,writable,value
个人理解:
对于不需要进行额外处理的数据可以使用数据属性描述符,比如PI = 3.1415926
对于需要额外处理的数据,比如年龄只能是10~20就需要使用存取属性描述符
var obj = {
name : "y",
age : 16
};
// obj对象不存在height属性,则先添加height属性,再设置其属性描述
Object.defineProperty(obj, 'height', {
value : 180
});
console.log(obj);
这里obj并不会输出height属性,因为
height是通过属性描述符添加的所以enumerable默认为false
// configurable展示
// obj就是前面的obj
Object.defineProperty(obj, 'height', {
value : 180,
configurable : false,
});
delete obj.name
console.log(obj.name); // undefined
delete obj.height
console.log(obj.height); // 180 没有被删除
Object.defineProperty(obj, 'height', {
value : 200,
configurable : true,
});
console.log(obj.height); // 180 没有被修改成200,因为configurable最开始是false
// enumerable展示
Object.defineProperty(obj, 'height', {
value : 180,
configurable : false,
enumerable : true
});
console.log(obj); // 此时可以正常打印出height属性
var obj = {
name : "y",
age : 16 ,
_address : "private" // 个人习惯:前面有个下划线表示私有变量
};
Object.defineProperty(obj, 'address', {
configurable : true,
enumerable : true,
get : function () {
return this._address;
},
set : function(value){
this._address = value;
}
});
这里使用
address属性作为_address属性的代理
因为_address作为私有变量不希望外界随意读取,所以使用address代理的方法
var obj = {
_age : 0,
}
Object.defineProperties(obj, {
name : {
configurable : true,
enumerable : true,
writable : true,
value : "y"
},
age : {
configurable : true,
enumerable : true,
get : function (){
return this._age;
},
set : function (value) {
this._age = value;
}
}
});
console.log(obj);
var obj = {
_age : 10,
set age(value){
this._age = value;
},
get age() {
return this._age;
}
}
obj.age = 20;
console.log(obj);
var obj = {
_age: 0,
}
console.log(Object.getOwnPropertyDescriptor(obj, "_age"));
var obj = {
name: "y",
age: 16,
_address: "private"
};
console.log(Object.getOwnPropertyDescriptors(obj));
Object.preventExtensions(obj);
Object.seal(obj);
Object.freeze(obj);
如果所有对象都使用前面所说的字面量方式来创建,那么会出现特别多的重复代码
function createPerson(name, age){
var person{};
person.name = name;
person.age = age;
return person;
}
var p1 = createPerson("name1", 10);
var p2 = createPerson("name2", 11);
var p3 = createPerson("name3", 12);
function foo(){
console.log("hello world");
}
var f = new foo();
var f2 = new foo; // 如果不需要传递参数,可以不要小括号
console.log(f2); // 返回了一个foo类型的对象
当使用
new关键字之后,foo就从普通函数变成了构造函数
new操作符调用,那么会执行如下操作
[[prototpe]]属性会被赋值为该构造函数的prototype属性function Person(name, age, height){
this.name = name;
this.age = age;
this.height = height;
this.eating = function (){
console.log(this.name + "正在吃饭");
}
}
var p1 = new Person("张三", 18, 180);
var p2 = new Person("李四", 10, 180);
console.log(p1.eating === p2.eating); // false
对比方法1的工厂方法,该方法可以明确知道p1这个变量是什么类型
p1和p2的函数并不是相同,可见每个函数都开辟了一个内存空间,存在浪费的问题
JavaScript当中每个对象都有一个特殊的内置属性[[prototype]],这个特殊的对象可以指向另一个对象,一般把[[prototype]]称为隐式原型(一般看不到、不会改、用不到)
prototype是原型的意思,在浏览器中可以使用
obj.__proto__来查看[[prototype]](部分浏览器支持)
var obj = {};
obj.__proto__.age = 10;
console.log(obj.age);
[[get]]操作[[prototype]](可以用来实现继承等操作)函数是一个对象,所以也有隐式原型[[prototype]]
函数存在一个显示原型prototype
[[prototype]]和prototype不是一个东西,前者是理论名称,后者是实际属性
fun.__proto__中的__proto__并不是标准支持的,而是部分浏览器为了方便程序员debug而增加的
function Person() {
}
// 函数也是一个对象,所以也有隐式原型[[prototype]]
console.log(Person.__proto__); // {}
console.log(Person.prototype); // {}
console.log(Object.getOwnPropertyDescriptors(Person.prototype)); // 输出一个constructor属性,指向构造函数本身
var p1 = new Person();
var p2 = new Person();
console.log(p1.__proto__ === Person.prototype); // true
Person.prototype.name = "y";
console.log(p1.name, " ", p2.name);
上面有对
new的调用操作进行解释,其中第二点对象内部的[[prototpe]]属性会被赋值为该构造函数的prototype属性的意思就是将返回对象的__proto__赋值等于函数的prototype,所以console.log(p1.__proto__ === foo.prototype)返回值为true
foo.prototype = {
name : "y",
age : 19,
}
Object.defineProperty(foo.prototype, "constructor", {
enumerable : false,
configurable : true,
writable : true,
value : foo
});
直接修改
prototype对象,但是prototype中必须存在一个constructor属性指向本身
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.eating = function() {
console.log(this.name + "吃东西");
}
var p1 = new Person("x", 10);
var p2 = new Person("y", 10);
p1.eating();
p2.eating();
function Person(name, age){
this.name = name;
this.age = age;
}
var p1 = new Person("x", 10);
var p2 = new Person("y", 10);
上述的Person应该称之为构造函数,但是对其他语言来说更像是一个类
var obj = {
name : "y"
}
console.log(obj.address);
obj对象并没有address属性,所以回去obj.__proto__原型上查找,如果也没有就会在obj.__proto__.__proto__上去查找直到找到或者顶层原型为止,这种类似链表的查找方式就是原型链
顶层
__proto__就是Object.__proto__
var obj = {};
console.log(obj.__proto__ === Object.prototype); // true
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.running = function() {
console.log(this.name + " is running");
}
function Student(sno){
this.sno = sno;
this.friends = [];
}
var p = new Person("1", "2");
Student.prototype = p;
Student.prototype.studying = function(){
console.log(this.name + " is studying");
}
function Teacher(title){
this.title = title;
}
Teacher.prototype.teaching = function() {
console.log(this.name + " is teaching");
}
var s1 = new Student(1);
var s2 = new Student(2);
s1.running();
s1.studying();
上述代码的内存解释
代码中能明显发现问题:s1、s2是两个对象公用同一个Person对象的引用会互相影响
为了解决原型链继承中存在的问题,开发人员提供了一种新的技术:constructor stealing(借用构造函数、经典继承、伪造对象)
apply()或call()方法也可以在新创建的对象上执行构造函数function Person(name, age, friends){
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.eating = function() {
console.log(this.name + " eating");
}
var p = new Person();
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends);
this.sno = sno;
}
Student.prototype = p;
var s1 = new Student("y", 10, ["1", "2"], 1);
本质上是
Student借用执行Person的构造函数的执行过程,本质其实是给Student赋值
Person函数执行了两次)一种继承方法,不是通过构造函数实现的方法
var obj = {
name : "y",
age : 16
};
function createObject(protoObj){
var newObj = {};
Object.setPrototypeOf(newObj, protoObj); // 设置newObj的原型为protoObj
return newObj;
}
// 不适用Object函数库实现设置原型的方法
function createObject2(protoObj){
function Fn() {}
Fn.prototype = protoObj;
var new Obj = new Fn();
return newObj;
// newObj.__proto__ = protoObj; // 不可这么写,因为__proto__不是所有js引擎都支持
}
// 创建info对象的原型指向obj对象
var info = {};
console.log(info);
console.log(info.__proto__);
info = Object.create(obj); // 功能等价于 createObject 和 createObject2
Object.setPrototypeOf(newObj, protoObj);设置newObj的原型为protoObj
寄生式继承的思路是结合原型类继承和工厂模式的一种方式
即创建一个封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再将这个对象返回
var personObj = {
running = function() {
console.log("running");
}
}
function createStudent(person, name){ // 工厂函数
var stu = Object.create(person); // 原型式继承
stu.name = name;
stu.studying = function() {
console.log("studying");
}
}
var stu1 = createStudent(person, "x");
var stu2 = createStudent(person, "y");
每个对象的
studying()方法都是新建的
stu1和stu2没有明确的类型(consolo.log一下就知道)
function CreateObject(o){
function Fn (){}
Fn.prototype = o;
return new Fn;
}
function Person(name, age, friends){
this.name = name;
this.age = age;
this.friends = friends;
}
Person.prototype.running = function() {
console.log(this.name + " running");
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends);
this.sno = sno;
this.score = score;
}
Student.prototype = CreateObject(Person.prototype);
Student.prototype.studying = function() {
console.log(this.name + " studying " + this.score);
}
var stu = new Student("x", 10, [], 1, 100);
console.log(stu); //Person { name: 'x', age: 10, friends: [], sno: 1, score: 100 }
stu.running(); // x running
stu.studying(); // x studying 100
输出
stu是Person类的,因为输出的是constructor的name属性,而这里的constructor使用的是Person的所以最后输出的名字是Person
function inheritPrototype(SubType, SuperType){
SubType.prototype = CreateObject(SuperType.prototype);
Object.defineProperty(SubType.prototype, 'constructor', {
enumerable : false,
configurable : true,
writable : true,
value : SubType
});
}
inheritPrototype(Student, Person);
手动设置
Student的constructor是Student自己就行了
var obj = {
name : "w",
age : 19
};
var info = Object.create(obj, {
address : {
value : "BJ",
enumerable : true
}
});
console.log(info);
console.log(info.hasOwnProperty('address')); // true
console.log(info.hasOwnProperty('name')); // false
console.log("address" in info); // true
console.log("name" in info); // true
创建对象时,为新对象添加属性描述符
info.hasOwnProperty('address')判断属性是否是自己的属性
"name" in info判断对象是否存在name属性
instanceof用于检测构造函数的prototype是否出现在某个实例对象的原型链上
function CreateObject(o){
function Fn (){}
Fn.prototype = o;
return new Fn;
}
function inheritPrototype(SubType, SuperType){
SubType.prototype = CreateObject(SuperType.prototype);
Object.defineProperty(SubType.prototype, 'constructor', {
enumerable : false,
configurable : true,
writable : true,
value : SubType
});
}
function Person(){
}
function Student() {
}
inheritPrototype(Student, Person);
var stu = new Student()
console.log(stu instanceof Student); // true
console.log(stu instanceof Person); // true
console.log(stu instanceof Object); // true
instanceof后面的必须是构造函数
JavaScript当中每个对象都有一个特殊的内置属性[[prototype]],这个特殊的对象可以指向另一个对象,一般把[[prototype]]称为隐式原型(一般看不到、不会改、用不到)
var obj = {};
console.log(obj.__proto__);
函数是一个对象,所以也有隐式原型[[prototype]]
函数存在一个显示原型prototype
当创建一个函数后,JS引擎会自动给函数对象添加属性Foo.prototype = { constructor : Foo }
定义Foo()函数时,相当于new Funtion()创建函数对象,这时编译器执行Foo.__proto__ = Function.prototype,而Function.prototype = { constructor : Function }
Function是极为特殊的对象,它的prototype和__proto__相等
function Foo(){
}
console.log(Foo.__proto__);
console.log(Foo.prototype);
console.log(Foo.prototype === Foo.__proto__); // false
console.log(Foo.prototype.constructor); // Function : Foo
console.log(Foo.__proto__.constructor); // Function : Function
console.log(Function.prototype === Function.__proto__); // true
理论上
class的底层实现方式还是 上述的旧版创建代码
使用babel可以将代码装成旧版本代码
constructorconstructor方法constructor方法,如果有多个会抛出异常// 类声明
class Person{
constructor(name, age){
this.name = name;
this.age = age;
this._address = "";
}
eating() {
console.log(this.name + " eating");
}
running() {
console.log(this.name + " running");
}
// 访问器
get address(){
return this._address;
}
set address(value){
this._address = value;
}
// 静态方法
static createPerson(){
return new Person("", 1);
}
};
console.log(Person.prototype);
console.log(Person.prototype.constructor); // 指向当前Person
console.log(typeof Person); // function
// 类的表达式 用的比较少
var Animal = class {
};
var p1 = new Person("x", 1);
var p2 = new Person("y", 2);
super关键字,一般用在三个地方:子类的构造函数、实例方法、静态方法
在子类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数
class Person{
constructor(name, age){
this.name = name;
this.age = age;
}
eating() {
console.log(this.name + " eating");
}
};
class Student extends Person {
constructor(name, age, sno){
super(name, age); // 调用父类构造方法
// super.eating(); // 调用父类的方法
this.sno = sno;
}
// 方法的重写
eating() {
console.log("Student " + this.name + " eating");
}
};
babel转换ES6为ES5class MyArray extends Array{
firstItem(){
return this[0];
}
lastItem(){
return this[this.length-1];
}
}
var arr = new MyArray(1, 2, 3);
console.log(arr.firstItem());
console.log(arr.lastItem());
扩展数组功能
Javascirpt的类只支持单继承,也就是说它只能有一个父类
class Person{
}
class Runner {
running(){
}
}
class Eater {
eating() {
}
}
function mixinRunner(BaseClass){
class NewClass extends BaseClass {
running() {
console.log("running");
}
}
return NewClass;
}
class Student extends Person{
}
var NewStudent = mixinRunner(Student);
var ns = new NewStudent();
ns.running();
通过
mixinRunner扩展类的功能
[name+123]来设置key的名称(下面的[name+123]例子)var name = "w";
var age = 10;
var obj = {
name,
age,
foo : function() {
console.log(this);
},
bar() {
console.log(this);
},
baz : () => {
console.log(this);
},
[name + 123] : "hgggg"
}
console.log(obj);
obj.foo();
obj.bar();
obj.baz();
var names = ["1", "2", "3"];
// 解构数组
var [item1, item2, item3] = names;
console.log(item1, item2, item3); // 1 2 3
// 解构后面的元素
var [, itema, itemb] = names;
console.log(itema, itemb); // 2 3
// 解构一个元素,后面的放到新数组
var [itemc, ...newNames] = names;
console.log(itemc); // 1
console.log(newNames); // 2 3
// 结构默认值 如果没有解构出来的值
var [item11, item22, item33, item44 = "aaa"] = names;
console.log(item44);
var obj = {
name : "w",
age : 15,
height : 190
};
var {name, age, height} = obj;
console.log(name, age, height);
var {height, age, name} = obj; // 读取顺序无关
console.log(name, age, height);
var {name : NewName} = obj; // 给换个新的变量名
console.log(NewName); // w
var {address : newAddress = "BJ"} = obj; // 设置默认值
console.log(newAddress);
console.log(foo); // undefined 不报错
var foo = "foo";
console.log(bar); // Error
let bar = "bar";
bar实际上是被创建了出来,但是不能访问
const name = "a";
name = "B"; // error
const obj = {};
obj.name = "B"; // success