JavaScript.Part.5 对象与 this

1、JavaScript数据类型

在 JavaScript 中,数据类型可以分为两大类:基本数据类型(原始数据类型)和引用数据类型。

1.1 基本数据类型

基本数据类型是不可变的,包含以下几种:

  • Number: 表示数字,包括整数和浮点数。

    let num = 42;
    
  • String: 表示字符串,文本数据。

    let str = "Hello, World!";
    
  • Boolean: 表示布尔值,只有两个值:truefalse

    let isTrue = true;
    
  • Undefined: 表示未定义的值,变量声明但未赋值时的默认值。

    let x;
    console.log(x); // 输出: undefined
    
  • Null: 表示空值或无值,表示“无”或“空”。

    let emptyValue = null;
    
  • Symbol: ES6 引入的唯一且不可变的值,通常用于对象属性的唯一标识符。

    let sym = Symbol('description');
    
  • BigInt: 用于表示大于 Number.MAX_SAFE_INTEGER 的整数。

    let bigIntValue = BigInt(123456789012345678901234567890);
    

1.2 引用数据类型

引用数据类型是可变的,主要包括:

  • Object: 对象是键值对的集合,可以包含多个值和功能。

    let obj = { name: "Alice", age: 25 };
    
  • Array: 数组是特殊类型的对象,用于存储有序的值。

    let arr = [1, 2, 3, 4, 5];
    
  • Function: 函数也是对象,可以被调用。

    function myFunction() {
        return "Hello!";
    }
    

1.3 总结

JavaScript 的数据类型包括:

  • 基本数据类型: Number, String, Boolean, Undefined, Null, Symbol, BigInt
  • 引用数据类型: Object, Array, Function

2、数据类型检测

在 JavaScript 中,可以使用多种方法来检查数据的类型。以下是一些常用的方法:

2.1 typeof 操作符

typeof 是最常用的方法,可以检查基本数据类型和函数类型。

console.log(typeof 42);            // "number"
console.log(typeof "Hello");       // "string"
console.log(typeof true);          // "boolean"
console.log(typeof undefined);     // "undefined"
console.log(typeof null);          // "object" (这是一个历史遗留问题)
console.log(typeof Symbol());      // "symbol"
console.log(typeof BigInt(123));   // "bigint"
console.log(typeof function(){});  // "function"
console.log(typeof []);            // "object" (底层设计问题)

typeof null == 'object'
不同类型数据在底层都表示为二进制,在js中二进制前三位都为0的话会被判断为object类型,
null的二进制表示是全0,自然前三位也是0
所以执行typeof时会返回object

2.2 instanceof 操作符

instanceof 用于检查对象是否是某个构造函数的实例,适用于引用数据类型。

let arr = [1, 2, 3];
console.log(arr instanceof Array);  // true

let obj = {};
console.log(obj instanceof Object);  // true

function MyClass() {}
let myInstance = new MyClass();
console.log(myInstance instanceof MyClass); // true

2.3 Array.isArray()

专门用于检查一个值是否为数组。

let arr = [1, 2, 3];
console.log(Array.isArray(arr));  // true

let notArr = {};
console.log(Array.isArray(notArr));  // false

2.4 Object.prototype.toString.call()

这种方法可以更准确地检查数据类型,尤其是对于 null 和数组。

console.log(Object.prototype.toString.call(42));            // "[object Number]"
console.log(Object.prototype.toString.call("Hello"));       // "[object String]"
console.log(Object.prototype.toString.call(true));          // "[object Boolean]"
console.log(Object.prototype.toString.call(undefined));     // "[object Undefined]"
console.log(Object.prototype.toString.call(null));          // "[object Null]"
console.log(Object.prototype.toString.call([]));            // "[object Array]"
console.log(Object.prototype.toString.call({}));            // "[object Object]"
console.log(Object.prototype.toString.call(function(){}));  // "[object Function]"
console.log(Object.prototype.toString.call(Symbol()));      // "[object Symbol]"
console.log(Object.prototype.toString.call(BigInt(123)));   // "[object BigInt]"

