一些你可能不熟悉的JS知识点总结


Posted in Javascript onMarch 15, 2019

 暂时性死区

只要块级作用域存在let命令,它所声明的变量就“绑定”这个区域,不再受外部的影响。这么说可能有些抽象,举个例子:

var temp = 123;
if(true) {
 console.log(temp);
 let temp;
}

 结果:

> ReferenceError: temp is not defined

 在代码块内,使用let声明变量之前,该变量都是不可用的。在语法上,称为“暂时性死区”。(temporal dead zone)

ES6规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。

call和apply方法

这两个方法都可以改变一个函数的上下文对象,只是接受参数的方式不一样。
call接收的是逗号分隔的参数。

apply接收的是参数列表。

相信你肯定看到过这样的代码:

var arr = [1, 2, 3];
var max = Function.prototype.apply.call(Math.max, null, arr);
console.log(max); // 3

那么对这段代码怎么理解呢?

1.将Function.prototype.apply看成一个整体

(Function.prototype.apply).call(Math.max, null, arr)

2.func.call(context, args)可以转化为context.func(args)

所以代码被转换为:

Math.max.apply(undefined, arr)

基本上到这一步已经没必要去解释了。

那么你有没有试过将call和apply互换位置呢?

var arr = [1, 2, 3];
var max = Function.prototype.call.apply(Math.max, null, arr);
console.log(max); // -Infinity

为什么的它的输出结果为-Infinity呢?

因为apply的第二参数必须为数组,这里并不是,所以参数不能正确的传递给call函数。
根据func.apply(context, args)可以转化为context.func(args)。所以被转化成了Math.max.call(), 直接调用则会输出-Infinity。

如果想要正确调用,则应这样书写:

var arr = [1, 2, 3];
var max = Function.prototype.call.apply(Math.max, arr);
console.log(max); // 3

为了巩固以上内容,且看一个面试题:

var a = Function.prototype.call.apply(function(a){return a;}, [0,4,3]);
alert(a);

分析弹出的a值为多少?

// 将call方法看成一个整体
(Function.prototype.call).apply(function(a){return a;}, [0,4,3]);

// func.apply(context, args)可以转化为context.func(...args)
(function(a){return a;}).call(0, 4, 3);

// 所以结果很明显,输出4

Proxy对象

作用:用来自定义对象中的操作。

let p = new Proxy(target, handler)

target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

且看一个的小栗子:

// onChange 即要进行的监听操作
var watch = (object, onChange) => {
 const handler = {
  // 如果属性对应的值为对象,则返回一个新的Proxy对象
  get(target, property, receiver) {
   try {
    return new Proxy(target[property], handler);
   } catch (err) {
    return Reflect.get(target, property, receiver);
   }
  },
  // 定义或修改对象属性
  defineProperty(target, property, descriptor) {
   onChange('define',property);
   return Reflect.defineProperty(target, property, descriptor);
  },
  // 删除对象属性
  deleteProperty(target, property) {
   onChange('delete',property);
   return Reflect.deleteProperty(target, property);
  }
 };

 return new Proxy(object, handler);
};


// 测试对象
var obj = {
 name: 'bjw',
 age: 22,
 child: [1, 2, 3]
}

// 对象代理
var p = watch(obj1, (type, property) => { 
 console.log(`类型:${type}, 修改的属性:${property}`)
});

p.name = 'qwe'
 类型:define, 修改的属性:name
 "qwe"

p.child
 Proxy {0: 1, 1: 2, 2: 3, length: 3}
 
p.child.push(4)
 类型:define, 修改的属性:3
 类型:define, 修改的属性:length
 4

p.child.length = 2
 类型:define, 修改的属性:length
 2

p.child
 Proxy {0: 1, 1: 2, length: 2}

 如果关注Vue进展的话,可能已经知道Vue3.0中将通过Proxy来替换原来的Object.defineProperty来实现数据响应式。之所以要用Proxy替换原来的API原因在于Proxy无需一层层递归为每个属性添加代理,一次即可完成以上操作。性能上更好,并且原本的实现有一些数据更新不能监听到,但Proxy可以完美监听到任何方式的数据改变,相信通过上面的例子已经能够感受到Proxy带来的优势了。唯一的缺点可能就是浏览器兼容性不太好了。

 Reflect对象

