首页 > 编程语言 >JavaScript Proxy() 构造函数、Proxy对象

JavaScript Proxy() 构造函数、Proxy对象

时间:2024-08-04 14:53:42浏览次数:21  
标签:target 对象 JavaScript user new Proxy 构造函数 属性

Proxy() 构造函数

Proxy() 构造函数用于创建 Proxy 对象。

语法

new Proxy(target, handler)

可以使用 Proxy() 构造函数来创建一个新的 Proxy 对象。构造函数接收两个必须的参数:

  • target
    是要创建的对象,即要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler
    是定义了代理的自定义行为的对象,其属性是定义了在对代理执行操作时的行为的函数。

注意Proxy() 只能通过 new 关键字来调用。如果不使用 new 关键字调用,则会抛出 TypeError

Proxy对象

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

语法

const p = new Proxy(target, handler)

handler对象的方法

handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。

所有的捕捉器都是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。

一个空的处理器(handler)将会创建一个与被代理对象行为几乎完全相同的代理对象。通过在 handler 对象上定义一组函数,你可以自定义被代理对象的一些特定行为。
如果在 handler 中存在相应的捕捉器,则它将运行,并且 Proxy 有机会对其进行处理,否则将直接对 target 进行处理。

示例:

let target = {};
let proxy = new Proxy(target, {}); // 空的 handler 对象

proxy.test = 5; // 写入 proxy 对象
console.log('target:', target, 'proxy:', proxt); // target和proxy中都有test属性!
console.log(target.test); // 5,test 属性出现在了 target 中!

console.log(proxy.test); // 5,我们也可以从 proxy 对象读取它

for(let key in proxy) console.log(key); // test,迭代也正常工作 

在这个示例中, handler 对象为空,没有捕捉器,所有对 proxy 的操作都直接转发给了 target

  1. 写入操作 proxy.test = 5 会将值写入 target。
  2. 读取操作 proxy.test 会从 target 返回对应的值。
  3. 迭代 proxy 会从 target 返回对应的值。
    此时,proxy 是一个 target 的透明包装器(wrapper)。它没有自己的属性。如果 handler 为空,则透明地将操作转发给 target

对于对象的大多数操作,JavaScript 规范中有一个所谓的“内部方法”,它描述了最底层的工作方式。
Proxy 捕捉器会拦截 对底层被代理对象的调用。
例如,通过定义 set() 可以自定义写入被代理对象的属性;通过定义 get() 可以自定义被代理对象的属性访问器。
常见的拦截操作和对应的捕捉器函数有:

  • set(target, propKey, value, receiver):拦截对象的设置属性操作,返回一个布尔值表示是否设置成功。
  • get(target, propKey, receiver):拦截对象的读取属性操作,返回属性值。
  • has(target, propKey):拦截对象的 in 操作符,返回一个布尔值表示对象是否包含该属性。
  • deleteProperty(target, propKey):拦截对象的 delete 操作符,返回一个布尔值表示是否删除成功。
  • ownKeys()Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器函数。
  • getPrototypeOf()Object.getPrototypeOf 方法的捕捉器。
  • setPrototypeOf()Object.setPrototypeOf 方法的捕捉器。
  • isExtensible()Object.isExtensible 方法的捕捉器。
  • preventExtensions()Object.preventExtensions 方法的捕捉器。
  • getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor 方法的捕捉器。
  • defineProperty()Object.defineProperty 方法的捕捉器。
  • apply(target, thisArg, args):拦截函数的调用操作,返回调用结果。
  • construct(target, args, newTarget):拦截 new 操作符,返回一个对象。

handler.set()

handler.set() 方法是设置属性值操作的捕获器。文档请看mdn

语法

new Proxy(target, {
  set(target, property, value, receiver) {
  }
});

handler.set() 方法用于拦截设置属性值的操作。this 绑定在 handler 对象上。
以下是传递给 set() 方法的参数:

  • target
    目标对象。该对象被作为第一个参数传递给 new Proxy
  • property
    目标属性名称(将被设置的属性名或 Symbol)。
  • value
    目标属性值。
  • receiver
    最初接收赋值的对象。通常是 proxy 本身,但 handlerset 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身)。

返回值

  • 返回 true 代表属性设置成功。
  • 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。

使用示例