2.5 总结

  • 使用 typeof 检查基本数据类型和函数。
  • 使用 instanceof 检查对象的构造函数。
  • 使用 Array.isArray() 检查数组。
  • 使用 Object.prototype.toString.call() 进行更精确的类型检查。
//精确类型检测
function dataTypeof(data){
	let type = Object.prototype.toString.call(data);
	return type.match(/[A-Z][a-z]*/g)[0];
}

3、严格模式

在 JavaScript 中,严格模式(Strict Mode)是一种通过在代码中添加特定指令来启用的模式,它可以帮助你编写更安全和更高效的代码。使用严格模式可以避免一些常见的错误,并且会使某些不安全的操作抛出错误。

如何启用严格模式

你可以在 JavaScript 文件的顶部或在函数内部启用严格模式。以下是两种常见的方法:

1. 在全局作用域中启用严格模式

在 JavaScript 文件的最顶部添加 "use strict";

"use strict";

function myFunction() {
    // 这里的代码在严格模式下运行
}
2. 在函数内部启用严格模式

在特定函数内部添加 "use strict";

function myFunction() {
    "use strict";
    // 这里的代码在严格模式下运行
}

严格模式的效果

启用严格模式后,以下行为会受到限制或抛出错误:

  • 禁止使用未声明的变量。
  • 禁止删除变量、对象或函数的名称。
  • 禁止使用 this 指向全局对象(在函数中 thisundefined)。
  • 禁止重复参数名。
  • 禁止使用某些保留字(如 evalarguments)。

示例

"use strict";

function example() {
    x = 3.14; // 报错:未声明的变量
}

example();

在这个例子中,尝试使用未声明的变量 x 会导致错误,因为严格模式不允许这种行为。

总结

  • 使用 "use strict"; 来启用严格模式。
  • 可以在全局作用域或函数内部启用。
  • 严格模式有助于捕获错误并提高代码的安全性。

4、this绑定规则

主要先讨论普通函数,即使用function关键字定义的函数。普通函数的this取决于函数的调用方式(调用时的上下文)

4.1 默认绑定

  • 普通函数调用时,如果未绑定任何对象,则默认绑定全局对象(浏览器中即window)
    • 但在严格模式下,默认绑定undefined
function fn(){
  console.log(this);
}
fn();  // Window {...}
"use strict";
function fn(){
  console.log(this);
}
fn();  // undefined
function fn1(){
  function fn2(){
    console.log(this);
  }
  fn2();
}
fn1();  // Window {...}
  • fn2虽然定义在局部作用域中,但是调用时未绑定任何对象,也会默认绑定window
  • 虽然this绑定了window,但是在window对象上是找不到fn2函数的,
  • 函数的可见性只与定义时的作用域有关,fn2定义在局部作用域,只能在局部作用域查找到和调用

4.2 隐式绑定

  • 当函数赋值到对象中时,函数的this指向直接调用它的对象
function fn(){
  console.log(this.a);
}
var a = 1;
var obj1 = {a:2, fn};
var obj2 = {a:3, obj1};
fn();            // 1  //this指向window
obj1.fn();       // 2  //this指向obj1
obj2.obj1.fn();  // 2  //this指向obj1
  • 当进行函数赋值时,会导致绑定丢失,而应用默认绑定
function fn(){
  console.log(this.a);
}
var a = 1;
var obj1 = {a:2, fn};
var fn1 = obj1.fn;
fn1();           // 1  //this指向window
  • fn1 = obj1.fn 其实只是做了一次地址传值,fn1就是fnfn1 === fn 和直接调用 fn() 没区别

4.3 显式绑定

  • 使用call, apply, bind等方法显示指定一个对象绑定this