为什么要有这样一个对象?

  • 用一个单一的全局对象去存储这些方法,能够保持其他的JavaScript代码整洁、干净。(不然的话得通过原型链调用)
  • 将一些命令式的操作delete、in使用函数代替,目的是为了让代码更好维护,避免出现更多的保留字。

Reflect对象拥有以下静态方法:

Reflect.apply
Reflect.construct
Reflect.defineProperty
Reflect.deleteProperty
Reflect.enumerate // 废弃的
Reflect.get
Reflect.getOwnPropertyDescriptor
Reflect.getPrototypeOf
Reflect.has
Reflect.isExtensible
Reflect.ownKeys
Reflect.preventExtensions
Reflect.set
Reflect.setPrototypeOf

具体函数细节:

Reflect.apply(target, this, arguments)

// target:目标函数
// this:绑定的上下文对象
// arguments:函数的参数列表
Reflect.apply(target, this, arguments)

const arr = [2, 3, 4, 5, 6];
let max;
// ES6
max = Reflect.apply(Math.max, null, arr)

// ES5 
max = Math.max.apply(null, arr);
max = Function.prototype.apply.call(Math.max, null, arr);

Reflect.construct(target, argumentsList[, newTarget])

// 这个方法,提供了一种新的不使用new来调用构造函数的方法
function A(name) {
 console.log('Function A is invoked!');
 this.name = name;
}
A.prototype.getName = function() {
 return this.name;
};

function B(age) {
 console.log('Function B is invoked!');
 this.age = age;
}
B.prototype.getAge = function() {
 return this.age;
};


// 测试 (这两种是一致的)
var tom = new A('tom');
var tom = Reflect.construct(A, ['tom']);


// jnney继承了A的实例属性,同时继承了B的共享属性
// 简单来说,A构造函数被调用,但是 jnney.__proto__ === B.prototype
var jnney = Reflect.construct(A, ['jnney'], B);

Reflect.defineProperty(target, propertyKey, attributes)

这个方法和Object.definePropperty(属性定义失败,会抛出一个错误,成功则返回该对象)相似,不过Reflect.defineProperty(属性定义失败,返回false,成功则返回true)返回的是一个Boolean值。

let obj = {};

let obj1 = Object.defineProperty(obj, 'name', {
 enumerable: true,
 value: 'bjw' 
});

// 这里会返回false 因为我们上面定义name这个属性是不可修改的,
// 然后我们又在这里修改了name属性,所以修改失败返回值为false
let result1 = Reflect.defineProperty(obj, 'name', {
 configurable: true,
 enumerable: true,
 value: 'happy'
});
console.log(result1); // false

Reflect.deleteProperty(target, propertyKey)

let obj = {
 name: 'dreamapple',
 age: 22
};

let r1 = Reflect.deleteProperty(obj, 'name');
console.log(r1); // true
let r2 = Reflect.deleteProperty(obj, 'name');
console.log(r2); // true
let r3 = Reflect.deleteProperty(Object.freeze(obj), 'age');
console.log(r3); // false

Reflect.get(target, propertyKey[, receiver])

Reflect.set(target, propertyKey, value[, receiver])

这个方法用来读取/设置一个对象的属性,target是目标对象,propertyKey是我们要读取的属性,receiver是可选的,如果propertyKeygetter函数里面有this值,那么receiver就是这个this所代表的上下文。

Reflect.getOwnPropertyDescriptor(target, propertyKey)

这个方法与Object.getOwnPropertyDescriptor方法类似,其中target是目标对象,propertyKey是对象的属性,如果这个属性存在属性描述符的话就返回这个属性描述符;如果不存在的话,就返回undefined。(如果第一个参数不是对象的话,那么Object.getOwnPropertyDescriptor会将这个参数强制转换为对象,而方法 Reflect.getOwnPropertyDescriptor会抛出一个错误。)

var obj = {age: 22}
Reflect.getOwnPropertyDescriptor(obj, 'age')
{value: 22, writable: true, enumerable: true, configurable: true}

Reflect.getPrototypeOf(target)

Reflect.setPrototypeOf(target, prototype)

这个方法与Object.getPrototypeOf方法是一样的,都是返回一个对象的原型,也就是内部的[[Prototype]]属性的值。

Reflect.setPrototypeOfObject.setPrototypeOf方法的作用是相似的,设置一个对象的原型,如果设置成功的话,这个对象会返回一个true;如果设置失败,这个对象会返回一个false。

