Skip to content

es6

let /const

const 值不可修改, 比如先const x = 1; 再 x = 2; 就会报x is readonly

解构赋值

  • 字符串、函数参数都可以解构

  • 可以直接用取到的nodeList了!不用Array.prototype.slice.call(..)啦!!

ps: 现在还可以直接用Array.from()转数组喔!

  • 对象展开符的妙用

  • replace apply

  • 操作一些数组
  • 浅复制对象/数组

字符串的扩展

  • startsWith, endWith, include

import & export

set and map

两种可以看做是array和obj的升级版

  • Set: Set数据结构中所有成员的值都是唯一的,没有重复的值。判断重复的标准是===

  • add, delete, has, clear

  • Array.from 可将 Set 结构转变为数组
  • keys, values, entries, forEach
  • 数组去重: return Array.from(new Set(array))

  • Weakset:

  • WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别:① WeakSet 的成员只能是对象,而不能是其他类型的值。② WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

  • 没有size和forEach属性

  • 用处:储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。

  • 关于为什么不会内存泄漏有疑问: http://www.ruanyifeng.com/blog/2017/04/memory-leak.html 难道其余的对象不被引用时内存就不被释放了吗?

  • Map

  • WeakMap

proxy

let p = new Proxy(target, handler)

其中handler有如下几种:

{
  get: function(target, property) {},
    set: function(target, property, value, receiver) {}
}

//可以用来操纵p的读取值

//在new Proxy时,target的所有属性会复制给b一份

//b新增减属性时 target也会同样接受这个操作 增减属性

可以验证向一个对象的传值:

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
   }
    obj[prop] = value;
  }
};

let person = new Proxy({}, validator);
person.age = 100;
person.age = 'young'; 
// 抛出异常: Uncaught TypeError: The age is not an intege

还可以对想要读取或修改私有变量做限制~

同样是在handlers.get+handlers.set或者直接是handlers.has中判断key,若key名是被限制的key名,那么则拦截操作。

否则执行Reflect.get(...) /set(...) / has(...)

generator&iterator

iterator迭代器

In JavaScript an iterator is an object that provides a next() method which returns the next item in the sequence.

这个方法返回两个值 done和value

function makeIterator(array) {
    var nextIndex = 0;
    return {
       next: function() {
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false} :
               {done: true};
       }
    };
}

ar it = makeIterator(['yo', 'ya']);
console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done);  // true

Iterables**(可迭代对象)为:Array, TypedArray, String, Map, Set;它们.prototype上都有一个[Symbol.iterator]属性 即xx.prototype[Symbol.itrator] !== undefined

Generators生成器 => 可用来生成*iterables*!

生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议。

function* gen() { 
 yield 1;
}
let g = gen()

Object.getPrototypeOf(g) //=> Generator {}

解释:

generator function即function*:会定义一个生成器函数,它返回一个Generator生成器对象(即一个指向内部状态的指针对象,通过调用 next 方法,指针会移向下一个状态)

调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器(iterator)对象。迭代器的 next() 方法的作用是分阶段执行 Generator 函数,yield 后紧跟迭代器要返回的值。

调用 next()方法时,如果传入了参数,那么这个参数会作为上一条执行的 yield 语句的返回值。也就是说本来生成器函数是一个包裹住内在状态的函数,但是使用者是可以染指这个内部状态的!~

或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

ps: 可以将一个数组转换成一个iterable<不是通过变Set..> 我放到刷题里拉 这题

class Box {
  constructor (stuffs = []) {
    this.stuffs = stuffs
  }
  [Symbol.iterator] () {
    let i = 0
    return {
      next: () => {
        if (i >= this.stuffs.length) return { done: true }
        else return { value: this.stuffs[i++], done: false }
      }
    }
  }
}

pps:

※ Generator函数,不管内部有没有yield语句,调用函数时都不会执行任何语句,只有当调用next(),内部语句才会执行,只要调用next(),就会返回一个对象。yield语句只是函数暂停执行的一个标记

※ for...of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法

function *foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 4+1; //只有在获取第五个元素时才会计算
  return 6;
}
for (let v of foo()) {
  console.log(v)  // 1 2 3 4 5
}

作用:

迭代器Iterator, infinite range, 暂停函数, lazy evaluation, 来实现async/await

阮的异步举例:

var fetch = require('a 封装好的 fetch promise');
function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}
// 使用:
// var r = gen().next()
// 写错!应该是
var g = gen(); var r = g.next()
r.then(res => {
    rg.next(res)
})

ref: http://www.ruanyifeng.com/blog/2015/04/generator.html

箭头函数

恩 箭头函数里还没有arguments 因为rest对象展开符可以实现arguments的作用 所以功能上也不需要它了!