let numbers = [];
numbers = new Proxy(numbers, { // (*)
  set(target, prop, val) { // 拦截写入属性操作
    if (typeof val == 'number') {
      target[prop] = val;
      return true;  // 此处必须要return true,表示属性设置成功
    } else {
      return false;
    }
  }
});

numbers.push(1); // 添加成功
numbers.push("test"); // TypeError(proxy 的 'set' 返回 false)

如果写入操作(setting)成功,set 捕捉器应该返回 true,否则返回 false(触发 TypeError)。

handler.get()

handler.get() 方法用于拦截对象的读取属性操作。文档请看mdn

语法

var p = new Proxy(target, {
  get: function (target, property, receiver) {},
});

以下是传递给 get() 方法的参数:

  • target
    目标对象。该对象被作为第一个参数传递给 new Proxy
  • property
    目标属性名称。
  • receiver
    Proxy 或者继承 Proxy 的对象。

返回值

  • get 方法可以返回任何值。

使用示例

let numbers = [0, 1, 2];
numbers = new Proxy(numbers, {
  get(target, prop) {
    if (prop in target) {
      return target[prop];
    }
  }
});

alert( numbers[1] ); // 1
alert( numbers[123] ); // undefined(没有这个数组项)

如果获取数组中不存在的值,会得到 undefined
get捕获器,可以给不存在的值一个默认值。

handler.deleteProperty()

handler.deleteProperty() 方法用于拦截对对象属性的 delete 操作。

deleteProperty语法

var p = new Proxy(target, {
  deleteProperty: function (target, property) {},
});

参数

  • target
    目标对象。
  • property
    待删除的属性名。

返回值
deleteProperty 必须返回一个 Boolean 类型的值,表示了该属性是否被成功删除。

使用示例

let target = {}
var p = new Proxy(
  target,
  {
    deleteProperty: function (target, prop) {
      console.log("拦截到的被删除的属性: " + prop);
      return true;
    },
  },
);

delete p.a; // "拦截到的被删除的属性: a"

注意:如果目标对象的属性是不可配置的,那么该属性不能被删除。

handler.defineProperty()

handler.defineProperty() 用于拦截对象的 Object.defineProperty() 操作。文档请看mdn

语法

var p = new Proxy(target, {
  defineProperty: function (target, property, descriptor) {},
});

this 绑定在 handler 对象上。
以下是传递给 defineProperty 方法的参数:

  • target
    目标对象。该对象被作为第一个参数传递给 new Proxy
  • property
    待检索其描述的属性名。
  • descriptor
    待定义或修改的属性的描述符。

返回值

  • defineProperty 方法必须以一个 Boolean 返回,表示定义该属性的操作成功与否。

拦截对象

  • Object.defineProperty()
  • Reflect.defineProperty()
  • proxy.property='value'

不变量

如果违背了以下的不变量,proxy 会抛出 TypeError:

  • 如果目标对象不可扩展,将不能添加属性。
  • 不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象 的不可配置的属性存在的话。
  • 如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的。
  • 如果一个属性在目标对象中存在对应的属性,那么 Object.defineProperty(target, prop, descriptor) 将不会抛出异常。
    在严格模式下,false 作为 handler.defineProperty 方法的返回值的话将会抛出 TypeError 异常

使用示例

let desc = {
  configurable: true,  // 可配置
  writable: true,      // 可写入
  enumerable: true,    // 可枚举
  value: 10
}
let target = {}
var p = new Proxy(
  target,
  {
    defineProperty: function (target, prop, descriptor) {
      console.log("defineProperty拦截的key: " + prop);
      return true;
    },
  },
);
Object.defineProperty(p, "obj", desc); // "defineProperty拦截的key: " + obj

当调用 Object.defineProperty() 或者 Reflect.defineProperty(),传递给 definePropertydescriptor 有一个限制:只有标准属性才有用,非标准的属性将会被无视。
标准属性:enumerableconfigurablewritablevaluegetset

使用 “ownKeys” 和 “getOwnPropertyDescriptor” 进行迭代

ownKeys语法

var p = new Proxy(target, {
  ownKeys: function (target) {},
});

文档请看mdn

参数

  • target
    目标对象。

返回值
ownKeys 方法必须返回一个可枚举对象。