function fn(b, c){
  console.log(this.a, b, c);
}
var a = 1;
var obj = {a:2};
fn.call(obj,3,4);     // 2 3 4
fn.apply(obj,[3,4]);  // 2 3 4
let fn1 = fn.bind(obj,3,4);
fn1();                // 2 3 4
let fn2 = fn.bind(obj);
fn2(5,6);                // 2 5 6
  • 如果绑定对象为nullundefinedcall, apply, bind会忽略这些值,实际还是默认绑定
function fn(){
  console.log(this.a);
}
var a = 1;
fn.call(null);       // 1
fn.call(undefined);  // 1

callapplybind 是 JavaScript 中用于改变函数 this 上下文的三种方法。它们的主要区别如下:

1. call
  • 用法func.call(thisArg, arg1, arg2, ...)
  • 功能:调用函数并指定 this 的值,后面可以传入参数。
  • 示例
function greet(greeting) {
    console.log(`${greeting}, ${this.name}`);
}

const person = { name: 'Alice' };
greet.call(person, 'Hello');  // 输出: "Hello, Alice"
2. apply
  • 用法func.apply(thisArg, [argsArray])
  • 功能:与 call 类似,但参数以数组的形式传入。
  • 示例
function greet(greeting) {
    console.log(`${greeting}, ${this.name}`);
}

const person = { name: 'Bob' };
greet.apply(person, ['Hi']);  // 输出: "Hi, Bob"
3. bind
  • 用法const boundFunc = func.bind(thisArg, arg1, arg2, ...)
  • 功能:返回一个新的函数,this 被永久绑定到指定的值,参数可以预设。
  • 示例
function greet(greeting) {
    console.log(`${greeting}, ${this.name}`);
}

const person = { name: 'Charlie' };
const greetCharlie = greet.bind(person);
greetCharlie('Hey');  // 输出: "Hey, Charlie"

4.4 new绑定

  • 使用new 关键字调用函数,会构造一个新对象,将函数的this绑定到新对象,并返回这个对象
function fn(a) {
	this.a = a;
    console.log(this);
}
let obj = new fn(3); // fn {a: 3}  //fn变成了一个类
console.log(obj.a); // 3

new 创建对象的流程:

    1. 创建一个新的空对象
    1. 设置新对象的[[Prototype]]原型为构造函数的原型
    1. 绑定构造函数的this为新对象
    1. 执行构造函数,如果构造函数没有返回对象,则将新对象返回

4.5 间接引用

function fn() {
    console.log(this.a);
}
var a = 1;
let obj1 = {a:2, fn};
let obj2 = {a:3};
fn();				// 1
obj1.fn();			// 2
(obj2.fn = obj1.fn)();	// 1
obj2.fn();			// 3
  • 赋值表达式也是有返回值的,返回值就是要赋的值的原始引用
  • (obj2.fn = obj1.fn) 的返回值就是fn函数的引用,(obj2.fn = obj1.fn)() 其实就是在执行 fn(),根据默认绑定原则,this指向window
  • obj2.fn === fn 这是成立的,但是就是fn()obj2.fn()的调用方式不同,导致this绑定不同

4.6 箭头函数的this

箭头函数()=>{}即不使用function关键字定义的无名函数。箭头函数的this取决于函数定义时的上下文

function fn1() {
    console.log(this);
}
let fn2 = ()=>{
	console.log(this);
}
fn1();	// Window {...}
fn2();	// Window {...}
  • 虽然fn1()fn2()都会指向window,但内部原因却不一样
  • fn1() 是因为调用时未绑定任何对象,默认绑定window
  • fn2() 是因为定义箭头函数时所在上下文是window,所以指向window
function fn1() {
	let fn2 = ()=>{
		console.log(this);
	}
    fn2();
}
fn1();	// Window {...}
  • fn2 定义在fn1 的局部作用域中,所以fn2this 指向fn1this
  • fn1 因为调用时未绑定任何对象,this 默认绑定window
  • fn2this 也就指向window
