JS 基础
原始数据类型:
Number、String、Boolean、Symbol、Undefined、Null、BigInt引用数据类型:
Objecttypeof的返回值:jstypeof 1; // ===> 'number' typeof '1'; // ===> 'string' typeof true; // ===> 'boolean' const symbolVal = Symbol(1); typeof symbolVal; // ===> 'symbol' typeof undefined; // ===> 'undefined' typeof null; // ===> 'object' *** (null 表示空对象指针) const func = function () {}; typeof func; // ===> 'function' const arr = [1, 2, 3]; typeof arr; // ===> 'object' const obj = { a: 1 }; typeof obj; // ===> 'object'在
Array和Object无法用typeof区分的情况下,可以借助 静态方法Array.isArray()和Object.prototype.toString()方法进行判定:jsconst arr = [1, 2, 3]; const obj = { a: 1 }; // 1. 方案一 Array.isArray(arr); // true Array.isArray(obj); // false // 2. 方案二 Object.prototype.toString.call(arr); // '[object Array]' Object.prototype.toString.call(obj); // '[object Object]'判断某个变量是一个
数组jsconst arr = [1, 2, 3];Array.isArray()jsArray.isArray(arr); // truetoStringjsObject.prototype.toString.call(arr) === '[object Array]'; // trueisPrototypeOf()jsArray.prototype.isPrototypeOf(arr); // trueconstructorjs// 不推荐直接访问 __proto__ 属性 // arr.__proto__.constructor === Array; // true Object.getPrototypeOf(arr).constructor === Array; // true // 或者 arr.constructor === Array; // trueinstanceofjsarr instanceof Array; // true 一般不推荐使用
this 指向
- 全局环境中的
this指向全局对象(window)(ES5 也是) - 函数中的
this,由调用函数的方式来决定- 如果函数是独立调用,在严格模式下,
this指向undefined,而不是全局对象;非严格模式下,this指向window - 如果函数是被某个对象调用,那
this指向被调用的这个对象。
- 如果函数是独立调用,在严格模式下,
- 构造函数里的
this及原型里的this对象,指向的都是生成的实例,即由new决定的
- 全局环境中的
new 的作用:
- 创建一个新对象
- 将构造函数的 this 指向这个新对象
- 返回这个新对象
实现
new操作符方法js// 我们协商第一个参数传递我们需要生成实例的构造函数, // 构造函数需要的参数,后面依次传入即可 function newFunc(Fn, ...args) { if (typeof Fn !== 'function') { throw new Error('The first param is not a function'); } if (!Fn.prototype) { // 假如 Fn 是个箭头函数 throw new Error("The function doesn't have the prototype property"); } // 创建一个对象,并将它的隐式原型指向 构造函数 Fn 的原型 const obj = Object.create(Fn.prototype); const res = Fn.call(obj, ...args); // const res = Fn.apply(obj, [...args]) // 存在 Fn 函数自己返回一个对象(原型指向 Object.prototype) return typeof res === 'object' ? res : obj; }
原型、原型链
原型
JavaScript常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个 原型对象,对象以其原型为模板、从原型继承方法和属性
所有的对象都是通过
new 函数来创建的,
即使是const arr = []的写法,本质上是通过new Array()来创建的;
function fn() {}的写法,本质上是通过new Function()来创建的;
所有的函数,本质上也是对象
所有的函数「非箭头函数」都有一个属性:
prototype,它被称之为 函数原型- 默认情况下,
prototype是一个普通的Object对象 - 函数原型里面有一个属性
constructor,默认情况下它指向 构造函数本身,如下:
jsclass Person { constructor(name) { this.name = name; } } function Man(name) { this.name = name; } // 可以得到: // Person.prototype.constructor === Person 为 true // Man.prototype.constructor === Man 为 true const p = new Person('Saul'); const m = new Man('Saul');引申问题:下面的代码,变量
p的构造函数是什么?jsfunction Person() { return {}; } const p = new Person(); // 结论:由于构造函数 Person 返回了一个对象 // new 操作符得到的对象会是其返回的结果 // 因而会将 p 的构造函数指向 Object // 那么就有: // p.constructor === Object 为 true // p.constructor === Person 为 false- 默认情况下,
所有的对象都有一个属性:
__proto__,称之为隐式原型;默认情况下,它指向创建该对象的函数的原型,从而就有:jsp.__proto__ === Person.prototype; // true
对象的原型(可以通过
Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)与构造函数的prototype属性之间的区别是很重要的。前者是 每个实例 上都有的属性,后者是 构造函数 的属性
如下关系图所示:

原型链
原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推,这种关系常被称为 原型链 (prototype chain)
特殊点:
Function的隐式原型指向自己的原型:jsFunction.__proto__ === Function.prototype; // true Person.__proto__ !== Person.prototype; // trueObject原型的隐式原型指向null:jsObject.prototype.__proto__ === null;
关系图如下:

面试题:
function User(name) {
this.name = name;
}
User.prototype.sayHello = function () {
console.log(`Hello ${this.name}`);
};
const u1 = new User('Saul');
const u2 = new User('K.');
console.log(u1.sayHello === u2.sayHello); // true
console.log(User.prototype.constructor); // User Function
console.log(User.prototype === Function.prototype); // false
console.log(User.__proto__ === Function.prototype); // true
console.log(User.__proto__ === Function.__proto__); // true
console.log(u1.__proto__ === u2.__proto__); // true
console.log(u1.__proto__ === User.__proto__); // false
console.log(Function.__proto__ === Object.__proto__); // true
console.log(Function.prototype.__proto__ === Object.prototype.__proto__); // false
console.log(Function.prototype.__proto__ === Object.prototype); // true原型链的应用
基础方法
W3C 不推荐直接使用系统成员属性 __proto__
获取对象的隐式原型:
Object.getPrototypeOf(对象)(Object 对象的静态方法)jsconst person = new Person('Saul'); // 获取 person 对象的 __proto__ 属性值 // 得到的结果等价于: person.__proto__ Object.getPrototypeOf(person);判断当前对象是否在指定对象的原型链上:
Object.prototype.isPrototypeOf(当前对象)jsconst person = new Person('Saul'); const obj = {}; const arr = []; // Object原型是否在 person 实例的原型链上 Object.prototype.isPrototypeOf(person); // true Array.prototype.isPrototypeOf(arr); // true // obj 是否在 person 实例的原型链上 obj.isPrototypeOf(person); // false person.isPrototypeOf(obj); // false // 得到 obj 的隐式原型,这里即是 Object原型 Object.getPrototypeOf(obj).isPrototypeOf(person); // true判断某个对象的原型链上有没有目标函数的原型:
对象 instanceof 函数jsconst obj = {}; const arr = []; obj instanceof Object; // true arr instanceof Array; // true 一般不推荐此方案从指定原型去创建一个新对象:
Object.create(原型)jsconst person = new Person('Saul'); const obj = Object.create(person); // 则 person 实例是对象 obj 的原型 person.isPrototypeOf(obj); // true // 那么 obj.__proto__ 就没有 constructor 属性了,但访问时会沿着 // 原型链继续向上寻找,得到 Person 构造函数判断对象是否具有自身的属性:
Object.prototype.hasOwnProperty(属性名)jsconst person = new Person('Saul'); const o = Object.create(person); person.hasOwnProperty('name'); //true o.hasOwnProperty('name'); // false // 我们在遍历对象时,也会循环出原型链上的属性 o.age = 20; o.city = 'Shenzhen'; for (const prop in o) { // console.log(prop) // 这会打印出: // age, city, name // 我们如果加一层判断 if (o.hasOwnProperty(prop)) { console.log(prop); // 只会打印出对象 o 自身的属性: // age, city } }在 Javascript 中,
属性由一个字符串类型的“名字”(name)和一个“属性描述符”(property descriptor)对象构成这里你会发现 原型链 上的属性有那么多,为什么
for...in...循环遍历出来的属性却很少呢?这就跟 属性描述符 有关了我们知道
vue2的响应式原理就是基于Object.defineProperty()来进行数据劫持的jsObject.defineProperty(o, 'x', { value: 'x的值', // 与 get & set 不能同时存在 writable: false, // 当前属性是否可以被重写覆盖 configurable: false, // 当前属性是否可以重新配置,是否能被删除 enumerable: false, // 当前属性是否可被枚举(也就是能否被遍历出来) get() {}, set(val) {}, });我们可以通过
Object.getOwnPropertyDescriptor(obj, prop)来获取目标对象(obj)目标属性(prop)的属性描述符对象
应用
类数组转换为真数组:
Array.prototype.slice.call(类数组),ES6 新增Array.from()也可以实现jsconst nodeList = document.getElementsByTagName('*'); const nodeArr = Array.prototype.slice.call(nodeList); Array.isArray(nodeList); // false Array.isArray(nodeArr); // trueES5 实现 继承 (ES6 有
class ... extends):jsfunction inherit(Son, Father) { // 以父级原型创建对象 Son.prototype = Object.create(Father.prototype); // 将子类原型 constructor 指向 子类(子函数) Son.prototype.constructor = Son; // 添加 uber 属性,方便访问父类原型 Son.prototype.uber = Father.prototype; // 或者与大家协商,改写为: // Son.prototype.uber = Father // 方便子类调用 this.uber() 来初始化,而不用手动调用 父类.call } // 旧版的继承实现方案 /** * const inherit = (function() { * // 通过闭包声明一个可重用的空函数 * const F = function () {} * return function (Son, Father) { * F.prototype = Father.prototype * Son.prototype = new F() * Son.prototype.constructor = Son * Son.prototype.uber = Father.prototype * } * }()) */ // 使用 // User 类 function User(firstName, lastName, age) { this.firstName = firstName; this.lastName = lastName; this.age = age; this.fullName = this.firstName + ' ' + this.lastName; } User.prototype.sayHello = function () { console.log(`Hello! 我是${this.fullName}, 今年${this.age}岁`); }; // VipUser 类 function VipUser(firstName, lastName, age, money) { // this.firstName = firstName // this.lastName = lastName // this.age = age // this.fullName = this.firstName + ' ' + this.lastName // 为避免上述重复代码的书写,我们可以使 VipUser 继承自 User User.call(this, firstName, lastName, age); this.money = money; } inherit(VipUser, User); // 先继承,再添加自定义原型方法 VipUser.prototype.upgrade = function () { this.money -= 10; console.log('已升级,消耗 ¥10'); }; const u = new User('S', 'K', 20); const vUser = new VipUser('Saul', 'K', 20, 100); u.sayHello(); // ... u.upgrade(); // ... is not a function vUser.sayHello(); // ... vUser.upgrade(); // 已升级,消耗 ¥10
函数柯里化
/**
* @description: 通用柯里化工具函数
* @param {Function} fn 待柯里化的函数
* @param {array} args 已经接收的参数列表
* @return {Function}
*/
const currying = function (fn, ...args) {
// fn需要的参数个数
const len = fn.length;
// 返回一个函数接收剩余参数
return function (...params) {
// 拼接已经接收和新接收的参数列表
const _args = [...args, ...params];
// 如果已经接收的参数个数还不够,继续返回一个新函数接收剩余参数
if (_args.length < len) {
return currying.call(this, fn, ..._args);
}
// 参数全部接收完调用原函数
return fn.apply(this, _args);
};
};
function add(a, b, c, d) {
return a + b + c + d;
}
const curryingAdd = currying(add);
// 我们就可以像下面这种方式进行调用
// 参数不够的情况下,执行结果的返回值还是一个函数
curryingAdd(1)(2)(3)(4); // => 10
curryingAdd(1, 2)(3)(4); // => 10
curryingAdd(1, 2, 3)(4); // => 10