var bar = (...arguments) => console.log(arguments);

另外 箭头函数要实现类似纯函数的效果,必须剔除外部状态。所以当你定义一个箭头函数,在普通函数里常见的this、arguments、caller是统统没有的。

所以以下情况不要用箭头函数:

image-20230223133815241

。。。

通过以上说明,我们可以看出,箭头函数除了传入的参数之外,真的是什么都没有!如果你在箭头函数引用了this、arguments或者参数之外的变量,那它们一定不是箭头函数本身包含的,而是从父级作用域继承的。另外,也不能用call(), apply(), bind()去改变this的指向

哈哈哈。

关于 this

呀呀呀关于this又要晕乎乎了!

这是原来普通函数的老情况:

一。函数调用

+ 比如parseInt('18')>函数调用; [2,4].join('')>方法调用

+ this在函数调用中是一个全局对象Window //!!无论是哪里的函数!都是!

function sum(a, b) {
    console.log(this === window); // => true
    this.myNumber = 20; // add 'myNumber' property to global object
    return a + b;
    }
// sum() is invoked as a function
// this in sum() is a global object (window)
sum(15, 16); // => 31
window.myNumber; // => 20

+ this在strict的函数里 是undefined

+ 陷阱: 内部函数中的this

var numbers = {
     a:1,
     b:2,
     add: function(){ //方法调用
         console.log(this === numbers); //true  //箭头函数: false。所以这里和底下的子函数都最好写成numbers.a和numbers.b
         function cal(){ //函数调用
               console.log(this === numbers); //false
               return this.a + this.b
          } 
          return cal(); //undefined
          //解决方法 return cal.call(this);
     }
}

但是对于“箭头函数”的this,箭头函数是没有this的,箭头函数里使用this就像使用普通变量一样,在箭头函数的scope内找不都会一直向父scope寻找。来源: 看到这里的讨论

image-20230223142710357

这个例子里 setTimeout定义时的上下文是foo 其.id = 42; settimeout执行时的上下文是全局window 其id是21。

所以this.id === 定义时上下文的id === 42

关于setTimeout里的this: 原来的this在函数调用中是一个全局对象 如左解释

然而箭头函数中:由它的外层函数决定

image-20230223142756579

二。方法调用

+ this在方法调用中是拥有这个方法的对象

+ 当对象从prototype继承的方法在新对象上被调用时,上下文依然是该对象本身

var myDog = Object.create({
     sayName: function(){
          console.log(this === myDog); //true   //被分离出来的方法: false 箭头函数: false。
          return this.name;
     }
});
myDog.name = 'Milo';
myDog.sayName(); //'Milo'
var sayname = myDog.sayName
sayname() //undefined

+ 陷阱: 从object中分离方法

function Animal(type, legs){
     this.type = type;
     this.legs = legs;
     this.logInfo = function() {
          console.log(this === myCat);
          console.log(this.type + this.legs);
     }
}
var myCat = new Animal('Cat',4);
setTimeout(myCat.logInfo, 1000); //undefined
//解决方法 setTimeout(myCat.logInfo.bind(myCat), 1000);
//这儿的myCat就是一普通对象 相当于var xx = myCat.logInfo; 然后再xx()也是undefined一样。方法被从对象身上分离出去了。

三。构造函数

+ this在构造函数调用中指向新创建的对象

+ 陷阱:忘了new

function Vehicle(type, wheelsCount) {
     this.type = type;
     this.wheelsCount = wheelsCount;
     this instanceof Vechicle
     return this;
}
var car = Vehicle('Car', 4);
this //=> Window
car.type; //=>car;
car === window // => true
∴ instanceof Vechicle => false
var car = new Vehicle('Car', 4)
this //=> Vechicle {type: Car, wheelsCount: 4}

四。隐式调用

+ 当函数被.call()或.apply()调用时 执行的是隐式调用

+ 方法.call(thisArg[, arg1[, arg2[, ...]]])将接受的第一个参数thisArg作为调用时的上下文,arg1, arg2, ...这些则作为参数传入被调用的函数。方法.apply(thisArg, [args])将接受的第一个参数thisArg作为调用时的上下文,并且接受另一个类似数组的对象[args]作为被调用函数的参数传入。

+ 在隐式调用中, this是第一个参数

var rbt = {name: 'white rabbit'};
function concatName(string){
     console.log(this === rabbit) //=>true
     return string + this.name;
}
concatName.call(rbt,'hello');
concatName.apply(rbt,['bye']);

∴隐式调用可以用于模拟在一个对象上调用某个方法

五。绑定函数

+ 绑定函数是一个与对象绑定的函数。通常它是通过在原函数上使用 .bind()来创建的。原函数和绑定的函数共享代码跟作用域,但是在执行时有不同的上下文。

