JavaScript 基础知识

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;
}
    原文作者:OreChou的小号
    原文地址: https://www.jianshu.com/p/27da89f64abe
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