Reflect.has(target, propertyKey)

这个方法相当于ES5的in操作符,就是检查一个对象上是否含有特定的属性;我们继续来实践这个方法:

function A(name) {
 this.name = name || 'dreamapple';
}
A.prototype.getName = function() {
 return this.name;
};

var a = new A();

console.log('name' in a); // true
console.log('getName' in a); // true

let r1 = Reflect.has(a, 'name');
let r2 = Reflect.has(a, 'getName');
console.log(r1, r2); // true true

Reflect.isExtensible(target)

这个函数检查一个对象是否是可以扩展的,也就是是否可以添加新的属性。(要求target必须为一个对象,否则会抛出错误)

let obj = {};
let r1 = Reflect.isExtensible(obj);
console.log(r1); // true
// 密封这个对象
Object.seal(obj);
let r2 = Reflect.isExtensible(obj);
console.log(r2); // false

模块化

使用模块化,可以为我们带来以下好处:

  • 解决命名冲突
  • 提供复用性
  • 提高代码可维护性

立即执行函数

在早期,使用立即执行函数实现模块化,通过函数作用域解决了命名冲突、污染全局作用域的问题。

AMD 和 CMD

这两种实现方式已经很少见到,具体的使用方式如下:

// AMD
define(['./a', './b'],function(a, b){
 // 模块加载完毕可以使用
 a.do();
 b.do(); 
});

// CMD
define(function(require, exports, module){
 // 加载模块
 var a = require('./a'); 
});

CommonJS

CommonJS最早是Node在使用,目前可以在Webpack中见到它。

// a.js
module.exports = {
 a: 1
}

// or 
exports.a = 1;

// 在b.js中可以引入
var module = require('./a');
module.a // log 1

难点解析:

// module 基本实现
var module = {
 id: 'xxx',
 exports: {}
}

var exports = module.exports;
// 所以,通过对exports重新赋值,不能导出变量

ES Module

ES Module 是原生实现模块化方案。

// 导入模块
import xxx form './a.js';
import { xxx } from './b.js';

// 导出模块
export function a(){}

// 默认导出
export default {};
export default function(){}

ES Module和CommonJS区别

  • CommonJS支持动态导入,也就是require(${path}/xx.js),ES Module不支持
  • CommonJS是同步导入,因为用于服务器端,文件都在本地,同步导入即使卡住主线程影响也不大。而ES Module是异步导入,因为用于浏览器,需要下载文件,采用同步导入会对渲染有很大影响
  • CommonJS在导出时都是值拷贝,就算导出值变了,导入的值也不会改变。如果想更新值,必须重新导入一次。但是ES Module采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟导出值变化
  • ES Module 会编译成 require/exports 来执行的

手写简单版本的Promise

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

function MyPromise(fn) {
 const _this = this;
 _this.state = PENDING;
 _this.value = null;
 _this.resolvedCallbacks = [];
 _this.rejectedCallbacks = [];


 // resolve函数
 function resolve(value) {
  if (_this.state === PENDING) {
   _this.state = RESOLVED;
   _this.value = value;
   _this.resolvedCallbacks.map(cb => cb(_this.value));
  }
 }

 // rejected函数
 function reject(value) {
  if (_this.state === PENDING) {
   _this.state = REJECTED;
   _this.value = value;
   _this.rejectedCallbacks.map(cb => cb(_this.value));
  }
 }

 // 当创建对象的时候,执行传进来的执行器函数
 // 并且传递resolve和reject函数
 try {
  fn(resolve, reject);
 } catch (e) {
  reject(e);
 }
}

// 为Promise原型链上添加then函数
MyPromise.prototype.then = function (onFulfilled, onRejected) {
 const _this = this;
 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
 onRejected = typeof onRejected === 'function' ? onRejected : r => {
  throw r;
 }
 if (_this.state === PENDING) {
  _this.resolvedCallbacks.push(onFulfilled);
  _this.rejectedCallbacks.push(onRejected);
 }
 if (_this.state === RESOLVED) {
  onFulfilled(_this.value);
 }
 if (_this.state === REJECTED) {
  onRejected(_this.value);
 }
 return _this;
}



