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 // true
person 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 指向了 Person
Person.prototype = new Animal();
// 这样导致了继承链混乱,所以将 constructor 改回 Person
Person.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;
}