前端开发【第5篇:JavaScript进阶】
语句
复合表达式和空语句
复合表达式意思是把多条表达式连接在一起形成一个表达式
{
let a = 100; let b = 200; let c = a + b; }
注意这里不能再块级后面加分号,块内都有缩进不是必须的,块内的原始语句通过;号进行分割
空语句在块级或者()后面加分号表示为空语句,可以理解为什么都没有的语句,在语句后面加分好";"
就算要写空语句最好也在后面加上注释
for(let i = 0, i < a.lenght; a[i++] = 0) /* empty */ ;
声明语句
变量声明:let和const不需要说了
函数声明
函数声明的语法如下:
function funcname([arg1, [arg2, ....]]) { statements }
函数的定义也可以写成如下:
let f function(x) {return x +1}
条件语句
和Python的条件判断基本相似
if(表达式为真){ 为真的时候执行的语句 }
什么时候为假:nul、undefind、false、0、NaN
else和 else if
let n = 100; if(n === 1){ //执行代码块1 }else if (n === 2){ //执行代码块2 }else if (n === 3){ //执行代码块3 }else{ //如果上面的都没有执行执行这里 }
switch语句感觉和shell中的case语句很像,非常适合这种多匹配的表达式
switch (n){ case 1: // 如果n === 1 从这里执行 break; // 执行完后跳出switch语句 case 2: // 如果n === 2 从这里执行 break; // 执行完后跳出switch语句 case 3: // 如果n === 3 从这里执行 break; // 执行完后跳出switch语句 default: // 如果上面的都没有匹配执行这里 break }
下面的例子更贴近实战
// switch实战 function convert(x) { switch (typeof x){ case 'number': // 如果是数将它转换为16进制 return x.toString(16); case 'string': // 如果是字符串 return '"' + x + '"'; default: // 使用普通方法转换其他类型 return String(x) } }
循环
while循环和python中的一样
n = 0; while (n < 10){ // 当条件不成立的时候停止循环否则会一直执行 //要执行的语句 console.log(n); n++ }
JavaScript中还有一个非常有意思的do while至少执行一次的while循环它不像while这么常用,因为这种情况少~~!
let n1 = 0; do { // 这里是while的执行内容 console.log('执行一次'); console.log(n1++) }while (n1++ < 10); // 这里是条件结果,当条件为假的时候停止
for循环有些特殊,有两种for循环一个一个看先看第一种与while循环类似的
for(let i = 0; i < 10; i){ // (初始值; 停止条件; 初始值操作 可以理解用来达成停止条件的操作) console.log(i); // 循环内容操作 console.log('Hello World') }
它很适合用在循环数组对象的地方
let loopArray = ['a', 'b', 'c', 'd', 'e']; for(let i = 0; i < loopArray.length; i++){ // 这个循环中我们可以看出来这个i 初始值为0,可以理解为数组的下标0开始循环当小于数组长度的时候停止 // 循环一次自加1达到了循环的效果 console.log(loopArray[i]) }
当循环普通对象的时候又有一个新的循环for / in 类似python中的循环,很呛人~~
在JavaScript中,对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。可枚举性决定了这个属性能否被for…in查找遍历到。如果一个对象中的属性是可枚举的那么它们枚举的顺序根据各自的浏览器可能不同知道就可以了
正常的循环对象或者数组对象和Python差不多:
function Person(name) { this.name = name } Person.prototype = { age : 18, job : 'shuaige' }; let shuaige = new Person('shuai'); for(let i in shuaige){ console.log(i) }
break、continue、return 不必说了
异常处理
和Python中的异常处理差不多很有意思
// 异常处理 try{ // 这里如果没有异常执行到尾的操作 }catch(e){ // 这里是如果出现异常之后执行的操作 }finally { // 这里是不管你是否只是成功还是失败都执行的操作 }
功能和Python异常一样,都是可以表面异常后导致程序终止的地方都可能需要加上异常处理,看自己程序的设计了
对象
终于到对象了,弱弱的问下你有对象吗?
言归正传,MD JavaScript中的普通对象可以理解为Python中的字典,OK只是可以理解为Python中的字典,但是有区别的(感觉是Python中字典和类的混合体~~忽略这一句话)
创建对象很简单
1、可以直接通过Object.create()创建对象
2、或者通过直接创建let a = {} 也创建了一个对象
3、或者通过new创建对象 let a = new Object() / new Date / new Array() / new RegExp()
原型
每个JavaScript对象除了null都和另一个对象相关联,“另一个”对象就是我们熟知的“原型”,
所有通过对象直接创建的对象都具有一个原型对象,并可以通过JavaScript代码Object.prototype获得原型对象的引用,举个例子:
通过new Object()创建的对象也继承了Object.prototype,同样new Date() 创建的对象也继承了Date.prototype
原型的诞生:
Brendan Eich思考之后,决定借鉴C++和java的new命令,将new命令引入了JavaScript,在传统的面向对象的语言里,new 用来构造实例对象,new 会调用构造函数,但是传统面向对象的语言new 后面的是类,内部机制是调用构造函数(constructor),而Brendan Eich简化了这个操作,在JavaScript里面,new 后面直接是构造函数,如是我们可以这么写一个Person类:
function Person(name) { this.name = name } let personOne = new Person("Brendan Eich"); console.log(personOne.name)
这样就创建了一个新的实例了。但是new有缺陷。用构造函数生成实例对象是无法无法共享属性和方法,例如下面代码:
function Person(name) { this.name = name; this.nation = 'USA'; } let person1 = new Person("Brendan Eich"); let preson2 = new Person("Shuai Ge"); person1.nation = "China"; console.log(person1.nation); // China console.log(preson2.nation); // USA
每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。和JavaScript工厂模式的缺点一样,过多重复的对象会使得浏览器速度缓慢,造成资源的极大的浪费。
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性,这个属性都是指向一个prototype对象。下面一句话很重要:所有实例对象需要共享的属性和方法,都放在这个Prototype对象(原型对象)里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。如是我们可以改写下上面的程序:
function Person(name) { this.name = name; } // 我们不需要指定prototype对象当我们创建对象的时候默认会生成 Person.prototype = {nation: "USA"}; let person1 = new Person("Brendan Eich"); let person2 = new Person("Shuai Ge"); console.log(person1.nation); console.log(person2.nation);
当我们这样写程序时候Person.prototype.nation = 'China'; 所有实例化的类的nation都会变成China。
由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像"继承"了prototype对象一样。prototype只是提供了实现JavaScript继承的一个很方便的途径和手段。
原型链
知道原型的作用和原型的由来后,我们在看下原型链
function Person(name) { this.name = name } Person.prototype = { 'key1': 'value1', 'key2': 'value2' }; let person1 = new Person('shuaige'); console.log(person1.__proto__ == Person.prototype); // true console.log(person1.constructor); // [Function: Object]
如上图所示,我们通过prototype指向了一个共享的属性,在实例化的对象中person1和person2中他们都有一个__proto__属性指向了,prototype
在prototype中又有一个constructor的属性它指向了构造函数
由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像"继承"了prototype对象一样。prototype只是提供了实现JavaScript继承的一个很方便的途径和手段。
可以说,由prototype、constructor、__proto__三个指针构成了整个javascript的原型链。
可以看出,原型的属性是共享的,因此,constructor属性也是共享的,可以通过实例访问
但是,另一方面,无法通过实例修改原型中的值。如果在实例中添加了一个属性,并且属性名跟原型中的相同,那么将会在实例中创建该属性并屏蔽原型中的那个属性。可以通过delete 删除自己的属性
function Person(name) { this.name = name } Person.prototype = { constructor: Person, 'key1': 'value1', 'key2': 'value2' }; let person1 = new Person('shuaige'); console.log(person1.key1); person1.key1 = 'shuaige'; console.log(person1.key1); delete person1.key1; console.log(person1.key1); 结果: /* value1 shuaige value1 */
in 操作符及 hasOwnProperty()方法,看下面的检查属性
javascript中检测对象属性有两种方式,一种是使用in操作符,一种是hasOwnProperty()方法。这两种方式的区别是:in操作符会同时检测原型和实例,而hasOwnProperty方法只会检测实例。看下面的例子:
function Person(name) { this.name = name } Person.prototype = { constructor: Person, 'key1': 'value1', 'key2': 'value2' }; let person1 = new Person('shuaige'); // 判断属性中是否有name,取值范围是实属性和原型属性 console.log("name" in person1); console.log("key1" in person1); // 判断name是否是实例属性,不会判断原型属性 console.log(person1.hasOwnProperty("name"));
原型的重写
由于为原型一个个单独添加属性和方法不利于封装,因此更好的做法是用封装好的属性和方法的字面量来重写整个原型对象。如下:
function Person(name) { this.name = name } Person.prototype = { constructor: Person, 'key1': 'value1', 'key2': 'value2' };
注意constructor 的属性,如果不在新的原型对象中重新设定,那么constructor属性将不在指向Person。因为这里完全重写了默认的prototype对象,因此constructor属性将指向新的contructor—object构造函数。
但是,如果使用重写原型的方法,就相当于切断了构造函数与最初原型的联系。一个实例只拥有一个唯一的原型,而实例中的指针又只指向原型,而不是构造函数。因此,原型的重写最好在实例之前,否则实例将指向最初的原型。如下例子:
function Person() { } let person1 = new Person(); Person.prototype = { constructor: Person, 'name': 'dashuaige', 'key1': 'value1', 'key2': 'value2' }; console.log(person1.name); // undefined console.log(person1.constructor.prototype.name); // dashuaige
属性的查询
function Person() { } Person.prototype = { constructor: Person, 'name': 'dashuaige', 'key1': 'value1', 'key2': 'value2' }; let person1 = new Person(); console.log(person1.name); // 获取name属性 console.log(person1["name"]); // 获取name属性
通过.访问的时候类似C和java对象的静态字段访问类似
第二种方法和python中的字典类似
删除属性
通过delete进行删除
function Person() { } Person.prototype = { constructor: Person, 'name': 'dashuaige', 'key1': 'value1', 'key2': 'value2' }; let person1 = new Person(); person1.age = 18; //设置一个属性age = 18 person1['hobby'] = 'belle'; //设置一个属性hobby = belle delete person1.age; delete person1.hobby;
检查属性
JavaScript对象可以看做属性的集合,可以通过in、hasOwnProperty、propertyIsEnumerable来完成查询
in
in左侧是属性名(字符串)右侧是对象,如果对象自有属性或继承属性中包含这个属性返回true
hasOwnProperty
hasOwnProperty检测给定的名字(字符串)是否是对象的自有属性,如果是对象的自有属性返回true如果是原型属性返回false
function Person() { } Person.prototype = { constructor: Person, 'name': 'dashuaige', 'key1': 'value1', 'key2': 'value2' }; let person1 = new Person(); person1.age = 18; //设置一个属性age = 18 person1['hobby'] = 'belle'; //设置一个属性hobby = belle console.log("hobby" in person1); // true 判断给定的左侧名字是否在自有属性或原型属性中 console.log(person1.hasOwnProperty("hobby")); //如果是自有属性返回 true console.log(person1.hasOwnProperty("name")); //如果是原型属性返回 false
枚举属性
let o = {'x': 1, 'y': 2, 'z': 3}; o.propertyIsEnumerable("toString"); // 设置为不可枚举 for(p in o) console.log(p)
为什么要有这个枚举属性
有许多工具库给Object.prototype添加了新的方法或属性,这些方法和属性可以被所有的对象继承并使用,然而在ECMAScript5标准前,这些方法是不能定义为不可枚举的。
因此他们都可以被for/in 循环中枚举出来。为了避免这种情况需要过滤for/in循环中的属性,下面两种方式是最常见的
for(let i in person1){ if (!person1.hasOwnProperty(i)) continue; // 跳过继承属性 console.log(i) } for(let i in person1){ if(typeof person1[i] == "function") continue; // 跳过方法 }
getter和setter
首先明确一下他是一个对象存储属性,set 、 get ,一个只有只读方法一个只有只写方法
let o = { // 普通的数据属性 "name": "None", // 存取器属性都是成对定义的函数 get accessor_prop(){/* 这里是函数体*/}, set accessor_prop(value){/* 这里是函数体 */} };
实例
let sex = { "sex": "man" , get accessor_sex() { return this.sex }, set accessor_sex(value){ return this.sex = value}, }; // 查询 console.log(sex.accessor_sex); // 设置 console.log(sex.accessor_sex = "woman");
或许你会觉得这是多此一举的,因为我们完全可以忽视get和set,直接让sex方法具备两种权限。 但之所以我们将get和set单独拿出来,是为了更加清晰地理解ECMAScript5对javascript对象键值操作中,一个更为严谨的诠释。
类属性
想获得对象的类可以通过toString()方法,通过直接量或者new创建或者自定义的函数创建的对象的类属性都是Object,因此对自定义类来说,没有办法通过类属性区分对象类
可扩展属性
对象的可扩展属性表示对象是否可以添加新属性,所有内置对象属性都是显示可扩展的,可以通过
function Person() { } Person.prototype = { constructor: Person, 'name': 'dashuaige', 'key1': 'value1', 'key2': 'value2' }; let person1 = new Person(); console.log(Object.isExtensible(person1)); // true 判断是否为可扩展对象 console.log(Object.preventExtensions(person1)); console.log(Object.isExtensible(person1)); // false person1.test = "valueTest"; console.log(person1); // 属性无法添加成功
序列化和反序列化
JavaScript提供了一个全局的对象JSON
let testObject = {"key1": "value1", "array1": [1, 2, 3, 4, 5]}; // 定义一个对象 let testObjectDumps = JSON.stringify(testObject); // 序列化为json格式 console.log(typeof testObjectDumps, "|", testObjectDumps); // string | {"key1":"value1","array1":[1,2,3,4,5]} let testObjectLoad = JSON.parse(testObjectDumps); console.log(typeof testObjectLoad, "|", testObjectLoad); // object | { key1: 'value1', array1: [ 1, 2, 3, 4, 5 ] }
数组
和Python差不多但是有些特殊的地方比如
稀疏数组
// 稀疏数组 let a = new Array(5); // 创建一个数组对象,长度为5,但是没有元素,我擦擦擦~ let b = []; // 创建一个空数组,长度为0 a[1000] = 0; // 赋值添加元素,数组长度为10001 let a1 = [,]; // 次数组没有元素长度为1 let a2 = [undefined]; // 次数组包含一个值为undefined console.log(0 in a1); // false: a1在下标为0的索引处没有元素 console.log(0 in a2); // true : a2在下标为0的索引处元素
数组长度可以修改元素我擦
console.log([].length); // 输出长度为0 console.log([1, 2, 3].length); //输出长度为3 let a = [1, 2, 3, 4, 5]; console.log(a.length); // 输出长度为3 // 见证奇迹的时候到了 a.length = 3; console.log(a); // [ 1, 2, 3 ] 竟然是截取保留前3个值 a.length = 0 ; console.log(a); // [] 删除所有的元素 a.length = 5 ; console.log(a); // [ <5 empty items> ] 长度为5但是没有元素,就像new Array()
数组添加元素删除元素
let a = [1, 2, 3, 4]; // 定义一个新数组 a[0] = 0; console.log(a); // 如果元素存在会被替换[ 0, 2, 3, 4 ] 但是如果是空的这也是一个增加元素的方法 a.push(5); console.log(a); // 在数组对象后面追加元素[ 0, 2, 3, 4, 5 ] 亦可以追加多个元素
删除就简单了
可以通过length。。。 也可以通过delete删除指定元素
数组方法
join 转化为字符串并拼接
let a = [1, 2, 3, 4]; console.log(a.join("-")); // 结果1-2-3-4
reverse()翻转数组方法
let a = [1 ,2, 3, 4, 5]; console.log(a.reverse()); // [ 5, 4, 3, 2, 1 ]
sort()排序方法
默认根据字母表排序如果有undefined将放到最后面
concat() 扩展数组
这个链接的是元素而不是数组本身,这个扩展数不会递归扁平化数组,也不会修改数组
let a = [1, 2, 3]; console.log(a.concat(4 ,5)); // [ 1, 2, 3, 4, 5 ] console.log(a.concat([4, 5])); // [ 1, 2, 3, 4, 5 ] console.log(a.concat([4, 5], [6, 7])); // [ 1, 2, 3, 4, 5, 6, 7 ] console.log(a.concat(4, [5, [6, 7]])); // [ 1, 2, 3, 4, 5, [ 6, 7 ] ]
slice()数组切片返回指定数组
// slice()切片方法 let a = [1, 2, 3, 4, 5]; console.log(a.slice(0,3)); // 返回[ 1, 2, 3 ] console.log(a.slice(3)); // 返回 [ 4, 5 ] console.log(a.slice(1, -1)); // 返回 [ 2, 3, 4 ] console.log(a.slice(-3, -2)); // 返回 [ 3 ]
splice()方法也是删除插入元素的方法但是和slice和concat有本质区别是它会修改调用的数组
// splice // splice 第一个参数指定了插入和删除的起始位置,第二个参数指定了应该从数组中删除多少个元素 let a = [1, 2, 3, 4, 5, 6, 7, 8,]; // 如果第二个元素起始点到后面的元素都被删除 console.log(a.splice(4), a); // 返回[ 5, 6, 7, 8 ] a被修改为[ 1, 2, 3, 4 ] // 现在a是 [1, 2, 3, 4] console.log(a.splice(1, 2), a); // 从1开始删两个是[ 2, 3 ] a被修改为[1, 4] // splice 前两个指定了起始位置和删除元素的个数,后面跟着的是插入的元素插入的位置是前面定义的位置 let b = [1, 2, 3, 4,]; console.log(b.splice(2, 0, 'a', 'b'), b); // [] 从第二个元素开始插入[ 1, 2, 'a', 'b', 3, 4 ]
push和pop方法
push和pop方法允许将数组当做栈来使用,push方法在数组尾部添加一个或多个元素,并返回新的长度,pop相反;它删除数组最后一个元素,减少数组长度并返回它删除的值,组合push和pop能够实现JavaScript数组实现先进先出的栈
// push() pop() let stack = []; console.log(stack.push(1, 2), stack); // 长度为2 [ 1, 2 ] console.log(stack.push(3), stack); // 长度为3 [ 1, 2, 3 ] console.log(stack.push([11, 22, 33,]), stack); // 长度为4 [ 1, 2, 3, [ 11, 22, 33 ] ] console.log(stack.pop(), stack); // 取出最后一个元素[ 11, 22, 33 ] 当前stack 为: [ 1, 2, 3 ]
unshift和shift
unshift和shift 与push和pop类似只是push和pop在尾部操作unshift和shift在头部操作
let stack = []; console.log(stack.unshift(1, 2), stack); // 长度为2 [ 1, 2 ] console.log(stack.unshift(3), stack); // 长度为3 [ 3, 1, 2 ] console.log(stack.unshift([11, 22, 33,]), stack); // 长度为4 [ [ 11, 22, 33 ], 3, 1, 2 ] console.log(stack.shift(), stack); // 取出最前一个元素[ 11, 22, 33 ] 当前stack为: [ 3, 1, 2 ]
ECMAScript5中数组方法
forEach方法从头到尾遍历数组,为每个元素调用指定函数
let stack = [1, 2, 3, 4, 5, 6]; let sum = 0; stack.forEach(function (value) {sum += value}); // sum 累加数组元素里面的值 console.log(sum); // 输出sum的值 // forEach()使用三个参数: 数组元素/元素的索引/数组本身 stack.forEach(function (v, i, a) {a[i] = v + 1}); console.log(stack);
注意forEach无法在所有元素都传递给调用的函数之前终止遍历,简而言之就是活没干完不许停,如果中途想停止需要放在一个异常处理里
map()方法将调用的数组每个元素传递给指定的函数,并返回一个数组,它包含该函数的返回值
// map() let testArrayA = [1, 2, 3, 4, 5]; let testArrayB = testArrayA.map(function (value) { return value * value }); console.log(testArrayB); // 输出结果是:[ 1, 4, 9, 16, 25 ]
filter()筛选方法
// filter let testArray = [1, 2, 3, 4, 5]; console.log(testArray.filter(function (value){return value < 3})); // [ 1, 2 ] console.log(testArray.filter(function (value, i){return i%2===0})); // [ 1, 3, 5 ] // 也可以使用它来排除空元素 let testArrayA = [1, 2, 3, 4, null, undefined, 5, 6]; console.log(testArrayA.filter(function (value) { return value !== null && value !== undefined })); // [ 1, 2, 3, 4, 5, 6 ]
every()和some()判断数组元素
every()和some()方法是数组的逻辑判定,它们对数组元素应用指定的函数进行判定,返回true或false
every针对所有当所有元素满足条件为true,任意一个不满足返回false, some与every想法只要有一个满足即可
// every() some() let a = [1, 2, 3, 4, 5, 6, ]; console.log(a.every(function (value){return value > 10})); // false 判断条件为a数组对象中所有元素都大于10 console.log(a.every(function (value){return value > 0})); // true 判断条件为a数组对象中所有蒜素都大于0 console.log(a.some(function (value) { return value > 3})); // true 判断条件为a数组中有对象大于3 console.log(a.some(function (value) { return value > 7})); // false 判断条件为a数组中有对象大于7 一个都没有返回false
reduce()和reduceRight() 指定的函数将数组元素进行组合
这在函数式编程中是常见的操作,也可以称为“注入”和“折叠”
// reduce 需要两个参数,第一个是执行简化操作的函数。第二个(可选)的参数是一个传递给函数的起始值 let a = [1, 2, 3, 4, 5,]; console.log(a.reduce(function (x, y){return x+y}, 0)); // 数组求和 15 console.log(a.reduce(function (x, y){return x*y}, 1)); // 数组求和 120 console.log(a.reduce(function (x, y){return (x>y)?x:y;},)); // 数组求最大值 5 /* reduce() 使用的函数与forEach()和map()使用不同,如果指定第二个参数的情况下也就是其市值情况下 (x, y)的值x 是每次操作后的累加值, y 就是第一个元素的值 拿第一个举例 console.log(a.reduce(function (x, y){return x+y}, 0)); 第一次操作: x = 0 , y = 1 相加结果为1 第二次操作: x = 1 , y = 2 这里需要注意的是这次 x 为上一次的结果, y 为第二个元素依次累加 如果没有起始值,默认的x为第一个元素,y 为第二个元素 */
reduceRight() 和reduce操作一样知识现在开始顺序从右到左
indexOf()和lastindexOf()
获取元素索引,indexOf从左边开始查找计算索引值,lastindexOf从右边开始查找计算索引值
let a = [0, 1, 2, 1, 0]; console.log(a.indexOf(1)); // 从左边开始找元素值为1的下标为: 1 console.log(a.lastIndexOf(1)); // 从右边开始找元素值为1的下标为: 3 // 不管从哪个方向找第一次出现的元素下标顺序一直是从左到右 console.log(a.indexOf(3)); // 从左边开始查找元素为3的小标为:值不存在返回-1
函数
函数定义
函数名称、圆括号(参数)、花括号{实际执行的操作}
如果函数挂载在一个对象上,作为对象的属性,它就称之为对象方法。
参数:形参(parameter)和实参(argument),形参相当函数中定义的变量,实参是在运行时的函数调用时传入的参数
函数调用
凡是没有形参的都可以省略圆括号
let a = new Object(); let a = new Object;
间接调用
JavaScript中函数也是对象,和其他JavaScript对象没什么两样,函数对象也可以包含方法,其中两个方法call()和apply()可以用来间接地调用函数
函数的形参和实参
ECMAScript6中支持默认参数了
// 支持默认参数 function testFunc(x = 1, y = 2){ this.x = x; this.y = y; console.log(this.x, this.y) } testFunc();
将对象属性用作实参
类似python中传字典的key:value
// 定义一个函数接收指定参数 function arrayCopy(/* array */ from, /* index*/ from_start = 0 , /* array */ to, /* index*/ to_start = 0 , /* integer */ length) { /* 逻辑代码*/ } // 定义一个函数接收对象属性 function easyCopy(args){ arrayCopy(args.from, args.from_start, args.to, args.to_start, args.length, ) } let a = [1, 2, 3, 4,] , b = []; easyCopy({'from': a, 'to': b, 'length': 4}); console.log(a, b);
JavaScript真是一个畸形语言waht's the fuck
类和原型
构造函数和原型对象,参考本文“对象-原型对象”
在JavaScript中类也是一个对象,它的创建是通过原型对象来创建的
// 定义一个类 开头大写 function Person(x, y) { // 实例化两个对象属性不共享保存在各自的实例中 this.x = x; this.y = y; } // 原型重写, 这里面的属性和方法是共享的 Person.prototype = { constructor : Person, // 指定原型对象,也就是对象创建者为Person personMethod1: function () { return 'hello 帅哥' }, 'key1': 'v1', 'key2': 'v2', }; let person1 = new Person(1, 2); console.log(person1.personMethod1());
在ES6以后出现了Class但是本质上class可以看做一个语法糖,只是让原型更加清晰而已
ES6 的类,完全可以看作构造函数的另一种写法。
class Point { // ... } console.log(typeof Point); // "function" 本质上是一个函数 console.log(Point === Point.prototype.constructor); // true 原型
构造函数的prototype
属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype
属性上面。
class Person{ constructor(){} toValue() {} toAdd() {} } // 等同于 Person.prototype = { constructor(){}, toValue() {}, toAdd() {}, };
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this
对象上),否则都是定义在原型上(即定义在class
上)。
//定义类 class Person { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } addMethod(){ return '(' + this.x + ', ' + this.y + ')'; } } let person1 = new Person(2, 3); console.log(person1.toString()); // (2, 3) console.log(person1.hasOwnProperty('x')); // true console.log(person1.hasOwnProperty('y'));// true console.log(person1.hasOwnProperty('toString')); // false console.log(person1.__proto__.hasOwnProperty('toString')); // true console.log(person1.__proto__.hasOwnProperty('addMethod')); // true
类的静态方法和属性
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Tools{ static makeWorker(){ return "Hello World" } } console.log(Tools.makeWorker()); // 静态方法可以直接通过类调用
感觉越来越像python了,不过JavaScript在升级的过程中不断吸取其他语言的专长也是好事 已入坑
静态属性指的是 Class 本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
class Foo { } Foo.prop = 1; Foo.prop // 1
上面的写法为Foo
类定义了一个静态属性prop
。
目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。
es7 可以直接声明了不加this~~
![](https://images.cnblogs.com/cnblogs_com/luotianshuai/1009142/o_o_Warning.png)