+ 方法.bind(thisArg[, arg1[, arg2[, ...]]])接受第一个参数thisArg作为绑定函数执行时的上下文,并且它接受一组可选的参数 arg1, arg2, ...作为被调用函数的参数。它返回一个绑定了thisArg的新函数。

function multiply(number){
     'use strict';
     return this*number;
}
var double = multiply.bind(2);
double(3) //=>6

跟.apply()以及.call()方法(见5.)马上调用函数不同,.bind()函数返回一个新的方法,它应该在之后被调用,只是this已经被提前设置好了。

+ this在调用绑定函数中是.bind()的第一个参数,并且非常强大难以被改变。 .bind()永久性地建立了一个上下文的链接,并且会一直保持它。一个绑定函数不能通过.call()或者.apply()来改变它的上下文,甚至是再次绑定也不会有什么作用。 只有用绑定函数的构造函数调用方法能够改变上下文,但并不推荐这个方法(因为构造函数调用用的是常规函数而不是绑定函数)。

+ .bind()总是会在forEach循环中使用~~比如:

DOMTokenList.prototype.adds = function(tokens) {
  tokens.split(" ").forEach(function(token) {
       this.add(token); //这里的this就是DOMTokenList的实例化对象 否则会是window
  }.bind(this));
  return this;
};

①注意这里用的是.bind() 因为forEach()里面要求传的是一个function参数,每遍历一次js就会执行一次这个参数。如果用的是call(),那么传入forEach()时就会执行了,那么参数就不是function格式了,就不对。

②我模拟的forEach:

Array.prototype.myEach = function(f){

var length = this.length;

for(var i = 0; i<length; i++){

   f(this[i],i); //也给传入的function传了两个值。第一个值是value, 第二个值是index

}

}

∴for循环与forEach大概有两个最明显的不同点(都与for循环的特性相关):**

①for循环不需要bind,但是forEach需要~

②for循环可以break, forEach不可以

③JS权威指南p218模拟的forEach,直接在调用传入的参数时就使用了call 还多了一个可以传context的参数

Set.prototype.foreach = function(f, context){
   for(var s in this.values) f.call(context, this.values[s])  //注意这里用的是call 并传入了value值
}

我一直很想弄清楚构造函数和 Class 的异同

构造函数:

function Test(a){
      this.a = a
      this.b = function(s){
     //this指的是被实例化的对象
     return this.a
  }
}
const rootProto = {
 pa: 'fff',
 pb: function(s){
    return this.a
 }
}
Test.prototype = rootProto

const t = new Test('boom')
Object.getPrototypeOf(t) // rootProto也就是{pa: 'fff', pb: f}
rootProto.isPrototypeOf(t) //true
for (props in t) {
// a b pa pb。a,b是自身属性,pa pb是继承属性。
}
Object.getOwnPropertyNames(t) // ['a', 'b']
//以下!是个关于this的需要注意的地方!
t.b() //boom
const tb = t.b
tb() //undefined

const t2 = Object.create(t)
Object.getPrototypeOf(t2) // 需要注意的这里取到的是直属上级原型t。也就是{a: 'boom', b: f}
rootProto.isPrototypeOf(t2) //true 毕竟root是终极原型
t2 instanceof Test // true
t2.pb() //boom
//t2现在拥有a b pa pb四个继承属性
t2.a = 'Im t2'
//t2现在拥有自身属性a 继承属性b pa pb

哇!!我觉得对于构造函数!!我一下子开窍了!!

突然领悟到构造函数的重点不是构造函数本身,而是产生的object这个结果。这里有一句虽然现在看起来过时 但却一语中的的话“JavaScript is a prototypal object oriented language. This means that objects in JavaScript directly inherit from other objects. Hence we don't need classes. All we need is a way to create and extend objects.”构造函数起到的正是create与extend objects的作用。

它指定输入,可以输出符合某种长相的对象。它还可以随身携带一个对象作为原型老大,以后生成的对象也会把原型老大当作终极原型,不管继承多少代。

如何继承对象?

如何继承构造函数?

 function A(){}
A.prototype.a = 1

function B(){}
B.prototype = Object.create(A.prototype)
B.prototype.b = 1
B.prototype.constructor = B

var b = new B()
console.log(b.a) \\ 1
console.log(b instanceof A) \\ true
console.log(b instanceof B) \\ true

恩以上是通过构造函数来继承的,实际上只要有一个对象,对象身上会携带着自己的prototype,因此别的对象也可以直接继承这个对象

function A(){}
A.prototype.a = 1
var a = new A()

function B(){}
B.prototype.b = 1

var b = new B()
b.__proto__=a 或b=Object.create(a)
console.log(b instanceof A)  // true
console.log(b instanceof B) // false