拦截对象

  • Object.getOwnPropertyNames()返回非 symbol 键
  • Object.getOwnPropertySymbols()返回 symbol 键。
  • Object.keys()Object.values()返回带有 enumerable 标志的非 symbol 键/值。
  • for..in 循环遍历所有带有 enumerable 标志的非 symbol 键,以及原型对象的键。
  • Reflect.ownKeys()

在下面这个示例中,使用 ownKeys 捕捉器拦截 for..inuser 的遍历,并使用 Object.keysObject.values 来跳过以下划线 _ 开头的属性:

let user = {
  name: "John",
  age: 30,
  _password: "***"
};

user = new Proxy(user, {
  ownKeys(target) {
  	// 获取user的keys并过滤带有'_'的key
    return Object.keys(target).filter(key => !key.startsWith('_'));
  }
});

// "ownKeys" 过滤掉了 _password
for(let key in user) console.log(key); // name,然后是 age

// 对这些方法的效果相同:
console.log( Object.keys(user) ); // name,age, '_password'被过滤掉
console.log( Object.values(user) ); // John,30,  '_password'的值被过滤掉

Object.keys 不会列出对象中不存在的键:

let user = {};
user = new Proxy(user, {
  ownKeys(target) {
  	// a,b,c在user对象中不存在
    return ['a', 'b', 'c'];
  }
});

console.log( Object.keys(user) ); // []

Object.keys 仅返回带有 enumerable 标志的属性。为了检查 enumerable 标志,该方法会对每个属性调用内部方法 [[GetOwnProperty]] 来获取 它的描述符(descriptor)。在这里,user对象没有属性,描述符为空,没有 enumerable 标志,因此它被略过。

为了让 Object.keys 返回一个属性,我们需要它存在于带有 enumerable 标志的对象;或者拦截对 [[GetOwnProperty]] 的调用(捕捉器 getOwnPropertyDescriptor 可以做到这一点),并返回带有 enumerable: true 的描述符。

let user = { };

user = new Proxy(user, {
  ownKeys(target) { // 一旦要获取属性列表就会被调用
    return ['a', 'b', 'c'];
  },

  getOwnPropertyDescriptor(target, prop) { // 被每个属性调用
  	console.log('target:', target, 'prop:', prop)
  	// 给每个设置enumerable属性
    return {
      enumerable: true,
      configurable: true
      /* ...其他标志,可能是 "value:..." */
    };
  }

});

alert( Object.keys(user) ); // a, b, c

如果该属性在对象中不存在,那么只需要拦截 [[GetOwnProperty]]

捕捉器的受保护属性

有一个约定:以下划线 _ 开头的属性和方法是内部的。不应从对象外部访问它们。

使用代理来防止对以 _ 开头的属性的任何访问:

  1. get 读取此类属性时抛出错误,
  2. set 写入属性时抛出错误,
  3. deleteProperty 删除属性时抛出错误,
  4. ownKeys 在使用 for..in 和像 Object.keys 这样的方法时排除以 _ 开头的属性。

具体实现:

let user = {
  name: "John",
  _password: "***"
};

user = new Proxy(user, {
  get(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error("Access denied");
    }
    let value = target[prop];
    return (typeof value === 'function') ? value.bind(target) : value; 
  },
  set(target, prop, val) { // 拦截属性写入
    if (prop.startsWith('_')) {
      throw new Error("Access denied");
    } else {
      target[prop] = val;
      return true;
    }
  },
  deleteProperty(target, prop) { // 拦截属性删除
    if (prop.startsWith('_')) {
      throw new Error("Access denied");
    } else {
      delete target[prop];
      return true;
    }
  },
  ownKeys(target) { // 拦截读取属性列表
    return Object.keys(target).filter(key => !key.startsWith('_'));
  }
});

// "get" 不允许读取 _password
try {
  console.log(user._password); // Error: Access denied
} catch(e) { 
  console.log(e.message); 
}

// "set" 不允许写入 _password
try {
  user._password = "test"; // Error: Access denied
} catch(e) { 
  alert(e.message); 
}
// "deleteProperty" 不允许删除 _password
try {
  delete userProxy._password; // Error: Access denied
} catch(e) { console.log(e.message); }

// "ownKeys" 将 _password 过滤出去
for(let key in userProxy) console.log(key); // name

// "checkPassword" 必须读取 _password
user.checkPassword = function (value) {
  return value === this._password;
};
try {
  console.log(user.checkPassword("***"));  // true
} catch (e) { 
  console.log(e.message); 
}