let obj = {
  fn1:function(){
    console.log(this);
  },
  fn2:()=>{
    console.log(this);
  }
}
obj.fn1();	// {fn1: ƒ, fn2: ƒ}
obj.fn2();	// Window {...}
  • obj.fn1 调用时隐式绑定了obj,所以this 指向obj
  • obj.fn2 虽然是在给obj定义函数,但定义操作还是在全局上下文中执行的,所以this 指向window
let obj = {
  fn1:function(){
    let fn2 = ()=>{
		console.log(this);
	}
	fn2();
  }
}
obj.fn1();	// {fn1: ƒ}
  • fn2 定义在fn1 的局部作用域中,所以fn2this 指向fn1this
  • fn1 因为调用时隐式绑定obj对象,this 指向obj
  • fn2this 也就指向obj

5、对象深层用法

5.1 存在性

检测属性是否存在于对象上。

let obj = { a: 1 };
obj.__proto__.b = 2;
'a' in obj;		// true
'b' in obj;		// true
obj.hasOwnProperty('a');		// true
obj.hasOwnProperty('b');		// false
  • in 操作符会检查属性是否在对象及其[[Prototype]]原型链中
  • hasOwnProperty 只会检查属性是否在当前对象中

5.2 Object.defineProperty()

Object.defineProperty() 是 JavaScript 中的一个方法,用于在对象上定义一个新属性,或修改现有属性的特性。它允许你精确控制属性的行为。

语法

Object.defineProperty(obj, prop, descriptor);
  • obj:要在其上定义属性的对象。
  • prop:要定义或修改的属性的名称。
  • descriptor:一个描述符对象,包含属性的特性。

属性描述符

描述符对象可以是以下几种类型:

  1. 数据描述符:具有值和可写性。

    • value:属性的值。
    • writable:布尔值,指示属性是否可被修改。
    • enumerable:布尔值,指示属性是否可被枚举。
    • configurable:布尔值,指示属性是否可被删除或修改其特性。
  2. 访问器描述符:具有 getter 和 setter。

    • get:一个函数,在获取属性值时调用。
    • set:一个函数,在设置属性值时调用。
    • enumerable:布尔值,指示属性是否可被枚举。
    • configurable:布尔值,指示属性是否可被删除或修改其特性。

示例

定义一个数据属性
const obj = {};
Object.defineProperty(obj, 'name', {
    value: 'Alice',
    writable: false,  // 不能修改
    enumerable: true, // 可枚举
    configurable: true // 可配置
});

console.log(obj.name); // 输出: "Alice"
obj.name = 'Bob'; // 无效,因为 writable 为 false
console.log(obj.name); // 仍然输出: "Alice"
定义一个访问器属性
const person = {
    firstName: 'John',
    lastName: 'Doe'
};

Object.defineProperty(person, 'fullName', {
    get: function() {
        return `${this.firstName} ${this.lastName}`;
    },
    set: function(value) {
        [this.firstName, this.lastName] = value.split(' ');
    },
    enumerable: true,
    configurable: true
});

console.log(person.fullName); // 输出: "John Doe"
person.fullName = 'Jane Smith';
console.log(person.firstName); // 输出: "Jane"
console.log(person.lastName);  // 输出: "Smith"

5.3 枚举

可以在对象中设置属性是否可枚举。

let obj = {  };
obj.__proto__.a1 = 3;
Object.defineProperty(obj, 'a', {enumerable:true, value:1});
Object.defineProperty(obj, 'b', {enumerable:false, value:2});
obj.propertyIsEnumerable('a');	// true
obj.propertyIsEnumerable('b');	// false
Object.keys(obj);				// ['a']
Object.getOwnPropertyNames(obj);// ['a','b']
  • propertyIsEnumerable 检查属性是否存在于对象中(而不是原型链上),并满足enumerable 为 true
  • Object.keys(obj) 返回一个包含对象上所有可枚举属性的数组(不包含原型链上的属性)
  • Object.getOwnPropertyNames(obj) 返回一个包含对象上所有属性的数组(不包含原型链上的属性)