----蒙了我半晌的问题!疑问:

var A = function(){this.a = 1}
var a = new A()

var B = function(){this.b = 1}
B.prototype.say = function(){console.log('hh')}
var b = new B()

A.prototype = Object.create(B.prototype)
console.log(a.say())

结果是a.say is undefined..当时我就很奇怪 不是已经“指定”了A的prototype了吗 在A的prototype上有say方法 为什么小a没有say方法呢?其实就是…一个顺序所导致的切断引用的问题呀!可以看出a.constructor.prototype===A的原始prototype,所以只可以A's original prototype.say = xx这样来事后添加新的属性。而我上述的“指定”更像是“重定向”,直接将A.prototype指向一个别的对象,但是a.constructor.prototype指的地方还是原处,就无法实现想要的结果了。

Class 类

class Test{
        constructor(a) {
    this.a = a
          }
        b(){
              //this指代的是实例化对象
               console.log(this.a)
          }
        static c(s){
              //this指的是Test这个class
              const b = this.b //undefined
              return s
         }
}

typeof Test //function
Test.c('im Test')
//c于Test是什么关系呢

const t = new Test('boom') // {a: 'boom'}
t.constructor //class Test
Object.getPrototypeOf(t) //{b: f, constructor: class Test}
Object.getOwnPropertyNames(t) //['a']
for (props in t) { } //a
'b' in t //true 说明b以不可枚举继承属性存在着
//此时t的可枚举自身属性为a 不可枚举继承属性为b

const t2 = Object.create(t)
t2.constructor //class Test
Object.getPrototypeOf(t2) //Test {a: 'boom'}即t指向的对象
for (props in t) {} //a
//此时t2的可枚举继承属性是a 不可枚举继承属性是b

class TestExtends extends Test{
        constructor(a, a2){
    super(a)
                  super.b() //打印出z
    this.a2 = a2
        }
       b2(){
               console.log(this)
        }
        static d(){
    //this指的是class TestExtends
                 return 'ssss'
         }
}
TestExtends.c('Im TestExtends')
TestExtends.d()

const te = new TestExtends('boom', 'zzz') // {a:'z', a2: 'zzz'}
Object.getOwnPropertyNames(t) //['a']
for (props in te) { } //a
//此时te的可枚举自身属性为a 不可枚举继承属性为b, b2
//不加constructor()时 te为{a: undefined} 表明依然带着a这个属性
//子class若有任何属性与或方法与父class同名 则会覆盖后者

关于它vs 构造函数

  1. First, a function created by class is labelled by a special internal property [[FunctionKind]]:"classConstructor"
  2. Class methods are non-enumerable. A class definition sets enumerable flag to false for all methods in the "prototype".
  3. Classes always use strict. All code inside the class construct is automatically in strict mode.

这里尤其想讨论原型方法这一项。这一项可以看作直接替代了构造函数.prototype.方法,因为很明显class的实例化对象t中,原型方法为t的继承属性,而不是自身属性。唯一的不同点是构造函数.protype.方法是t的可枚举属性,而class的原型方法是t的不可枚举属性。

所以我最大的困惑,构造函数.prototype.方法去了哪里,以及class.原型方法到底是什么,就彻底解决了!:D

讲到静态方法static,这一项似乎是class特有的,只能通过class来调用该方法。文档说,静态方法通常用于为一个应用程序创建工具函数。感觉就是放只准给这个类调用的函数而已…

然后还有一个超神奇的!super!我一开始只知道它起到在实例化子类时,执行父类的constructor()并给之传参的作用。其实它还可以当场调用父对象上的原型函数,所以super.b()才可以执行。

extends也可以用于扩展常规的构造函数!然后这个extends的意义正在于原型方法。因为普通构造函数的原型方法是不可扩展的,只要rootPrototype上添加一条,它的所有子类都会加上。而class则可以另开一个类,在继承原类原型方法的基础上,又能覆盖,还能新增自己的原型方法,以传给自己要生产的对象们。岂不是特别的棒!

关于React的思考。

经过对于class的整理后 我开始有了一点渐渐明了的概念啦!那就是比如底下:

其中的state肯定在Component上有定义,componentDidMount和render这些肯定也有。那么子class就借助了“覆盖”的概念,以自己定义的属性或方法覆盖了父级的方法。然后Component一定有个类似init功能的方法。在init(){}里,会用到this.state,this.componentDidMount()及this.render()等,那么就起到了直接调用到子class的方法,也就是拓展class的概念。

表述不清 希望以后的自己多多海涵呀。

p.s. 以后的自己表示 思考的很好哈哈哈!以后的以后的自己也要像这个过去的自己学习哦!