get不允许读取 _passworduser.checkPassword("***")为什么能读取成功?

get中,(typeof value === 'function') ? value.bind(target) : value 语句判断传入的value是否是function,如果是function,将对象方法的上下文绑定到原始对象 target
user.checkPassword() 的调用将使用 target 作为 this,不会触发任何捕捉器。

handler.has()

handler.has() 方法是针对 in 操作符的代理方法。文档请看mdn

参数说明:
has(target, property)

  • target —— 是目标对象,被作为第一个参数传递给 new Proxy
  • property —— 属性名称。

has 捕捉器会拦截 in 调用。
示例:

let user = {
  name: "John",
  _password: "***"
};
user = new Proxy(user, {
  has(target, prop) {
    return prop in target;
  }
})
console.log('name' in user); // true
console.log('age' in user);  // false

handler.apply()

handler.apply() 方法 方法用于拦截函数的调用。文档请看mdn

语法

var p = new Proxy(target, {
  apply: function (target, thisArg, argumentsList) {},
});

this 上下文绑定在 handler 对象上。
参数

  • target
    目标对象(函数)。
  • thisArg
    this的值,被调用时的上下文对象。
  • argumentsList
    被调用时的参数数组。

返回值
apply 方法可以返回任何值。

拦截

该方法会拦截目标对象的以下操作:

  • proxy(...args)
  • Function.prototype.apply()Function.prototype.call()
  • Reflect.apply()

使用示例

function delay(f, ms) {
  return new Proxy(f, {
    apply(target, thisArg, args) {
      setTimeout(() => target.apply(thisArg, args), ms);
    }
  });
}

function sayHi(user) {
  alert(`Hello, ${user}!`);
}

sayHi = delay(sayHi, 3000);

console.log(sayHi.length); // proxy 将“获取 length”的操作转发给目标对象

sayHi("John"); // Hello, John!(3 秒后)

可撤销 Proxy

一个 可撤销 的代理是可以被禁用的代理。
##语法

let {proxy, revoke} = Proxy.revocable(target, handler)

该调用返回一个带有 proxyrevoke 函数的对象以将其禁用。
一旦代理对象被撤销,对它执行可代理操作将会抛出 TypeError 异常。

function accessTheDatabase() {
  /* 实现被省略 */
  return 42;
}

let { proxy, revoke } = Proxy.revocable(accessTheDatabase, {});

proxy(); // => 42,代理提供了对底层目标函数的引用

revoke(); // 但你可以随时让代理失效

proxy(); // 抛出 TypeError: 代理已失效,无法再使用代理调用底层目标函数了

revoke() 的调用会从代理中删除对目标对象的所有内部引用,因此它们之间再无连接。

Proxy局限性

  • 某些内置对象的内部机制限制
    对于一些内置对象(如 Map、Set、Date、Promise 等),都使用了所谓的“内部插槽”。
    它们类似于属性,但仅限于内部使用,仅用于规范目的。例如,Map 将项目(item)存储在 [[MapData]] 中。内建方法可以直接访问它们,而不通过 [[Get]]/[[Set]] 内部方法。所以 Proxy 无法拦截它们
    例如:
let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('test', 1); // Error

在内部,一个 Map 将所有数据存储在其 [[MapData]] 内部插槽中。代理对象没有这样的插槽。内建方法 Map.prototype.set 方法试图访问内部属性 this.[[MapData]],但由于 this=proxy,在 proxy 中无法找到它,只能失败。
使用Reflect.get()可以解决这个问题:

let map = new Map();
let proxy = new Proxy(map, {
  get(target, prop, receiver) {
    let value = Reflect.get(...arguments);
    return typeof value == 'function' ? value.bind(target) : value;
  }
});

proxy.set('test', 1);
console.log(proxy.get('test')); // 1

现在 get 捕捉器将函数属性(例如 map.set)绑定到了目标对象(map)本身。
proxy.set(...) 内部 this 的值并不是 proxy,而是原始的 map。因此,当set 捕捉器的内部实现尝试访问 this.[[MapData]] 内部插槽时,它会成功。

私有字段是通过内部插槽实现的。JavaScript 在访问它们时不使用 [[Get]]/[[Set]]