5.4 遍历

for...in 可以遍历对象的所有可枚举属性(包括原型链上的)

let obj = {  };
obj.__proto__.a1 = 3;
Object.defineProperty(obj, 'a', {enumerable:true, value:1});
Object.defineProperty(obj, 'b', {enumerable:false, value:2});
for(let key in obj){
  console.log(key);
}
// a  a1

for...of 可以遍历可迭代对象

  • 可以遍历可迭代对象(iterable objects
  • 可迭代对象:包括数组、字符串、Set、Map、Typed Arrays、NodeList 等
  • Typed Arrays,类型化数组,比如Uint8ArrayFloat32Array
  • NodeList ,比如document.querySelectorAll返回的元素数组
  • 对象本身没有迭代器,但可以自定义迭代器Symbol.iterator

自定义遍历对象上所有属性(包括原型链)

let obj = {  };
obj.__proto__.a1 = 3;
Object.defineProperty(obj, 'a', {enumerable:true, value:1});
Object.defineProperty(obj, 'b', {enumerable:false, value:2});
function getAllProperties(obj) {
    let props = [];
    while (obj) {
        props = props.concat(Object.getOwnPropertyNames(obj));
        obj = Object.getPrototypeOf(obj);
    }
    return props;
}
console.log(getAllProperties(obj));
// [ "a", "b", "a1", "constructor", "__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "isPrototypeOf", "propertyIsEnumerable", "toString", "valueOf", "__proto__", "toLocaleString" ]

6、浅拷贝与深拷贝

  • 浅拷贝:只复制对象的第一层属性,引用类型的属性共享同一引用。
  • 深拷贝:递归复制所有层级的属性,引用类型的属性创建全新副本。

浅拷贝

const original = {
    a: 1,
    b: { c: 2 }
};
// 浅拷贝
const shallowCopy = Object.assign({}, original);

// 修改浅拷贝的引用类型属性
shallowCopy.b.c = 3;

console.log(original.b.c); // 输出: 3 (原始对象也被修改)

深拷贝

  1. JSON.parse(JSON.stringify()) 可以复制一些简单类型的值,但无法复制函数、undefinedSymbolBigInt
const original = {
    a: 1,
    b: { c: 2 }
};

// 深拷贝
const deepCopy = JSON.parse(JSON.stringify(original));

// 修改深拷贝的引用类型属性
deepCopy.b.c = 3;

console.log(original.b.c); // 输出: 2 (原始对象未被修改)
  1. 自定义深拷贝函数,包含所有数据类型
function deepClone(value) {
    // 处理基本数据类型
    if (value === null || typeof value !== 'object') {
        return value; // 基本数据类型直接返回
    }

    // 处理日期
    if (value instanceof Date) {
        return new Date(value);
    }

    // 处理正则表达式
    if (value instanceof RegExp) {
        return new RegExp(value);
    }

    // 处理数组
    if (Array.isArray(value)) {
        return value.map(item => deepClone(item));
    }

    // 处理 Map
    if (value instanceof Map) {
        const mapCopy = new Map();
        value.forEach((val, key) => {
            mapCopy.set(deepClone(key), deepClone(val));
        });
        return mapCopy;
    }

    // 处理 Set
    if (value instanceof Set) {
        const setCopy = new Set();
        value.forEach(item => {
            setCopy.add(deepClone(item));
        });
        return setCopy;
    }

    // 处理普通对象
    const newObj = Object.create(Object.getPrototypeOf(value)); // 保留原型
    for (const key in value) {
        if (value.hasOwnProperty(key)) {
            newObj[key] = deepClone(value[key]); // 递归处理
        }
    }
    return newObj;
}
  • deepClone 中我保留了原型,深拷贝后的对象与原对象共用原型,所以如果原型上有变化,都会发生变化
  • 对于原型的处理,可以根据实际情况自己调整
    • 保留原型
    • 丢弃原型
    • 继续遍历原型,将原型也都复制一遍
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值