JS 基本数据类型
Undefined、Null、Boolean、String、Number
ES6 新增 Symbol,表示独一无二的值,凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突
Null 和 Undefined 的区别:
null:表示一个对象是“没有值”的值,即值为“空”;typeof 为 object
undefined:表示一个声明但没有初始化的变量;typeof 为 undefined
JS 的数据类型、及内存位置
栈:原始数据类型(Undefined、Null、Boolean、Number、String)
堆:引用数据类型(对象、数组、函数)
JS 内置对象
数据封装对象:Object、Array、Boolean、Number、String、Map、WeakMap、Set、WeakSet
其他对象:Function、Arguments、Math、Date、RegExp、Error
Object 是 JS 中所有对象的父对象
原型、原型链
每个对象都会在其内部初始化一个属性,就是 prototype (原型)
当我们访问一个对象的属性时, 如果这个对象内部不存在这个属性,那么他就会去 prototype 里找这个属性,这个prototype 又会有自己的 prototype , 于是就这样一直找下去,这样的一个链型结构就是原型链
let obj = Object.create(null)
这种方式创建的对象没有原型。使用的情景:
你需要一个非常干净且高度可定制的对象当作数据字典的时候;
想节省 hasOwnProperty 带来的一丢丢性能损失并且可以偷懒少些一点代码的时候
JS 创建对象的方式
工厂模式
function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; o.sayName = function () { alert(this.name); } return 0; }var person = createPerson('OreChou', 23);
所创建的对象的原型都为 Object 。这种方式无法解决对象识别问题,即使用 instance 和 typeof 的时候,其值为 Object。
构造函数模式
// 此处的函数名首字母为大写,约定构造函数的首字母为大写function Person(name, age) { this.name = name; this.age = age; // 方法会在每个实例上重新创建一遍 this.sayName = function() { alert(this.name); } // 上面的代码与下面的等价 // 相当于每次新建实例,都会创建一个函数,函数也是对象。所以有性能的开销 this.sayAge = new Function("alert(this.age)"); }var person = new Person('OreChou', 23); person.constructor === Person // trueperson instanceof Person // true
调用构造函数会经历以下步骤:
以该函数为原型创建一个新对象
将函数的 prototype 赋值给对象的 proto 属性
将函数的作用域赋给新对象(即 this 指向了该新对象),执行函数中的代码
若有 return(且不为基础类型)则返回 return 的内容,没有则返回新对象
缺点:
每个方法都会在实例上重新创建一遍(解决:把这种方法改成全局的,或者使用原型)
构造函数 + 原型模式
function Person(name, age) { this.name = name; this.age = age; } Person.prototype = { // 这里指定了构造函数为 Person // 若不指定,则 Person 的原型为 Object constructor: Person, sayName: function() { alert(this.name); } } Person.prototype.sayAge = function() { alert(this.age); }var person = new Person('OreChou', 23); Person.prototype.isPrototypeOf(person) // true
每一个函数都有一个 prototype 属性,属性为一个指针,指向一个对象。该对象可以包含该特定类型的所有实例共享的属性与方法。
构造函数的 prototype 属性,实例的 [[prototype]] 属性(Chrome、Safari、Firefox中每个实例对象上的属性 proto ),都指向函数的原型对象。
使用 Class
class Person { // 等价于 Person 的构造函数 // 除 constructor 外没有其他方法的保留名 constructor(name, age) { this.name = name; this.age = age; } // 等价于 Person.prototype.sayName // 类中所有方法都是不可枚举的 sayName() { console.log(this.name); } }// 上面和下面两种方法等价let Person = (function() { 'use strict'; const Person = function(name, age) { if (typeof new.target === 'undefined') { throw new Error('必须通过关键字 new 调用函数'); } this.name = name; this.age = age; } Object.defineProperty(Person.prototype, 'sayName', { value: function() { if (typeof new.target !== 'undefined') { throw new Error('不可以使用关键字 new 调用该方法'); } console.log(this.name); }, // 当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。 enumerable: false, // 当且仅当该属性的 writable 为 true 时,value 才能被赋值运算符改变。默认为 false。 writable: true, // 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。 configurable: true }) return Person; })
使用类的注意事项:
类声明与 let 类似,不能被提升
类中的所有代码自动运行在严格模式下
类中所有方法都是不可枚举的
类中有一个名为 constructor 的方法,且只能使用 new 调用,否则报错
类中其他的方法不能使用 new 调用,否则报错
JS 实现继承的方式
假设有如下的一个 Animal 类
function Animal() { this.species = '动物'}
构造函数继承
function Person(name, age) { // 使用call或apply方法,将父对象的构造函数绑定在子对象上 Animal.apply(this, arguments); this.name = name; this.age = age; }
原型继承
function Person(name, age) { this.name = name; this.age = age; }// (1)// 将 Person 的原型对象指向 Animal 的实例 (这种方式因为新增了 Animal 的一个实例,所以消耗了内存)// 此时的 Person.prototype.constructor 指向了 PersonPerson.prototype = new Animal();// 这样导致了继承链混乱,所以将 constructor 改回 PersonPerson.prototype.constructor = Person;// (2)// 这种方式不会新增内存,但是对 Person 原型的修改会反应到 Aniaml 的原型上Person.prototype = Animal.prototype// (3)// 利用空对象作为中介。F 是空对象,几乎不占据内存,且修改 Person 的原型,不会反应到 Animal 的原型上var nullObj = function() {}; F.prototype = Animal.prototype; Person.prototype = new F(); Person.prototype.constructor = Person;
拷贝继承
实现一个拷贝函数,将父对象的所有属性方法拷贝到子对象
// 浅拷贝的实现function shallowCopy(p) { var c = {}; for (var i in p) { // js 的对象是一个指针,指向了该对象在内存中的地址 // 所以这里如果属性是一个对象的话,其实只拷贝了该对象的地址 // 那么拷贝后的对象对该属性的修改,会影响到原对象 c[i] = p[i]; } return c; }// 深拷贝的一个简单实现function deepCopy(p) { // 确定 p 为对象还是数组 var c = Array.isArray(p) ? [] : {}; if (p && typeof p === 'object') { for (var i in p) { // 如果属性是对象,则递归拷贝 if (p[i] && typeof p[i] === 'object') { c[i] = deepCopy(p[i]); } else { c[i] = p[i]; } } } return c; }
作者:OreChou的小号
链接:https://www.jianshu.com/p/27da89f64abe