例如,getName() 方法访问私有的 #name 属性,并在代理后中断:

class User {
  #name = "Guest";
  getName() {
    return this.#name;  // this指向代理后的 user
  }
}
let user = new User();
user = new Proxy(user, {});
alert(user.getName()); // Error

在调用 getName() 时,this 的值是代理后的 user,它没有带有私有字段的插槽。

再次,带有 bind 方法的解决方案使它恢复正常:

class User {
  #name = "Guest";

  getName() {
    return this.#name;
  }
}

let user = new User();

user = new Proxy(user, {
  get(target, prop, receiver) {
    let value = Reflect.get(...arguments);
    return typeof value == 'function' ? value.bind(target) : value;
  }
});

alert(user.getName()); // Guest

该解决方案也有缺点:它将原始对象暴露给该方法,可能使其进一步传递并破坏其他代理功能。

  • 兼容性问题:Proxy 是 ES6 中新增的特性,并非所有的浏览器都完全支持。如果在不支持 Proxy 的环境中运行使用了 Proxy 的代码,会出现报错的情况,因为这些环境无法识别 Proxy。

  • 性能开销:使用 Proxy 会引入一定的性能开销,因为它需要对对象的操作进行拦截和处理。在一些性能敏感的场景中,需要谨慎使用,以避免对性能产生较大的影响。

  • 操作源对象时捕获器不会被触发:只有通过代理对象进行的操作才会被捕获器捕获到,如果直接操作源对象,则捕获器不会被触发。这意味着如果在某些情况下不小心直接操作了源对象,而不是通过代理对象进行操作,可能会导致预期的行为没有被正确拦截或处理。

在什么场景下选择使用 Proxy 而不是传统的对象操作方式?

  1. 数据验证和保护:比如您想要确保对对象属性的访问和修改符合特定的规则,例如属性值必须是特定的数据类型、在特定范围内等。通过 Proxy 的拦截器,可以在读取和设置属性时进行验证,抛出错误或进行修正。

例如,验证user对象的年龄属性age,年龄必须是一个大于 0 小于 150 的整数。

let user = {
  age: 25
};

let userProxy = new Proxy(user, {
  set(target, prop, value) {
    if (prop === 'age' && (typeof value!== 'number' || value < 0 || value > 150)) {
      throw new Error('年龄必须是 0 到 150 之间的整数');
    }
    target[prop] = value;
    return true;
  }
});

userProxy.age = 180; // 抛出错误Uncaught Error: 年龄必须是 0 到 150 之间的整数
  1. 懒加载和缓存:当对象的某些属性获取计算成本较高时,可以使用 Proxy 来实现懒加载。只有在真正需要获取该属性时才进行计算,并将结果缓存起来,下次获取时直接返回缓存的值。

例如,有一个对象 data ,其中包含一个需要从服务器获取的大型数据属性 bigData 。

let data = {
  // 其他属性...
};

let dataProxy = new Proxy(data, {
  get(target, prop) {
    if (prop === 'bigData' &&!target[prop]) {
      // 模拟从服务器获取数据
      target[prop] = '获取到的大型数据...';
    }
    return target[prop];
  }
});

console.log(dataProxy.bigData); // 第一次获取时从服务器获取数据并返回
console.log(dataProxy.bigData); // 第二次直接返回已缓存的数据
  1. 日志和审计:可以拦截对象的操作,记录每一次属性的读取、修改等操作,用于日志记录或审计目的。

例如,有一个配置对象 config ,您想要记录对其属性的所有修改操作。

let config = {
  theme: 'light',
  fontSize: 14
};

let configProxy = new Proxy(config, {
  set(target, prop, value) {
    console.log(`属性 ${prop} 从 ${target[prop]} 被修改为 ${value}`);
    target[prop] = value;
    return true;
  }
});

configProxy.fontSize = 16; 
// 输出:属性 fontSize 从 14 被修改为 16
  1. 实现虚拟属性:创建一些看似存在但实际不存在于原始对象中的属性。通过 Proxy 的拦截器,在访问这些虚拟属性时返回计算得到的值。

例如,有一个商品对象 product ,但您想要提供一个虚拟的 discountedPrice 属性,根据原价和折扣计算得出。

let product = {
  price: 100,
  discount: 0.8
};