// 测试
new MyPromise(function (resolve, reject) {
 setTimeout(() => {
  resolve('hello');
 }, 2000);
}).then(v => {
 console.log(v);
}).then(v => {
 console.log(v + "1");
})

这篇文章就介绍到这了,需要的朋友可以参考一下。

Javascript 相关文章推荐
用JavaScript实现仿Windows关机效果
Mar 10 Javascript
070823更新的一个[消息提示框]组件 兼容ie7
Aug 29 Javascript
学习ExtJS Column布局
Oct 08 Javascript
javascript parseInt与Number函数的区别
Jan 21 Javascript
在JQuery dialog里的服务器控件 事件失效问题
Dec 08 Javascript
js中prototype用法详细介绍
Nov 14 Javascript
模拟用户点击弹出新页面不会被浏览器拦截
Apr 08 Javascript
jQuery表单对象属性过滤选择器实例详解
Sep 13 Javascript
深入理解JavaScript创建对象的多种方式以及优缺点
Jun 01 Javascript
vue使用prop可以渲染但是打印台报错的解决方式
Nov 13 Javascript
详解React 条件渲染
Jul 08 Javascript
vue+element使用动态加载路由方式实现三级菜单页面显示的操作
Aug 04 Javascript
使用element-ui table expand展开行实现手风琴效果
Mar 15 #Javascript
element-ui组件table实现自定义筛选功能的示例代码
Mar 15 #Javascript
vue过滤器用法实例分析
Mar 15 #Javascript
vue v-for循环重复数据无法添加问题解决方法【加track-by='索引'】
Mar 15 #Javascript
详解vue移动端项目代码拆分记录
Mar 15 #Javascript
小程序自定义单页面、全局导航栏的实现代码
Mar 15 #Javascript
使用webpack搭建vue项目实现脚手架功能
Mar 15 #Javascript
You might like
php中文字符截取防乱码
2008/03/28 PHP
PHP 网页过期时间的控制代码
2009/06/29 PHP
PHP 工厂模式使用方法
2010/05/18 PHP
一个实用的php验证码类
2017/07/06 PHP
Avengerls vs KG BO3 第二场2.18
2021/03/10 DOTA
js实现的GridView即表头固定表体有滚动条且可滚动
2014/02/19 Javascript
JS JSOP跨域请求实例详解
2016/07/04 Javascript
微信小程序 实现拖拽事件监听实例详解
2016/11/16 Javascript
JS 实现随机验证码功能
2017/02/15 Javascript
BootstrapTable加载按钮功能实例代码详解
2017/09/22 Javascript
基于jQuery中ajax的相关方法汇总(必看篇)
2017/11/08 jQuery
JS代码实现电脑配置检测功能
2018/03/21 Javascript
使用Angular-CLI构建NPM包的方法
2018/09/07 Javascript
详解如何更好的使用module vuex
2019/03/27 Javascript
vue中使用微信公众号js-sdk踩坑记录
2019/03/29 Javascript
js观察者模式的弹幕案例
2020/11/23 Javascript
微信小程序视频弹幕发送功能的实现
2020/12/28 Javascript
零基础写python爬虫之urllib2中的两个重要概念:Openers和Handlers
2014/11/05 Python
python reduce 函数使用详解
2017/12/05 Python
基于Python Numpy的数组array和矩阵matrix详解
2018/04/04 Python
使用python读取csv文件快速插入数据库的实例
2018/06/21 Python
python代码 输入数字使其反向输出的方法
2018/12/22 Python
Python制作词云图代码实例
2019/09/09 Python
pycharm 关掉syntax检查操作
2020/06/09 Python
Python 爬虫的原理
2020/07/30 Python
Python Sqlalchemy如何实现select for update
2020/10/12 Python
美国最大网上鞋店:Zappos
2016/07/25 全球购物
匡威俄罗斯官网:Converse俄罗斯
2020/05/09 全球购物
最受欢迎的自我评价
2013/12/22 职场文书
亲戚结婚的请假条
2014/02/11 职场文书
理工学院学生自我鉴定
2014/02/23 职场文书
教师爱岗敬业演讲稿
2014/05/05 职场文书
生日宴会策划方案
2014/06/03 职场文书
2014大学生职业生涯规划书最新范文
2014/09/13 职场文书
车辆委托书范本
2014/10/05 职场文书
nginx 多个location转发任意请求或访问静态资源文件的实现
2021/03/31 Servers