let productProxy = new Proxy(product, {
  get(target, prop) {
    if (prop === 'discountedPrice') {
      return target.price * target.discount;
    }
    return target[prop];
  }
});

console.log(productProxy.discountedPrice); 
// 输出:80
  1. 跨框架数据绑定:在某些前端框架中,Proxy 可以方便地实现数据的双向绑定,自动同步数据的变化到视图或其他相关部分。

标签:target,对象,JavaScript,user,new,Proxy,构造函数,属性
From: https://blog.csdn.net/fishmemory7sec/article/details/140767083

相关文章

  • JavaScript 中 arguments 对象与剩余参数的对比及转换
    引言在JavaScript中,处理函数调用时传递的不同数量的参数是一项常见的任务。为此,JavaScript提供了两种不同的方法:arguments对象和剩余参数(RestParameters)。本文将探讨这两种方法的区别,并介绍如何将arguments对象转换为真正的数组。arguments对象vs.剩余参数arguments......
  • 【前端】JavaScript入门及实战131-135
    文章目录131定时器(1)132定时器(2)133定时器(3)134轮播图135tools.js131定时器(1)<!DOCTYPEhtml><html><head><title></title><metacharset="utf-8"><styletype="text/css"> *{ margin:0; padding:0......
  • 【前端】JavaScript入门及实战136-140
    文章目录136类的操作137二级菜单138JSON139JSON140json2.js136类的操作<!DOCTYPEhtml><html><head><title></title><metacharset="utf-8"><styletype="text/css"> .b1{ width:100px; height:100p......
  • JavaScript (十七)——JavaScript 声明提升和严格模式
    目录JavaScript声明提升JavaScript初始化不会提升在头部声明你的变量JavaScript严格模式(usestrict)使用"usestrict"指令严格模式声明严格模式的限制JavaScript声明提升JavaScript中,函数及变量的声明都将被提升到函数的最顶部。JavaScript中,变量可以在......
  • JavaScript(十八)——JavaScript 使用误区
    目录赋值运算符应用错误比较运算符常见错误加法与连接注意事项浮点型数据使用注意事项JavaScript字符串分行错误的使用分号语句使用注意事项return使用注意事项数组中使用名字来索引定义数组元素,最后不能添加逗号定义对象,最后不能添加逗号Undefined不是Null程......
  • C++ 面向对象基础-构造函数
    目录1.构造函数1.1基本使用1.2函数参数默认值1.3构造初始化列表 1.4隐式调用构造函数2.拷贝构造函数2.1概念2.2浅拷贝2.3深拷贝3.析构函数1.构造函数1.1基本使用构造函数是一种特殊的成员函数,用于创建对象时初始化,写法上有以下要求:●函数名称必......
  • JavaScript 中的闭包和事件委托
    闭包(Closures)闭包是JavaScript中一个非常强大的特性,它允许函数访问其外部作用域中的变量,即使在该函数被调用时,外部作用域已经执行完毕。闭包可以帮助我们实现数据的私有化、封装和模块化,使代码更简洁、易读和可维护。闭包的定义简单来说,闭包是指有权访问另一个函数作用域......
  • JavaScript实现tab栏切换 jquery实现tab栏切换 的方法的对比
    这个例子比较简单,但却很实用,当然实际工作中我们一般不会这样去写,我们通常会把以此为基础去封装一个可重用的控件,但基本思想不变。JavaScript实现tab栏切换在JavaScript中实现Tab切换的基本逻辑是通过监听每个Tab的点击事件,然后隐藏所有的内容区域,并显示对应于点击的Tab的......
  • javascript学习 - DOM 元素获取、属性修改
    什么是WebAPIWebAPI是指网页服务器或者网页浏览器的应用程序接口。简单来讲,就是我们在编写JavaScript代码时,可以通过WebAPI来操作HTML网页和浏览器。WebAPI又可以分为两类:DOM(文档对象模型)BOM(浏览器对象模型)DOM(DocumentObjectModel),即文档对象模型,主要用......
  • javascript学习 - DOM 事件
    事件什么是事件在之前DOM的学习中,我们主要学习了如何获取DOM元素,并且学会了如何给获取的元素进行属性修改等操作。但这些基本都是静态的修改,并没有接触到一些动作。而今天要学习的事件,其实就是这些动作的总称。所谓事件,就是在编程时系统内所发生的动作或者发生的事情......