整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          熬夜7天,我總結了JavaScript與ES的25個

          熬夜7天,我總結了JavaScript與ES的25個知識點


          說起JavaScript,大家都知道是一門腳本語言。那么ES是什么鬼呢?ES全稱ECMAScript ,是JavaScript語言的國際標準。

          最近,我總結了25條JavaScript的基礎特性相關的知識點,大家一起看一下吧

          1.嚴格模式

          • 使用嚴格模式,可以在函數內部進行較為嚴格的全局和局部的錯誤條件檢查
          • 嚴格模式的編譯指示,"use strict"
          • 創建全局變量,未聲明變量,非嚴格模式下為創建全局變量;嚴格模式下為拋出ReferenceError
          • 對變量調用delete操作符,刪除變量,非嚴格模式下為靜默失敗;嚴格模式下為拋出ReferenceError
          • 操作對象情況下:a,只讀屬性賦值會拋出TypeError;b,對不可配置的屬性使用delete操作符會拋出TypeError;c,為不可擴展的對象添加屬性會拋出TypeError。
          • 重名屬性情況:a,非嚴格模式下沒有錯誤,以第二個屬性為準;b,嚴格模式下會拋出語法錯誤。
          • 函數參數必須唯一,重名參數,在非嚴格模式下沒有錯誤,只能訪問第二個參數;嚴格模式下,會拋出錯誤。
          function funValue(value) {    value="dada";    alert(value); // dada    alert(argument[0]); // 非嚴格模式:dada    // 嚴格模式模式 dadaqianduan}?funValue('dadaqianduan');
          • 訪問arguments.callee和arguments.caller,在非嚴格模式下沒有問題,嚴格模式下拋出TypeError。

          2.Class基礎語法


          在JavaScript當中如何聲明一個類?如何定義類中的方法?如何實例化對象?


          我們來看看下面的代碼示例:


          // es5?let dada=function(type) {	this.type=type}?dada.prototype.study=function() {	console.log('魔王哪吒');}?let da1=new dada('程序員')let da2=new dada('It')?da1.constructor.prototype.study=function() {	console.log('dadaqianduan');}da1.study()

          JavaScript constructor 屬性

          定義和用法

          constructor 屬性返回對創建此對象的數組函數的引用。

          語法

          object.constructor

          constructor 是一種用于創建和初始化class創建的對象的特殊方法。

          // es6class Da {  constructor(name) { // 構造函數內寫屬性    this.name=name;  }  eat() { // 構造函數外寫方法  	console.log('i eat')  }}?const da1=new Da('da1');?console.log(da1.name); // da1console.log(da1);
          1. 一個類中只能有一個名為“constructor"的方法,出現多次構造函數constructor方法會拋出一個SyntaxError錯誤
          2. 在一個構造方法中可以使用super來調用一個父類的構造方法
          3. 如果沒有指定一個構造函數方法constructor方法,就會使用一個默認的構造函數

          3.類的屬性Setter和Getter

          var daObj={ get val() {  return ; }, set val(value) { }}

          get:

          var da={    a: 1,    get val(){        return this.a + 1;    }}?console.log(da.val);//2da.val=100;console.log(da.val);//2?class Da { constructor(type) {  this.type=type } get age() {  return 1 } set age(val) {  this.realAge=val } eat() {  console.log('i am eat') }}let da1=new Da('da1')console.log(da1.age)da1.age=1console.log(da1.realAge)
          class Da { constructor(type, age) {  this.type=type  this.age1=age } get age() {  return this._age } set age(val) {  this._age=val }}

          利用set/get實現對element.innerHTML封裝

          class myHTMLElement { constructor(element) {  this.element=element } get html() {  return this.element.innerHTML } set html(value) {  this.element.innerHTML=value }}

          設置一個閉包,通過一定的規則來限制對它的修改:

          let myName='dada'class Da { constructor(type) {  this.type=type } get name() {  return myName } set name(val) {  myName=val }}

          4.靜態方法

          在es5中實現的靜態方法:

          let Da=function (type) { this.type=type this.eat=function() {  console.log('i eat') }}Da.study=function(book) { console.log('i book');}
          let Da=function(type) { this.type=type}Da.prototype.eat=function() { Da.walk() console.log('i am')}Da.walk=function(){ console.log('walk')}let da1=new Da('da1')da1.eat()?// walk// i am

          靜態方法在你的實例化對象是找不到的

          在es6中的靜態方法,標記static

          class Da { constructor(type) {  this.type=type } eat() {  console.log('i eat') } static study() {  console.log('i study') }}

          5.如何繼承一個類

          在es5中的繼承:

          // 定義一個父類let Da=function(type) { this.type=type}// 定義方法Da.prototype.eat=function() { console.log('i am')}// 定義靜態方法Da.study=function(book) { console.log('i study')}// 定義子類let Da1=function() { // 初始化父類 Da.call(this, 'da1'); this.run=function() {  console.log('i run') }}// 繼承Da1.prototype=Da.prototype

          在es6中的繼承

          class Da { constructor(type) {  this.type=type } eat() {  // Da.walk();  console.log('i eat') } static walk(){  console.log('i walk') }}?class da extends Da { // 構造函數 //constructor (type) {  //super(type) //} run() {  console.log('i run') }}let da1=new da('da1')

          6.面向對象編程Class

          類的聲明,屬性,方法,靜態方法,繼承,多態,私有屬性

          // 類的聲明let Da=function(type) { this.type=type this.eat=function() {  console.log('i eat'); }}?let da=new Da('da');
          // prototypelet Da=function(type) { this.type=type}Da.prototype.eat=function() { console.log('i eat')}let da1=new Da('da1')

          es6中的Class

          class Da { // 構造函數 constructor(type) {  this.type=type } // 方法 walk() {  console.log('i walk') }}let da=new Da('da');// console.log(typeof Da); function

          7.函數參數的默認值

          函數參數是從左到右解析,如果沒有默認值會被解析成 undefined

          // 參數默認值function da (x,y,z) {}function sum() { let num=0 Array.prototype.forEach.call(arguments, function(item){  num +=item * 1 }) Array.from(arguments).forEach(function(item){  num +=item * 1 }) return num}
          // 不確定function sum(...nums) { let num=0 nums.forEach(function(item){  num +=item * 1 }) return num}console.log(sum(1,2,3,4,5))
          function sum () {  let num=0  Array.prototype.forEach.call(arguments, function (item) {    num +=item * 1  })  return num}?function sum (...nums) {  let num=0  nums.forEach(function (item) {    num +=item * 1  })  return num}

          8.es6箭頭函數

          箭頭函數表達式的語法比函數表達式更簡潔,并且沒有自己的this,arguments,super或new.target。箭頭函數表達式更適用于那些本來需要匿名函數的地方,并且它不能用作構造函數。

          ()=> {}// function Da() {}// let da=function() {}let da=()=> { console.log('hello')}da()?let da=name=> {}
          const materials=[  'Hydrogen',  'Helium',  'Lithium',  'Beryllium'];console.log(materials.map(material=> material.length));// expected output: Array [8, 6, 7, 9]

          拓展

          判斷函數有幾個參數

          1. 在 ES5 中可以在函數體內使用 arguments 來判斷。
          2. 在 ES6 中可以借助 Function.length 來判斷。(統計第一個默認參數前面的變量數)
          console.log(sum(...[4]))console.log(sum.apply(null, [4]))

          9.JavaScript中的三個點(…)

          JavaScript當中,函數的參數前面有三個點,代表什么呢?我們看下代碼示例:

          function myFunc(a, b, ...args) { console.log(a); // 22 console.log(b); // 98 console.log(args); // [43, 3, 26]};myFunc(22, 98, 43, 3, 26);
          function myFunc(x, y, ...params) { // used rest operator here  console.log(x);  console.log(y);  console.log(params);}?var inputs=["a", "b", "c", "d", "e", "f"];myFunc(...inputs); // used spread operator here// "a"// "b"// ["c", "d", "e", "f"]
          var obj1={ foo: 'bar', x: 42 };var obj2={ foo: 'baz', y: 13 };?var clonedObj={ ...obj1 };// Object { foo: "bar", x: 42 }?var mergedObj={ ...obj1, ...obj2 };// Object { foo: "baz", x: 42, y: 13 }

          10.Object Property

          JS中對象的屬性定義,代碼示例如下:

          let x='da1';let y='da2';let obj={ x, y}console.log(obj);?// 結果{x:'da1',y:'da2'}
          let x=1; let y=2; let z=3let obj={ 'x': x, y, [z+y]: 4, * hello() { // 異步  console.log('dada') }}// function* functionName() {}obj.hello()

          其中,*hello是Generator函數,這是ES6提供的一種異步解決方案。

          11.Set數據結構

          Set存儲的成員不允許的重復的(它類似于數組)

          Set 本身是一個構造函數,用來生成 Set 數據結構。

          const s=new Set();?[2, 3, 5].forEach(x=> s.add(x));?Set 函數可以接受一個數組(或類似數組的對象)作為參數,用來初始化?const set=new Set([1, 2, 3, 4, 4]);

          實現數組去重

          var arr=[1,1,2,2,3,3]; // step1:數組轉集合var s=new Set(arr); // 已經去掉重復值,當前不是數組,而集合s.size; // 3// step2:集合轉數組console.log([...s]); // 1,2,3;?// Array.form 方法可以將 Set 結構轉為數組const items=new Set([1, 2, 3]);const arr=Array.from(items);?function dada(array) {  return Array.from(new Set(array));}?dada([1, 1, 2])

          Set的遍歷

          • keys():返回鍵名的遍歷器
          • values():返回鍵值的遍歷器
          • entries():返回鍵值對的遍歷器
          • forEach():使用回調函數遍歷每個成員

          操作方法

          • add(value):添加某個值,返回Set結構本身。
          • delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
          • has(value):返回一個布爾值,表示該值是否為Set的成員。
          • clear():清除所有成員,沒有返回值。
          let set=new Set([1, 2, 3, 4, 4]);?// 添加數據 let addSet=set.add(5);console.log(addSet); // Set(5) {1, 2, 3, 4, 5}?// 刪除數據 let delSet=set.delete(4);console.log(delSet); // true?// 查看是否存在數據 4let hasSet=set.has(4);console.log(hasSet); // false?// 清除所有數據set.clear();console.log(set); // Set(0) {}

          實現并集(Union)、交集(Intersect)和差集(Difference)

          let a=new Set([1, 2, 3]);let b=new Set([4, 3, 2, 1]);?// 并集let union=new Set([...a, ...b]);// Set {1, 2, 3, 4}?// 交集let intersect=new Set([...a].filter(x=> b.has(x)));// set {1, 2, 3}?// 差集let difference=new Set([...b].filter(x=> !a.has(x)));// Set {4}

          12.Map數據結構

          JS當中的哈希表,使用方法如下:

          let map=new Map()map.set(1, 2)map.set(3, 4)map.set(1, 3)console.log(map)?創建var da=new Map();var jeskson={};遍歷da.forEach(function(value,key,map){}長度da.size刪除//da.delete() 刪除key,全部清楚da.clear()新增da.set(key,value)da.has(查索引值)?da.forEach((value,key)=>{})?for( let [key, value] of map){}?// let map=new Map( [[1,2], [3,4]] )?map的key任意都可以let o=function() { console.log('o')}map.set(o, 3)console.log(map.get(o)); // 3
          // map.jsvar Dictionary=function() { var items={}; // 檢查鍵 this.has=function(key) {  return key in items; } // 添加鍵值對 this.set=function(key, value){  items[key]=value; } // 通過鍵移除元素 this.delete=function(key) {  if(this.has(key)){   delete items[key]   return true  }  return false } // 鍵獲取值 this.get=function(key){  return this.has(key) ? items[key] : undefined; } // 列表返回字典值 this.values=function() {  var values=[];  for(var k in items) {   if(this.has(k)) {    values.push(items[k])   }  }  return values; } // 獲取全部鍵名 this.keys=function() {  return Object.keys(items); } // 獲取私有變量items this.getItems=function() {  return items; }}

          Map數據結構,它類似于對象,也是鍵值對的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對象)都可以當作鍵。

          13.Object.assign(對象的拷貝)

          Object.assign() 方法用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象。

          const target={ a: 1, b: 2 };const source={ b: 4, c: 5 };?const returnedTarget=Object.assign(target, source);?console.log(target);// expected output: Object { a: 1, b: 4, c: 5 }?console.log(returnedTarget);// expected output: Object { a: 1, b: 4, c: 5 }?> Object { a: 1, b: 4, c: 5 }> Object { a: 1, b: 4, c: 5 }

          語法

          Object.assign(target, ...sources)

          參數

          target

          目標對象

          sources

          源對象

          返回值

          目標對象。

          const obj={ a: 1 };const copy=Object.assign({}, obj);console.log(copy); // { a: 1 }
          • Object.assign()拷貝的是(可枚舉)屬性值
          • Object.assign方法的第一個參數是目標對象,后面的參數都是源對象
          • 如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性
          • 由于undefined和null無法轉成對象,所以如果它們作為參數,就會報錯
          • 如果undefined和null不在首參數,就不會報錯
          • 如果源對象某個屬性的值是對象,那么目標對象拷貝得到的是這個對象的引用(這個對象的任何變化,都會反映到目標對象上面。)
          Object.assign(undefined) // 報錯Object.assign(null) // 報錯?let obj={a: 1};Object.assign(obj, undefined)===obj // trueObject.assign(obj, null)===obj // true?const obj1={a: {b: 1}};const obj2=Object.assign({}, obj1);?obj1.a.b=2;obj2.a.b // 2?const target={ a: { b: 'c', d: 'e' } }const source={ a: { b: 'hello' } }Object.assign(target, source)// { a: { b: 'hello' } }?const source={  get foo() { return 1 }};const target={};?Object.assign(target, source)// { foo: 1 }

          Object.assign復制的是屬性值value,如果屬性值是一個引用類型,那么復制的其實是引用地址,就會存在引用共享的問題(Object.assign(target,source1,...,sourceN)淺拷貝的過程)

          要點:

          function ObjectAssign(target, ...sources) { // 對第一個參數的判斷,不能為undefined和null if(target===undefined || target===null) {  throw my TypeError('error'); } // 將第一個參數轉為對象(不是對象轉換為對象) const targetObj=Object(target); // 將源對象自身的所有可枚舉屬性復制到目標對象 for(let i=0; i<sources.length; i++){  let source=sources[i];  // 對于undefined和null在源中不會報錯,會直接跳過  if(source !==undefined && source !==null) {   // 將源象轉換成對象   // 需要將源自身的可枚舉數據進行復制   // Reflect.ownKeys(obj)   const keysArray=Reflect.ownKeys(Object(source));   for (let nextIndex=0; nextIndex < keysArray.length; nextIndex++) {    const nextKey=keysArray[nextIndex];    // 去除不可枚舉屬性    const desc=Object.getOwnPropertyDescriptor(source,nextKey);    if(desc!==undefined&&desc.enumerable){     targetObj[nextKey]=source[nextKey];    }   }  } } return targetObj;}if(typeof Object.myAssign !=='function'){ Object.defineProperty(Object, 'myAssign', {  value: ObjectAssign,  writable: true,  enumerable: false,  configurable: true });}

          淺拷貝 Object.assign 的實現原理

          拷貝第一層的基本類似值和第一層的引用類型地址:

          let da1={ name: 'da1', age: 1}?let da2={ name: 'da2', study: {  title: 'web' }}?let da3=Object.assign(da1,da2);console.log(da3);// {// name: 'da2',// age: 1,// study: { title: 'web' }// }console.log( da1===da3); // true?da2.name='da22';da2.study.title='web2';console.log(da2);// {// name: 'da22',// study: { title: 'web2' }// }?console.log(da1);// {// age: 1,// name: 'da2',// study: { title: 'web2' }// }

          如果源對象的屬性值是一個指向對象的引用,它也只拷貝這個引用地址哦!

          let da1={ name: 'da1', age: 1}let da2={ a: Symbol('dadaqianduan'), b: null, c: undefined}let da3=Object.assign(da1, da2);console.log(da3);// {// name: 'da1',// age: 1,// a: Symbol('dadaqianduan'),// b: null,// c: undefined// }console.log(da1===da3); // true
          let map=new Map([iterable])// Map是用來實現字典的功能-Object鍵值對

          動態屬性鍵

          // ES5 codevar  key1='one',  obj={    two: 2,    three: 3  };?obj[key1]=1;?// obj.one=1, obj.two=2, obj.three=3?// ES6 codeconst  key1='one',  obj={    [key1]: 1,    two: 2,    three: 3  };?// obj.one=1, obj.two=2, obj.three=3?// ES6 codeconst  i=1,  obj={    ['i' + i]: i  };?console.log(obj.i1); // 1

          補充:前端面試考點,HTML和CSS,性能優化,原型,作用域,異步,各種手寫代碼,DOM事件和Ajax,HTTP協議。

          • css(布局,定位,移動端響應式)
          • es(原型,原型鏈,作用域,閉包,異步,單線程)
          • webapi(DOM BOM,Ajax跨域,事件存儲)
          • 開發環境(版本管理,調試抓包,打包構建)
          • 運行環境(頁面渲染,性能優化,web安全)
          • 網絡通訊
          1. 布局(盒模型,BFC,float,flex)
          2. 定位,圖文樣式,移動端響應式(rem,media query,vw/vh),動畫、漸變
          3. 變量類型和計算(值類型和引用類型,類型判斷,邏輯運算)
          4. 原型和原型鏈(class,繼承,原型,原型鏈,instanceof)
          5. 作用域和閉包(作用域,自由變量,閉包,this)
          6. 異步(單線程,callback,應用場景,Promise,event-loop,async/await,微任務/宏任務)
          7. 模塊化(ES6 Module)
          8. DOM(樹形結構,節點操作,屬性,樹結構操作,性能)
          9. BOM(navigator,screen,location,history)
          10. 事件(綁定,冒泡,代理)
          11. ajax(XMLHttpRequest,狀態碼,跨域)
          12. 存儲(cookie,localStorage,sessionStorage)
          13. 開發環境(git,調試,webpack和babel,linux命令)
          14. 運行環境(頁面加載:加載,渲染。性能優化:加載資源優化,渲染優化。安全:xss,CSRF)
          15. HTTP協議:狀態碼,Method,Restful API,headers,緩存策略

          14.模板文字

          模板文字是es2015/es6的新功能,與es5及以下版本相比,可以通過新穎的方式使用字符串,先只需要反引號代替單引號或雙引號即可:

          const module_string=`dadaqianduan`

          它們之所以獨特是因為它們提供了很多用引號構建的普通字符串不具備的功能:

          1. 提供了定義多行字符串的語法;
          2. 提供了一種簡單的方法來插值字符串中的變量和表達式
          3. 允許您使用模板標簽創建DSL(領域特定的語言)

          使用多行字符串

          在es6之前的版本:

          // 要創建跨越兩行的字符串,必須\在行尾使用字符?const dada='dada \  dadaqianduan'  // 呈現效果:在兩行上創建一個字符串,但是僅在一行上呈現

          要在多行上呈現,則需要使用\n在每行的末尾添加

          const string='dada 魔王哪吒\n \  dadaqianduan'

          使用反引號打開模板文字后,只需要按enter鍵就行:

          const dada=`dadaqianduan 魔王哪吒`

          在這里請記住空間是有意義的:

          const da=`First            Second`

          使用trim()方法,可以消除第一個字符之前的任何空格

          插補:模板文字提供了一種將變量和表達式插入字符串的簡便的方法

          const da=`dadaqianduan ${mydada}`?${}里面可以添加任何東西?const da1=`dada ${1+2+3}`const da2=`dada ${dafun() ? 'x' : 'y'}`

          15.什么是解構賦值

          let da=['hello', 'world']let [firstName, surName]=dacosole.log(firstName, surName);

          解構賦值在于賦值,拷貝出來賦值給變量,而賦值的元素本身不會發生改變

          默認值

          let [da1, da2]=[];?console.log(da1); // undefinedconsole.log(da2); // undefined

          給變量賦值(默認值),防止出現undefined的情況:

          let [da1='da1', da2='da2']=['dadaqianduan]?console.log(da1); // dadaqianduanconsole..log(da2); // da2

          解構分配

          ES5中的索引提取這些值:

          var myArray=['a', 'b', 'c'];var  one=myArray[0],  two=myArray[1],  three=myArray[2];?// one='a', two='b', three='c'

          ES6解構允許使用更簡單方法:

          const [one, , three]=myArray;?// one='a', three='c'

          使用rest運算符(...)提取剩余元素:

          const [one, ...two]=myArray;?// one='a', two=['b, 'c']
          const myObject={  one:   'a',  two:   'b',  three: 'c'};?// ES6 destructuring exampleconst {one: first, two: second, three: third}=myObject;?// first='a', second='b', third='c'

          可變值交換

          var a=1, b=2;?// ES5 swapvar temp=a;a=b;b=temp;?// a=2, b=1?// ES6 swap back[a, b]=[b, a];?// a=1, b=2?[b, c, d, e, a]=[a, b, c, d, e];

          在ES6中,我們可以為任何參數分配默認值

          function dada(param={}) {

          函數返回多個值(函數只能返回一個值,但可以是一個復雜的對象或多維數組)

          function f() {  return [1, 2, 3];}?const [a, b, c]=f();?// a=1, b=2, c=3

          ES6 JavaScript深度解構

          默認情況下,找不到的屬性為undefined

          var {da}={bar: 'dada'}console.log(da)// undefined

          如果訪問不存在的父級的深層嵌套屬性,則將獲得異常。

          var {da:{bar}}={dada: 'dadaqianduan'}// Exception
          var key='dadaqianduan'var { [key]: foo }={ dadaqianduan: 'bar' }console.log(foo)// 'bar'
          var {da=3}={ da: 2 }console.log(da)// 2var {da=3}={ da: undefined }console.log(da)// 3var {da=3}={ bar: 2 }console.log(da)// 3?var [a]=[]console.log(a)//  undefinedvar [b=10]=[undefined]console.log(b)//  10var [c=10]=[]console.log(c)//  10?function da () {  return {    x: 1,    y: 2  }}var {x, y}=da()console.log(x)// 1console.log(y)// 2

          16.異步操作

          Callback

          Promise

          function loadScript(src) { return new Promise((resolve, reject)=> {  let script=document.createElement('script')  script.src=src  script.onload=()=> resolve(src)  script.onerror=(err)=> reject(err)  document.head.append(script) })}
          function loadScript(src) { let script=document.createElement('script'); script.src=src; document.head.append(script)}
          var promise=new Promise(function(resolve, reject){ resolve('傳遞給then的值')})promise.then(function(value){ console.log(value)},function(error){ console.error(error)})

          Promise對象是用于表示一個異步操作的最終完成(或失敗),以及其結果值。

          示例:

          const promise=new Promise((resolve, reject)=> { setTimeout(()=> {  resolve('da'); }, 200);});?promise.then((value)=> { console.log(value);});?console.log(promise);

          語法:

          new Promise(function (resolve,reject){...});

          描述:Promise對象是一個代理對象,被代理的值在Promise對象創建時可能是未知的,它允許你為異步操作的成功和失敗分別綁定相應的處理方法,這讓異步方法可以像同步方法那樣返回值,但并不是立即返回最終執行結果,而是一個能代表未來出現的結果的promise對象。

          一個Promise有以下幾種狀態:

          1. pending,初始狀態,既不是成功,也不是失敗狀態。
          2. fulfilled,意味著操作成功完成。
          3. rejected,意味著操作失敗。

          pending狀態的Promise對象可能會變為fulfilled狀態并傳遞一個值給相應的狀態處理方法。

          Promise.prototype.then和Promise.prototype.catch方法返回promise對象,所以它們可以被鏈式調用。

          方法:

          Promise.all(iterable)

          1. 返回一個新的promise對象
          2. 在iterable參數對象里所有的promise對象都成功時,才會觸發成功
          3. 當任何一個iterable里面的promise對象失敗,才會觸發promise對象的失敗
          4. 成功狀態會把一個包含iterable里所有promise返回值的數組作為成功回調的返回值,順序和iterable的順序一樣
          5. 如果這個新的promise對象觸發了失敗,會把iterable里的第一個觸發失敗的promise對象的錯誤信息作為它的失敗信息
          6. 場景,多用于處理多個promise對象的狀態集合

          Promise.any(iterable)

          1. 接收一個Promise對象的集合,當其中的一個promise成功,就返回那個成功的promise的值

          Promise.reject(reason)

          1. 返回一個狀態為失敗的Promise對象,然后將失敗信息傳遞給對應的處理方法

          Promise.resolve(value)

          1. 返回一個狀態由給定value決定的Promise對象

          Promise原型

          屬性:Promise.prototype.constructor返回被創建的實例函數,默認為Promise函數。

          方法:

          • Promise.prototype.catch(onRejected)
          • Promise.prototype.then(onFulfilled,onRejected)
          • Promise.prototype.finally(onFinally)
          function myAsyncFunction(url) { return new Promise((resolve, reject)=> {  const xhr=new XMLHttpRequest();  xhr.open('GET',url);  xhr.onload=()=> resolve(xhr.responseText);  xhr.onerror=()=> reject(xhr.statusText);  xhr.send(); });}

          17.ES6代理

          1. 默認情況下,代理不執行任何操作

          示例:

          var target={}var handler={}var proxy=new Proxy(target, handler)proxy.a='b'console.log(target.a)// 'b'console.log(proxy.c===undefined)// true

          為了更好地了解代理的有用性,讓我們做一些小練習。

          示例:

          想象一下,您已經17歲了,即將滿18歲。并且您希望您的程序在打開時自動向您祝賀。為此,您可以使用代理。

          var person={  name: "dada",  age: 17};?person=new Proxy(person, {  set(target, property, value) {    if (value===18) {      console.log("Congratulations! You are of legal age");      Reflect.set(target, property, value);      return true;    }  }});?person.age=18;?if (value < 13 && value > 99) {  throw new Error('The age should be between 13 and 99')} else {  Reflect.set(target, property, value)}

          語法:

          let p=new Proxy(target, handler)
          1. target 用Proxy包裝的目標對象
          2. handler 一個對象,其屬性是當執行一個操作時定義代理的行為的函數

          如果不想再調用key的時候,返回undefined:

          console.log(o.dada || '')

          使用Proxy

          let o={ name:'dada', age: 1}?let handler={ get(obj, key) {  return Reflect.has(obj, key)?obj[key]:'' }}?let p=new Proxy(o, handler)?console.log(p.from)

          希望從服務器獲取的數據只讀,不允許修改:

          for (let [key] of Object.entries(response.data)) {  Object.defineProperty(response.data, key, {  writable: false })}

          使用Proxy:

          let data=new Proxy(response.data, { set(obj, key, value) {   return false }})

          檢驗邏輯代碼:

          // Validator.js?export default(obj, key, vlaue)=> { if(Reflect.has(key) && value > 20) {  obj[key]=value }}?import Validator from './Validator'?let data=new Proxy(response.data, { set: Validator})

          使用Proxy,對讀寫進行監控:

          let validator={ set(target, key, value) {  if(key==='age') {   if(typeof value !=='number' || Number.isNaN(value)) {    throw new TypeError('Age must be a number')   }   if(value<=0){    throw new TypeError('Age must be a positive number')   }  }  return true }}?const person={ age: 12 }const proxy=new Proxy(person,validator)proxy.age='dada' // TypeError numberproxy.age=NaNproxy.age=0 // positive numberproxy.age=3

          示例:每個對象都有一個自己的id

          class Component { constructor() {  this.proxy=new Proxy({   id: Math.random().toString(36).slice(-8)  }) } get id() {  return this.proxy.id }}

          18.Generator

          function * dada() { for(let i=0; i<2; i++ {  yield console.log(i); }}?const da=dada()da.next()da.next()

          Generator函數與普通函數的區別在于定義的時候有一個*,執行下面函數:

          function* dada() {console.log('dadaqianduan');}dada(); // 沒有執行函數 如需要輸出,改為:var da=dada();da.next();

          要生成一個自增的id:

          var count_id=0;function dada_id() {count_id ++;return count_id;}

          方法

          Generator.prototype.next()返回一個由 yield表達式生成的值。?Generator.prototype.return()返回給定的值并結束生成器。?Generator.prototype.throw()向生成器拋出一個錯誤。

          書寫風格:

          function *da() {}?function* da(){}

          方法

          Generator對象方法:next,return,throw

          通過Next方法來獲取每一次遍歷的結果,這個方法返回一個對象,這個對象包含兩個value和done。

          value:當前程序的運行結果 done:遍歷是否結束

          next是可以接收參數的,這個參數可以讓你在generator外部給內部傳遞數據,這個參數就是作為yield的返回值。

          return()方法可以讓generator遍歷終止

          function * da() { yield 1 yield 2 yield 3}var d=da()console.log(d.next()) // {value:1,done:false}console.log(d.return()) // {value:undefined,done:true}console.log(d.next()) // {value:undefined,done:true}

          return可以傳入參數,作為返回的value的值

          function * da() { yield 1 yield 2 yield 3}var d=da()console.log(d.nex()) // {value:1,done:false}console.log(d.return(100)) // {value:100,done:true}console.log(d.next()) // {value:undefined,done:true}

          throw()方法在generator外部控制內部執行的“終斷”

          generator函數聲明:

          function* genFunc(){...}const genObj=genFunc();

          generator表達式:

          const genFunc=function* () {...}const genObj=genFunc();

          對象中定義:

          const obj={ * generatorMethod(){  ... }}const genObj=obj.generatorMethod();

          類定義(類聲明或類表達式):

          class MyClass{ * generatorMethod(){  ... }}const myInst=new MyClass();const genObj=myInst.generatorMethod();

          最簡單的iterator遍歷規范:

          authors[Symbol.iterator]=function(){ // this return {  next(){   return{    done:false,    value:1   }  } }}

          19.module

          在es6前,js文件之間的導入,導出是借助require.js,sea.js,如現在使用import,export,來實現原生javascript的導入,導出。

          export:

          導出變量或者常量?export const da='dadaqianduan'export let da1='da1'export var da2='da1'?const name='dada'let name1='dada1'export { name, name1}?導出函數export function da(value){ console.log(value)}?const da=(value)=> { console.log(value);}?export { da}?導出Objectexport({ name: 'da1', message: 'dadaqianduan'})?let da={ name: 'name1'}export { da}?導出Classclass Da { constructor(){  this.id=1 }}export { Da}?export class Da { constructor() {  this.id=1 }}?修改導出名稱const name='da1'export { name as cname}export default name

          import

          // 直接導入const name='dada'let name1='dada1'var name2='dada2'export { name as cname}export default name2?import name2, {name1, name} from A
          export const sqrt=Math.sqrt;export function square(x) { return x * x;}export function dada(x,y) { return sqrt(square(x) + square(y));}?import {square,da} from 'da';console.log(square(11)); // 121console.log();
          export default function() {...}import myFunc from 'myFunc';export default class{...}import MyClass from 'MyClass';const inst=new MyClass();

          20.import, export

          require--lib.js--function add(x,y){ return x + y}module.exports={ add: add,};?--main.js--var add=require('lib').addd;console.log(add(1,2));
          import--lib.js--export function add(x,y) { return x + y}--main.js--import {add} from 'lib';console.log(add(1,2));
          --lib.js--export const sqrt=Math.sqrt;export function square(x) { return x * x;}export function da(x,y) { return sqrt(square(x)+square(y));}--main.js--import {square, da} from 'lib'??--myFunc.js--export default function() {...};--main.js--import myFunc from 'myFunc';myFunc();

          21.Array.prototype.includes,Promise

          該方法判斷一個數組是否包含一個指定的值,返回布爾值

          let da1=[1,2,3];console.log(da1.includes(2));
          arr.find(function(item){return item===1;})?arr.filter(function(item){return item===2;})?Math.pow(2,3)->2**3
          async function firstAsync(){ let promise=new Promise ((resolve,reject)=> {  setTimeout(function(){   resolve('dadaqianduan')  },1000) }) console.log(await promise) console.log(await Promise.resolve(1)) console.log(2) return Promise.resolve(3)}firstAsync().then(val=> { console.log(val)})

          await后面是Promise對象

          Object.values()返回一個數組,其元素是在對象上找到的可枚舉屬性值。

          let da={ 'da': 1, 'da2': 2}console.log(Object.value(da)) // [1,2]?Object.values是在對象上找到可枚舉的屬性的值,所以只要這個對象是可枚舉的就可以

          Object.entries()方法返回一個給定對象自身可枚舉屬性的鍵值對數組

          22.JS異步進階

          題目一:

          Promise.resolve().then(()=>{ console.log(1)}).catch(()=>{ console.log(2)}).then(()=>{ console.log(3)})

          題目二:

          Promise.resolve().then(()=>{ console.log(1) throw new Error('da')}).catch(()=>{ console.log(2)}).then(()=>{ console.log(3)})

          題目三:

          Promise.resolve().then(()=>{ console.log(1) throw new Error('da')}).catch(()=>{ console.log(2)}).catch(()=>{ console.log(3)})

          題目四:

          async function fn() { return 1}(async function() { const a=fn() // ?? const b=await fn() // ??})()

          題目五:

          console.log(100)setTimeout( ()=> { console.log(200)})Promise.resolve().then( ()=> { console.log(300)})console.log(400)

          題目六:

          async function async1() { console.log('async1 start') await async2() console.log('async1 end')}?async function async2 () { console.log('async2')}?console.log('script start')?setTimeout(function(){ console.log('setTimeout')},0)?async1()?new Promise(function (resolve){ console.log('promise1') resolve()}).then(function(){ console.log('promise2')})?console.log('script end')

          加載圖片:

          // 加載function  loadImg(src) { const p=new Promise(  (resolve,reject)=> {   const img=document.createElement('img')   img.onload=()=>{    resolve(img)   }   img.onerror=()=>{    const err=new Error('圖片加載失敗')    reject(err)   }   img.src=src  } ) return p}const url='https'const p=loadImg(url)?p.then(img=>{ console.log(img.width) return img}).then(img=>{ console.log(img.height)}).catch(ex=> { console.error(ex)})
          async function async1() { console.log('async1 start') // 2 await async2() // undefined console.log('async1 end') // 5}async function async2() { console.log('async2') // 3}console.log('script start') // 1async1()console.log('script end') // 4

          for...of常用于異步的遍歷

          function add(num) { return new Promise(resolve=> {  setTimeout(()=>{   resolve(num*num)  },1000) })}const nums=[1,2,3]nums.forEach(async(i)=>{ const res=await add(i)})

          23.宏任務和微任務

          宏任務:setTimeout,setInterval,ajax等 微任務:Promise async/await

          微任務執行時比宏任務要早:

          宏任務:DOM渲染后觸發,如setTimeout

          微任務:DOM渲染前觸發,如Promise

          24.For await of 異步操作集合

          function da(time) { return new Promise(function(resolve,reject){  setTimeout(function(){   resolve(time)  },time) })}async function test() { let arr=[da(2000),da(1000),da(3000)] for await (let item of arr) {  console.log(Date.now(), item) }}
          const input={ a: 1, b: 2}const output={ ...input, c: 3}console.log(output)?const input={ a: 1, b: 2, c: 3}let {a, ...rest }=input

          25.Array.prototype.flat()

          該方法會按照一個可指定的深度遞歸遍歷數組,并將所有元素與遍歷到的子數組中的元素合為一個新數組。

          Array.prototype.flat()建議將數組遞歸展平至指定范圍depth并返回新數組。

          depth(指定要提取嵌套數組的結構深度)

          語法:Array.prototype.flat(depth)

          depth —默認值1,Infinity用于展平所有嵌套數組。

          const numbers=[1, 2, [3, 4, [5, 6]]];?// Considers default depth of 1numbers.flat(); ?> [1, 2, 3, 4, [5, 6]]?// With depth of 2numbers.flat(2); ?> [1, 2, 3, 4, 5, 6]?// Executes two flat operationsnumbers.flat().flat(); ?> [1, 2, 3, 4, 5, 6]?// Flattens recursively until the array contains no nested arrays?numbers.flat(Infinity)> [1, 2, 3, 4, 5, 6]

          語法:Array.prototype.flatMap(callback)

          callback:function產生新Array的元素。

          const numbers=[1, 2, 3];?numbers.map(x=> [x * 2]);> [[2], [4], [6]]?numbers.flatMap(x=> [x * 2]);> [2, 4, 6]

          Object.fromEntries

          Object.fromEntries執行與的相反操作Object.entries。它將一組鍵值對轉換為一個對象。

          const records=[['name','da'], ['age', 32]];?const obj=Object.fromEntries(records);?> { name: 'da', age: 32}?Object.entries(obj);?> [['name','Mathew'], ['age', 32]];

          Symbol.prototype.description

          只讀屬性,返回Symbol對象的可選描述:

          Symbol('desc').toString();> "Symbol(desc)"?Symbol('desc').description;  > "desc"?Symbol('').description;      > ""?Symbol().description;> undefined

          點關注,不迷路

          好了各位,以上就是這篇文章的全部內容,能看到這里的人都是人才。我后面會不斷更新技術相關的文章,如果覺得文章對你有用,歡迎給個“贊”,也歡迎分享,感謝大家 !!


          喜歡本文的朋友,歡迎關注公眾號 程序員小灰,收看更多精彩內容

          日,在世紀之初風靡全球、一時風頭無兩的女星“小甜甜”布蘭妮·斯皮爾斯再次出現在公眾視野。但這一次,不是因為她讓萬人空巷的全球巡演,不是因為她熱銷3000萬張的個人專輯,而是因為她請求法院解除父親杰米·斯皮爾斯對自己的永久監護權。

          “小甜甜”布蘭妮·斯皮爾斯。

          在6月23日的庭審中,布蘭妮通過語音連線,向法官講述了過去13年中,自己遭受父親及其工作團隊的虐待,具體內容包括被迫長時間、高強度工作,接受非自愿并有較大副作用的精神治療,安置節育環以致她無法再次懷孕。此外,處于監護狀態下的她,完全喪失了對自己財產的使用權利,連簡單的度假、美甲、按摩等需求都無法得到父親批準,“在加州,只有被關起來的性工作者才這么慘——信用卡、現金、手機、護照全部被收走。”

          在通話的最后,布蘭妮說道:“我希望我能一直和你聊下去。我生怕一掛掉電話,就會立刻回到被人全盤否定的狀態。我覺得自己被排擠、被欺負、被冷落……我受夠了這種孤獨的狀態。我值得擁有和任何人一樣的權利,我也想有孩子、有家庭、有自己的人生……”

          2008年,布蘭妮因使用精神類藥物而被強制送往精神醫療中心治療。之后,她的父親杰米向法院申請,成為了布蘭妮的“永久監護人(Conservator)”。根據美國法律,“監護(Conservatorship)”是一種將個體的個人、經濟和法律決策權轉讓給他人的機制。對于因年老癡呆、身體或精神缺陷而無法自主作出決定的成年人,法院可以任命一位法定監護人,負責監督他的日常活動,如醫療保健或生活安排,并對它的財務事務進行托管。

          此后13年,布蘭妮便一直處于被父親“監護”的狀態之下。盡管在此期間,她發行了4張專輯,其中2張成為白金唱片(在美國,專輯銷量超過100萬張即可被認證為“白金唱片”),并在2018年進行了全球巡回演出,但法院仍判定,“她的精神狀態無法自主作出明智的決定”。

          在社交網絡上,由布蘭妮粉絲發起的“釋放布蘭妮(Free Britney)”運動已進行多年。自2020年起,布蘭妮本人也試圖通過法律途徑擺脫被父親監護的狀態。

          而這一次,布蘭妮本人通過連線出庭,聲淚俱下地講述了自己這13年的遭遇。這不僅牽動了成千上萬關注她的粉絲的心,也讓更多粉絲之外的法律研究者、婦女權利保護者乃至美國國會議員開始反思“監護人”制度的正當性。

          同時,布蘭妮的遭遇也引發了不少好萊塢工作者的共情。在他們看來,是娛樂圈長期盛行的厭女風氣,加之狗仔娛記和大眾審視的步步緊逼,共同造成了布蘭妮的悲劇。

          為什么身為成年人、具有基本工作能力的布蘭妮還要被“監護”?是什么讓她被判定為“精神失常”,被媒體和輿論塑造為一個“瘋女人”?誰該為布蘭妮的悲劇負責?本文系統梳理了國內外多家媒體以及相關領域專家的各方觀點。

          撰文 | 肖舒妍

          備受爭議的監護權:是保護還是虐待?

          “如果我能工作、賺錢、還能付錢給別人,我就不應該被監護,”在6月23日的庭審中,布蘭妮提出了這一觀點,“這毫無意義。法律需要改變。”

          最初,布蘭妮是如何“陷入”監護之中的?

          2007年,布蘭妮的狀態一度滑入低谷。飽受輿論壓力的她先是在一家理發店公開剃光了自己的頭發,對著鏡頭大喊“我受夠了別人不停碰我!”緊接著又陷入了和前夫凱文·費德勒爭奪兩個孩子撫養權的官司。她失去了孩子的獨立撫養權,僅獲得探視權。在一次探望孩子卻被前夫拒之門外之后,面對狗仔的長槍短炮和不斷追問,布蘭妮情緒崩潰,舉起一把雨傘企圖趕走狗仔,卻被狗仔拍下了她失控的狀態,放上雜志封面。一時間,布蘭妮“陷入瘋癲”、成為“瘋女人”的傳聞便不絕于耳。

          2007年2月,布蘭妮情緒失控之后,在理發店公開剃光了自己的頭發。

          次年一月的一個晚上,費德勒來到布蘭妮家中,準備接走兩個孩子,卻發現布蘭妮把自己和兒子鎖在衛生間,不肯開門。警方趕到后,發現布蘭妮“受到精神類藥物影響”,于是將其強制送往精神醫療中心治療。在她住院期間,杰米·斯皮爾斯向法院提交申請,獲得了女兒的“臨時監護權”。在2008年10月,“臨時監護權”又轉為“永久監護權”。

          盡管此后布蘭妮的精神狀態有所好轉,監護托管卻并未因此終止。熟悉此案的律師Vivian Thoreen在《紐約時報》拍攝的紀錄片《陷害布蘭妮》(Framing Britney Spears)中提到,如果被監護人想要終止監護,需要向法院提交一份請愿書,并提供自己已不需要監護(或監護已經失效)的證據,但在她所參與的所有案件中,沒有一個被監護人曾成功終止監護,“一旦進入監護系統,就很難再有可能脫身”。

          “如果個人想要終止監護,責任應該由反對終止的一方承擔(以證明監護有必要繼續),但實際上,通常情況下,往往是個人必須證明他們不再需要被監護。”卡多佐法學院(Cardozo School of law)的臨床法學教授、監護法專家萊斯利·薩爾茲曼(Leslie Salzman)這樣表示。

          “就像‘第22條軍規’的規定,瘋子可以免于飛行任務,但必須由飛行員本人提出申請,而本人一旦提出申請,便可證明他并不是瘋子。想從監護中脫身,布蘭妮必須要證明自己有管理生活以及財產的能力,但她正處于監護中,也就意味著她沒有上述能力。” 支持布蘭妮的導演史塔克向《綜藝》雜志說道。

          一個精神正常的人該如何證明自己精神正常呢?如果布蘭妮在監護狀態下身心健康,則可以解釋為“監護制度行之有效”;如果布蘭妮對監護狀態掙扎抵抗,又可以說明“她無法做出理智判斷,必須接受監護”;在監護狀態下,布蘭妮犯的任何一個小錯誤,都能夠作為她不宜解除監護的證據。但即使一個身心健全的普通人,也并不總能做出符合個人最大利益的明智決定。

          “如果一個普通人決定,‘我要休假,喝幾杯酒,吃一大堆甜甜圈,然后小睡一會兒,’這是很正常的事情,屬于個人基本權利,”記者Sara Luterman舉例論證,“但如果你有精神疾病或生理缺陷,這就會被視為你‘沒有能力負責任地管理自己的生活’的進一步證據。”

          在2020年的庭審中,法官在判決時提出,布蘭妮的情況很特殊,她是一名“具有高能力的受監護人”。許多人對此感到疑惑,這顯然是個自相矛盾的詞。

          另一方面,布蘭妮的監護人人選也飽受質疑。杰米·斯皮爾斯雖然是布蘭妮的父親,卻在2008年之前長期缺席她的人生,與妻子離婚多年,甚至找不到一張他和女兒合影。此外,他曾因酗酒被送進強制康復機構,多次創業失敗最終申請破產。在紀錄片《陷害布蘭妮》中,布蘭妮曾經的唱片營銷總監回憶:“我不能判斷杰米是個怎樣的人,我只見過他一面。他對我說過唯一的一句話就是:‘我女兒會變得很富有,她會給我買艘大船。’”

          布蘭妮的父親杰米·斯皮爾斯。(圖源:紀錄片《陷害布蘭妮》)

          無論是布蘭妮的母親林恩(Lynn),還是自她5歲起就認識她、之后一直陪伴她參加活動、幫助她打理事業的長期助理Felicia Culotta,都比杰米更加了解、關心布蘭妮的生活狀態。在進入監護狀態之后,布蘭妮曾多次向法院提出更換監護人,在2020年更表示,如果杰米繼續掌控她的事業,她將拒絕演出。

          “雖然受監護人未必能夠選擇自己的監護人,但他們當然可以推薦并說明希望誰成為監護人。法院應該對這一請求給予充分考慮。考慮到監護人要托管受監護人的個人事務,他要能夠被對方所接受。”薩爾茲曼這樣認為。而在布蘭妮的案例中,她的需求顯然沒有被尊重。

          布蘭妮的支持者要求法院終止她的“被監護”狀態。

          加之布蘭妮作為全球巨星所擁有的高額財產,進一步提高了監護權的復雜程度。布蘭妮的公開資產為6000萬美元,本身極為可觀,同時有媒體披露,布蘭妮的實際財產超過6億美元,但大部分均被杰米轉移。此外,布蘭妮還要提供自己全部收入的1.5%作為“監護傭金”。很難判斷對杰米而言,女兒是需要照顧的受監護人,還是日進斗金的搖錢樹。

          布蘭妮在法庭陳述中提到,自己被要求每周工作7天,每天工作10小時,2018年的巡回演出和在拉斯維加斯長達4年的駐唱都是被迫簽約,即便在發燒40度時她也要上臺演出。根據《紐約時報》的調查,布蘭妮在拉斯維加斯的演出總票房高達1.37億美元,但屬于布蘭妮的分成只有每周2000美元。

          而在2020年接受法院問詢時,杰米團隊的律師更提出,他們將采用一種“全新的混合型商業模式”,幫助布蘭妮完成事業開拓和財富增長。有團隊表示,監護團隊成立時,布蘭妮的資產只有幾百萬美元,而現在已超過6000萬美元,這證明了團隊的監護工作行之有效。但他們似乎沒有意識到,托管成立的初衷,是為了幫助布蘭妮恢復身心健康,而不是讓她成為賺錢機器。

          在布蘭妮與杰米對簿公堂時,她不僅要支付自己的治療費用、自己的法務費用,還要支付杰米團隊的工資,杰米方的法務費用。

          杰奎琳·布徹(Jacqueline Butcher)曾是斯皮爾斯家族的好友,并在2008年提供證詞幫助杰米獲得監護權。而現在,她對自己過去的行為表示遺憾,“當時我以為自己是在幫忙,但實際上我幫一個腐敗的家族掌控了一切。”

          “盡管布蘭妮的情況看起來非常極端并令人不安,” Erica Schwiegershausen在《紐約》雜志中寫道,“她講述的大部分內容——例如未經同意接受藥物治療、接受非自愿精神病評估和被強制送進精神病院——對任何有精神疾病經歷的人來說都很熟悉。”據估計,在美國有數以百萬計的智力缺陷或社交能力缺陷者被剝奪了法律行為能力,處于某種形式的監護之下。這導致了一系列虐待行為,包括強制醫療、強制避孕、強制終止妊娠、非自愿監禁、強制生活安排和行動自由受限。

          紀錄片《陷害布蘭妮》劇照。

          父權控制與女性反抗:一場漫長的斗爭

          在布蘭妮所遭受的一系列限制和虐待中,“強制避孕”一點尤其令人不解,無論從任何角度都無法看作是對于布蘭妮的保護。在法庭陳述中,布蘭妮說道:“我希望能夠結婚生子,但這卻被監護團隊所禁止。我的體內放有宮內節育器,因此無法懷孕。我想取出宮內節育器,再生一個孩子,可是這個所謂的團隊不讓我接近醫生。”

          限制布蘭妮生育這一行為,幾乎遭到了美國各黨派人士的譴責,包括美國計劃生育行動基金董事會主席亞歷克西斯·麥吉爾·約翰遜(Alexis McGill Johnson),保守派共和黨參議員特德克魯茲(Ted Cruz)和眾議員南希·梅斯(Nancy Mace)、卡羅琳·馬洛尼(Carolyn B.Maloney)。

          “你可以打著保護的幌子強迫一個女人絕育,這太瘋狂了。如果這能發生在布蘭妮·斯皮爾斯身上,全國還會有多少其他女人在默默受苦?” 眾議員南希·梅斯在推特上寫道。

          “從一開始,男人就控制著女人的身體、思想和抱負,”另一位眾議員卡羅琳·馬洛尼則向《華盛頓郵報》表示,“斯皮爾斯女士和世界各地的任何女性一樣,理應對自己的身體、權益和財產享有完全的自主權。”

          這一控制在布蘭妮身上顯得尤其讓人心碎。在出道伊始,布蘭妮的形象便與獨立、自主、強大聯系在一起。在早年的采訪視頻中,她笑著告訴記者:“我知道自己所有的合同,我知道自己所有要做的工作,我才不是那種只聽經紀人話的女孩。”

          曾與布蘭妮合作過的伴舞也多次提到:“她絕不是牽線木偶。她是老大(boss)。”對于演出造型、舞蹈動作、演唱歌曲,布蘭妮都會堅定而自信地提出自己的看法。

          在粉絲看來,布蘭妮展示出的“女人的我行我素,不必再取悅他人”,正是她最大的魅力所在。“布蘭妮的形象不再只是父權制下少女形象的意義典范,而是父權控制與女性抵抗,甚至資本主義與個體之間的符號沖突(semiotic struggle)的表現。”公眾號“看理想”在一篇文章中如此概括布蘭妮的文化意義。

          但是當時的美國大眾,一方面狂熱地喜愛這樣一個“既性感又純潔、既乖巧又獨立”的熒幕形象,一方面卻又沒做好準備接受這樣的女性走出熒幕、走進生活。“在‘圣經地帶’出生、長大的布蘭妮,身上集中了美國人的兩種期望:既要穿著性感,給人曖昧的想象,又要保持純潔的處女身;既要盡可能地觸探性的界限,又絕不能越雷池半步。兩種期望顯然是矛盾的,這讓她受到了嚴格的形象、行為、道德審查。” 作者李孟蘇在“三聯生活周刊”公眾號文章中寫道。

          紀錄片《陷害布蘭妮》截圖。

          當時美國馬里蘭州的州長夫人肯德爾·埃爾利希(Kendel Ehrlich)甚至公開對媒體表示:“如果我有機會,真想一槍崩了布蘭妮。”以此指責布蘭妮給孩子們做了不良的榜樣,讓美國的媽媽們感到不安。

          在一次訪談中,主持人當著布蘭妮的面播放了這段視頻,布蘭妮聽到后緊蹙雙眉,微微搖頭,“太可怕了……可是,我不是用來幫她們教育小孩的啊。”她忍不住哭了出來。

          在紀錄片《陷害布蘭妮》中,一位業內人士提出,沒有一個男孩樂隊的成員會像布蘭妮這樣受到如此嚴格的道德審查。

          “讓我們直截了當地說:發生在布蘭妮·斯皮爾斯身上的事情,永遠不會發生在一個男版的她身上。”專欄作家海琳·奧倫(Helaine Olen)在《華盛頓郵報》一針見血地寫道,“想想看,有多少男明星曾在公共場合做出過瘋狂的、情緒失控的、甚至磕了藥似的行為。幾秒內我就能說出邁克爾·杰克遜、坎耶·韋斯特和小羅伯特·唐尼的名字。小羅伯特·唐尼曾被警察帶走,理由是他光著身子走進鄰居家,躺在孩子的床上睡著了。當鄰居撥打911時,你甚至可以聽到他的鼾聲。還有布蘭妮的父親,杰米——一個非常不合時宜的男人,布蘭妮的前夫曾申請并獲得了限制令,禁止杰米靠近自己以及兩個孩子。”

          “管理學理論表明,人們會默認男人是有能力的,不管他們的過去如何;而女性卻不得不一次又一次證明自己的工作能力、財務敏感、理智水平。” 海琳·奧倫繼續寫道,“縱觀歷史,監護制度和非自愿承諾制度一直被用來控制女性的人身自由和財務自由。”

          紀錄片《陷害布蘭妮》截圖。一位曾經合作過的演員在接受采訪時如此評論布蘭妮。

          回看當時輿論對布蘭妮的圍觀,以及對她“精神病”的指控,都彌漫著濃厚的厭女氣息。莫伊拉·多納根(Moira Donegan)在《衛報》寫道:“歷史上,許多人僅僅因為持有不受歡迎的觀點,或行為冒犯了普遍傳統,就被認為是瘋了。女性尤其深受其害。長期以來,那些厭惡女性或覬覦女性財富的人,總能因為一些微不足道的原因,把女性指控成瘋子。”

          而在紀錄片《陷害布蘭妮》于今年2月5日上線之后,聯合國婦女署活動家蒙羅·伯格多夫(Munroe Bergdorf)在社交媒體上嚴肅表態:“布蘭妮為自己所取得的成就付出了太大的代價。當時的社會選擇對精神疾病避而不談,不愿正視女性的自我風貌,不知如何消解四處蔓延的厭女情緒。女性公眾人物被媒體拿來消費。世人追捧你到制高點,為的是最終能親手將你毀滅。”

          大眾審視與娛樂產業:誰是施害者,誰是受害者?

          “世人追捧你到制高點,為的是最終能親手將你毀滅。”這句話,可能道出了布蘭妮悲劇的本質。

          首張個人專輯《…Baby One More Time》讓她一夜成名,全球銷量超過3000萬張,成為世界銷量最高的專輯之一。但水能載舟亦能覆舟,隨著喜愛而來的是大眾對她從頭到腳的審視,以及對她私生活永不饜足的窺探。

          布蘭妮的首張個人專輯《…Baby One More Time》封面。

          在布蘭妮10歲剛出道的一段視頻中,一位滿頭銀發的主持人問身高才到自己腰身的布蘭妮:“你有男朋友了嗎?……你可以考慮我。”而當時的觀眾,并沒有人感到不適。

          年齡稍長之后,布蘭妮遇到的采訪問題更加刻薄露骨:“你還是處女嗎?”“你知道所有人都在想著你的胸嗎?”而在2001年的一段電視采訪中(當時布蘭妮只有19歲),主持人直言:“對許多人來說,你是一個矛盾體。一面是甜美、純潔、童貞,一面是只穿著內衣的性感蕩婦。”布蘭妮只好尷尬回應:“不是‘只穿著內衣’,在《滾石》的封面上是唯一一次。我在演出時不會。”

          攝影師大衛·拉查佩爾(David LaChapelle)拍攝的這張著名照片登在了《滾石》的封面上,讓布蘭妮成為被公眾凝視的對象。

          一張布蘭妮的“黑照”,價值100萬美元,在紀錄片《陷害布蘭妮》中職業狗仔記者Daniel Ramos提到。重賞之下,必有勇夫。于是布蘭妮的私人生活也暴露在了長槍短炮、層層包圍的鏡頭之下。

          在前文提及的布蘭妮在前夫家探視孩子未果后,正是Daniel Ramos步步緊逼、追問布蘭妮的心情,最終致使她情緒崩潰,揮舞雨傘砸向了他開來的車。在拍下了布蘭妮的失態照片后,他摸著車上坑坑洼洼的痕跡,喜笑顏開:“天啊,這次賺大了。”憑借幾次三番偷拍到布蘭妮形容憔悴、情緒失控或是剃成光頭的照片,Daniel Ramos賺得盆滿缽滿。

          多年以后,《紐約時報》紀錄片團隊問這名娛樂記者:“你覺得自己影響了布蘭妮的生活嗎?”

          Daniel Ramos回答:“我不這么認為。在她身邊工作了這么多年,她從來沒對我們說‘我不想理你們,別煩我了’。”

          團隊反問:“她說過‘別煩我’吧?”

          Daniel Ramos想了片刻,回答道:“她是說過‘今天你能讓我一個人待著嗎?’,但并不代表說‘永遠別煩我’啊。”

          紀錄片《陷害布蘭妮》截圖。直播采訪時,主持人問布蘭妮:“你是不是希望那些狗仔隊都走開?”

          比起布蘭妮巔峰時期溫暖陽光的笑容,她低谷階段剃光的頭發、濃重的黑眼圈似乎更能勾起大眾的注意、引發人們的唏噓。

          《每日人物》主筆安小慶曾把布蘭妮的境遇與她筆下香港的“瘋女人們”進行類比。在《香港為什么有那么多“瘋女人”? 》一文中,她寫道:

          藍潔瑛頂著“四大癲王“的封號,與后來的“瘋女人“們——吳綺莉、吳卓林、關淑怡一起,以肉身的磨蝕和精神的苦痛,源源不斷地給香港社會供應著日常運轉所需的“瘋癲“樣本。比起“傳奇“,他們更熱衷狗血和瘋癲,尤其是那些從原有高階層跌落折墮的“瘋女人“的故事,因為這不僅能夠滿足世人獵奇的心態,還能最大程度地警示所有的香港人、尤其是香港女人——在這個國際自由貿易港,這個頂級消費社會,貧窮是可恥的,階層墜落更是不可饒恕的。

          2021年3月,布蘭妮·斯皮爾斯在社交媒體上發了一段跳舞的視頻,配文提到,在《陷害布蘭妮》上線后,她足足“哭了兩個星期”,“我的人生總是被猜測、被注視、被審判。為了我的精神狀態(保持理智和穩定),我需要在每個晚上跳舞,來感受野性地、作為一個人似的活著。我的整個人生都被表演在公眾面前。將脆弱托付給世界、展現給世界,真的需要很大的力量,因為我總是被審判、被侮辱,被媒體搞得狼狽不堪,直到今天依然如此。”

          在《新周刊》的記者眼中,紀錄片《陷害布蘭妮》是對過去傷害布蘭妮的小報的一次清算,人們希望借此將這位昔日美國偶像從疑似泥沼般的生活里解救出來。

          但很難判斷,布蘭妮自己是否需要媒體的再一次巨大關注。對她而言,《陷害布蘭妮》到底是對她的拯救,還是再一次揭開她的疤痕?就像粉絲發起的“釋放布蘭妮”運動,通過布蘭妮社交媒體上的蛛絲馬跡來解讀她的生活狀態,從而得出她需要“被拯救”的結論,這一方面讓更多粉絲聯合起來,給予布蘭妮支持和力量,另一方面,也讓布蘭妮的隱私和傷痛暴露于公眾面前。

          紀錄片《陷害布蘭妮》截圖。

          “世界根本無權知道有關布蘭妮·斯皮爾斯的任何事情。但是,大眾和媒體卻喜歡對女性公眾人物生活中最私密的細節進行關注和判斷。每一個粉絲、記者和媒體評論員都認為自己有權了解和評判斯皮爾斯最私人的事情。這是一種令人不安的侵犯,和監護本身帶來的控制似乎并沒有什么不同,”霍夫斯特拉大學(Hofstra University)公共關系副教授卡拉·阿拉莫(Kara Alaimo)發表在CNN的評論中寫道,“監護是否必要,是圍繞布蘭妮·斯皮爾斯的心理健康和財務狀況展開的。這些都是她有權保密的問題。在美國,關于這方面的隱私權規定非常明確。《紐約時報》和其他新聞媒體的報道并不違法,但很難把挖掘女性私生活理解為為了什么公共利益。”

          這意味著,在屏幕之前審視、關注甚至支持著布蘭妮的人們也并非全然置身事外,以至于在某種程度上成為了悲劇的幫兇。公眾號“看理想”則進一步提出,這種審視不僅傷害了布蘭妮,也可能在傷害我們,在觀看明星被規訓的同時,我們自身也成了規訓的對象——

          如果身為“監視主體”的我們,總是在以自我感覺良好的傲慢態度和侵略性眼光來檢視著這些明星的話,也會在不知不覺間通過自我規訓的方式,默然順從了偏見、刻板和節目所宣揚的美學標準與行為規范。——想要安全逃脫這種“全景敞視機制”(panopticism)的控制,是絕無可能的。

          參考鏈接:

          1.紀錄片《陷害布蘭妮》(Framing Britney Spears),《紐約時報》團隊拍攝

          2.https://www.forbes.com/sites/maddieberg/2021/06/23/britney-spears-full-statement-against-her-conservatorship/?sh=4cb8fb0421bd

          3.https://www.npr.org/2021/06/24/1009726455/britney-spears-conservatorship-how-thats-supposed-to-work

          4.https://www.nytimes.com/2021/07/01/us/politics/britney-spears-warren-casey-conservatorship.html

          5.https://www.foxnews.com/entertainment/free-britney-a-bipartisan-cause-on-capitol-hill-after-impassioned-testimony

          6.https://www.hrw.org/news/2021/06/26/britney-spearss-conservatorship-mirrors-reality-millions-disabilities

          7.https://www.cnn.com/2021/06/23/opinions/britney-spears-deserves-privacy-alaimo/index.html

          8. https://www.nytimes.com/2021/06/29/opinion/britney-spears-conservatorship.html

          9.https://www.washingtonpost.com/opinions/2021/06/26/britney-spears-conservatorship-mistreatment/

          10.《是誰陷害了“小甜甜”布蘭妮?》,看理想,2021-02-25,https://mp.weixin.qq.com/s/Cg02JcHn4uOpKWqEEhosKA

          11.《從小甜甜到“瘋女人”,誰在陷害布蘭妮?》,新周刊,2021-05-02,https://mp.weixin.qq.com/s/dhFRiUXByvhgvwCqFTcPrA

          12.《誰制造了小甜甜布蘭妮的悲劇?》,李孟蘇,三聯生活周刊,2021-03-10,https://mp.weixin.qq.com/s/G4_VB4xBSSTzET-qYsqgFw

          13.《香港為什么有那么多“瘋女人”?》,安小慶,每日人物,2018-08-19,https://mp.weixin.qq.com/s/K5fK_TIkH5VrTClPjBMG8g

          撰文 | 肖舒妍

          編輯 | 李永博;王青

          校對 | 王心

          端開發者丨JavaScript

          實際需求中開始

          要求:

          • 此類繼承自 Date,擁有Date的所有屬性和對象

          • 此類可以自由拓展方法

          形象點描述,就是要求可以這樣:

          1. // 假設最終的類是 MyDate,有一個getTest拓展方法

          2. let date=newMyDate();

          3. // 調用Date的方法,輸出GMT絕對毫秒數

          4. console.log(date.getTime());

          5. // 調用拓展的方法,隨便輸出什么,譬如helloworld!

          6. console.log(date.getTest());

          于是,隨手用JS中經典的組合寄生法寫了一個繼承,然后,剛準備完美收工,一運行,卻出現了以下的情景:

          但是的心情是這樣的: 囧

          以前也沒有遇到過類似的問題,然后自己嘗試著用其它方法,多次嘗試,均無果(不算暴力混合法的情況),其實回過頭來看,是因為思路新奇,憑空想不到,并不是原理上有多難。。。

          于是,借助強大的搜素引擎,搜集資料,最后,再自己總結了一番,才有了本文。

          正文開始前,各位看官可以先暫停往下讀,嘗試下,在不借助任何網絡資料的情況下,是否能實現上面的需求?(就以 10分鐘為限吧)

          分析問題的關鍵

          借助stackoverflow上的回答。

          經典的繼承法有何問題

          先看看本文最開始時提到的經典繼承法實現,如下:

          1. /**

          2. * 經典的js組合寄生繼承

          3. */

          4. functionMyDate() {

          5. Date.apply(this, arguments);

          6. this.abc=1;

          7. }

          8. functioninherits(subClass, superClass) {

          9. functionInner() {}

          10. Inner.prototype=superClass.prototype;

          11. subClass.prototype=newInner();

          12. subClass.prototype.constructor=subClass;

          13. }

          14. inherits(MyDate,Date);

          15. MyDate.prototype.getTest=function() {

          16. returnthis.getTime();

          17. };

          18. let date=newMyDate();

          19. console.log(date.getTest());

          20. 就是這段代碼?,這也是JavaScript高程(紅寶書)中推薦的一種,一直用,從未失手,結果現在馬失前蹄。。。

            我們再回顧下它的報錯:

            再打印它的原型看看:

            怎么看都沒問題,因為按照原型鏈回溯規則, Date的所有原型方法都可以通過 MyDate對象的原型鏈往上回溯到。再仔細看看,發現它的關鍵并不是找不到方法,而是 thisisnotaDateobject.

            嗯哼,也就是說,關鍵是:由于調用的對象不是Date的實例,所以不允許調用,就算是自己通過原型繼承的也不行。

            為什么無法被繼承?

            首先,看看 MDN上的解釋,上面有提到,JavaScript的日期對象只能通過 JavaScriptDate作為構造函數來實例化。

            然后再看看stackoverflow上的回答:

            有提到, v8引擎底層代碼中有限制,如果調用對象的 [[Class]]不是 Date,則拋出錯誤。

            總的來說,結合這兩點,可以得出一個結論:要調用Date上方法的實例對象必須通過Date構造出來,否則不允許調用Date的方法。

            該如何實現繼承?

            雖然原因找到了,但是問題仍然要解決啊,真的就沒辦法了么?當然不是,事實上還是有不少實現的方法的。

            暴力混合法

            首先,說說說下暴力的混合法,它是下面這樣子的:

            說到底就是:內部生成一個 Date對象,然后此類暴露的方法中,把原有 Date中所有的方法都代理一遍,而且嚴格來說,這根本算不上繼承(都沒有原型鏈回溯)。

            ES5黑魔法

            然后,再看看ES5中如何實現?

            1. // 需要考慮polyfill情況

            2. Object.setPrototypeOf=Object.setPrototypeOf ||

            3. function(obj, proto) {

            4. obj.__proto__=proto;

            5. returnobj;

            6. };

            7. /**

            8. * 用了點技巧的繼承,實際上返回的是Date對象

            9. */

            10. functionMyDate() {

            11. // bind屬于Function.prototype,接收的參數是:object, param1, params2...

            12. vardateInst=new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();

            13. // 更改原型指向,否則無法調用MyDate原型上的方法

            14. // ES6方案中,這里就是[[prototype]]這個隱式原型對象,在沒有標準以前就是__proto__

            15. Object.setPrototypeOf(dateInst,MyDate.prototype);

            16. dateInst.abc=1;

            17. returndateInst;

            18. }

            19. // 原型重新指回Date,否則根本無法算是繼承

            20. Object.setPrototypeOf(MyDate.prototype,Date.prototype);

            21. MyDate.prototype.getTest=functiongetTest() {

            22. returnthis.getTime();

            23. };

            24. let date=newMyDate();

            25. // 正常輸出,譬如1515638988725

            26. console.log(date.getTest());

            27. 一眼看上去不知所措?沒關系,先看下圖來理解:(原型鏈關系一目了然)

              可以看到,用的是非常巧妙的一種做法:

              正常繼承的情況如下:

              • newMyDate()返回實例對象 date是由 MyDate構造的

              • 原型鏈回溯是: date(MyDate對象)-date.__proto__-MyDate.prototype-MyDate.prototype.__proto__-Date.prototype

              這種做法的繼承的情況如下:

              • newMyDate()返回實例對象 date是由 Date構造的

              • 原型鏈回溯是: date(Date對象)-date.__proto__-MyDate.prototype-MyDate.prototype.__proto__-Date.prototype

              可以看出,關鍵點在于:

              • 構造函數里返回了一個真正的 Date對象(由 Date構造,所以有這些內部類中的關鍵 [[Class]]標志),所以它有調用 Date原型上方法的權利

              • 構造函數里的Date對象的 [[ptototype]](對外,瀏覽器中可通過 __proto__訪問)指向 MyDate.prototype,然后 MyDate.prototype再指向 Date.prototype。

              所以最終的實例對象仍然能進行正常的原型鏈回溯,回溯到原本Date的所有原型方法。

              這樣通過一個巧妙的欺騙技巧,就實現了完美的Date繼承。不過補充一點, MDN上有提到盡量不要修改對象的 [[Prototype]],因為這樣可能會干涉到瀏覽器本身的優化。如果你關心性能,你就不應該在一個對象中修改它的 [[Prototype]]

              ES6大法

              當然,除了上述的ES5實現,ES6中也可以直接繼承(自帶支持繼承 Date),而且更為簡單:

              1. classMyDateextendsDate{

              2. constructor() {

              3. super();

              4. this.abc=1;

              5. }

              6. getTest() {

              7. returnthis.getTime();

              8. }

              9. }

              10. let date=newMyDate();

              11. // 正常輸出,譬如1515638988725

              12. console.log(date.getTest());

              對比下ES5中的實現,這個真的是簡單的不行,直接使用ES6的Class語法就行了。而且,也可以正常輸出。

              注意:這里的正常輸出環境是直接用ES6運行,不經過babel打包,打包后實質上是轉化成ES5的,所以效果完全不一樣。

              ES6寫法,然后Babel打包

              雖然說上述ES6大法是可以直接繼承Date的,但是,考慮到實質上大部分的生產環境是: ES6+Babel

              直接這樣用ES6 + Babel是會出問題的。

              不信的話,可以自行嘗試下,Babel打包成ES5后代碼大致是這樣的:

              然后當信心滿滿的開始用時,會發現:

              對,又出現了這個問題,也許這時候是這樣的⊙?⊙

              因為轉譯后的ES5源碼中,仍然是通過 MyDate來構造,而 MyDate的構造中又無法修改屬于 Date內部的 [[Class]]之類的私有標志,因此構造出的對象仍然不允許調用 Date方法(調用時,被引擎底層代碼識別為 [[Class]]標志不符合,不允許調用,拋出錯誤)。

              由此可見,ES6繼承的內部實現和Babel打包編譯出來的實現是有區別的。(雖說Babel的polyfill一般會按照定義的規范去實現的,但也不要過度迷信)。

              幾種繼承的細微區別

              雖然上述提到的三種方法都可以達到繼承 Date的目的-混合法嚴格說不能算繼承,只不過是另類實現。

              于是,將所有能打印的主要信息都打印出來,分析幾種繼承的區別,大致場景是這樣的:

              可以參考:( 請進入調試模式)https://dailc.github.io/fe-interview/demo/extends_date.html

              從上往下, 1,2,3,4四種繼承實現分別是:(排出了混合法)

              • ES6的Class大法

              • 經典組合寄生繼承法

              • 本文中的取巧做法,Date構造實例,然后更改 __proto__的那種

              • ES6的Class大法,Babel打包后的實現(無法正常調用的)

              1. ~~~~以下是MyDate們的prototype~~~~~~~~~

              2. Date{constructor: ?, getTest: ?}

              3. Date{constructor: ?, getTest: ?}

              4. Date{getTest: ?, constructor: ?}

              5. Date{constructor: ?, getTest: ?}

              6. ~~~~以下是new出的對象~~~~~~~~~

              7. SatJan13201821:58:55GMT+0800(CST)

              8. MyDate2{abc:1}

              9. SatJan13201821:58:55GMT+0800(CST)

              10. MyDate{abc:1}

              11. ~~~~以下是new出的對象的Object.prototype.toString.call~~~~~~~~~

              12. [objectDate]

              13. [objectObject]

              14. [objectDate]

              15. [objectObject]

              16. ~~~~以下是MyDate們的__proto__~~~~~~~~~

              17. ?Date() { [native code] }

              18. ? () { [native code] }

              19. ? () { [native code] }

              20. ?Date() { [native code] }

              21. ~~~~以下是new出的對象的__proto__~~~~~~~~~

              22. Date{constructor: ?, getTest: ?}

              23. Date{constructor: ?, getTest: ?}

              24. Date{getTest: ?, constructor: ?}

              25. Date{constructor: ?, getTest: ?}

              26. ~~~~以下是對象的__proto__與MyDate們的prototype比較~~~~~~~~~

              27. true

              28. true

              29. true

              30. true

              31. 看出,主要差別有幾點:

                1. MyDate們的proto指向不一樣

                2. Object.prototype.toString.call的輸出不一樣

                3. 對象本質不一樣,可以正常調用的 1,3都是 Date構造出的,而其它的則是 MyDate構造出的

                我們上文中得出的一個結論是:由于調用的對象不是由Date構造出的實例,所以不允許調用,就算是自己的原型鏈上有Date.prototype也不行

                但是這里有兩個變量:分別是底層構造實例的方法不一樣,以及對象的 Object.prototype.toString.call的輸出不一樣(另一個 MyDate.__proto__可以排除,因為原型鏈回溯肯定與它無關)。

                萬一它的判斷是根據 Object.prototype.toString.call來的呢?那這樣結論不就有誤差了?

                于是,根據ES6中的, Symbol.toStringTag,使用黑魔法,動態的修改下它,排除下干擾:

                1. // 分別可以給date2,date3設置

                2. Object.defineProperty(date2,Symbol.toStringTag, {

                3. get:function() {

                4. returnDate;

                5. }

                6. });

                然后在打印下看看,變成這樣了:

                1. [objectDate]

                2. [objectDate]

                3. [objectDate]

                4. [objectObject]

                可以看到,第二個的 MyDate2構造出的實例,雖然打印出來是 [objectDate],但是調用Date方法仍然是有錯誤。

                此時我們可以更加準確一點的確認:由于調用的對象不是由Date構造出的實例,所以不允許調用。

                而且我們可以看到,就算通過黑魔法修改 Object.prototype.toString.call,內部的 [[Class]]標識位也是無法修改的。(這塊知識點大概是Object.prototype.toString.call可以輸出內部的[[Class]],但無法改變它,由于不是重點,這里不贅述)。

                ES6繼承與ES5繼承的區別

                從上午中的分析可以看到一點:ES6的Class寫法繼承是沒問題的。但是換成ES5寫法就不行了。

                所以ES6的繼承大法和ES5肯定是有區別的,那么究竟是哪里不同呢?(主要是結合的本文繼承Date來說)

                區別:(以 SubClass, SuperClass, instance為例)

                ES5中繼承的實質是:(那種經典組合寄生繼承法)

                • 先由子類( SubClass)構造出實例對象this

                • 然后在子類的構造函數中,將父類( SuperClass)的屬性添加到 this上, SuperClass.apply(this,arguments)

                • 子類原型( SubClass.prototype)指向父類原型( SuperClass.prototype)

                • 所以 instance是子類( SubClass)構造出的(所以沒有父類的 [[Class]]關鍵標志)

                • 所以, instance有 SubClass和 SuperClass的所有實例屬性,以及可以通過原型鏈回溯,獲取 SubClass和 SuperClass原型上的方法

                ES6中繼承的實質是:

                • 先由父類( SuperClass)構造出實例對象this,這也是為什么必須先調用父類的 super()方法(子類沒有自己的this對象,需先由父類構造)

                • 然后在子類的構造函數中,修改this(進行加工),譬如讓它指向子類原型( SubClass.prototype),這一步很關鍵,否則無法找到子類原型(注,子類構造中加工這一步的實際做法是推測出的,從最終效果來推測)

                • 然后同樣,子類原型( SubClass.prototype)指向父類原型( SuperClass.prototype)

                • 所以 instance是父類( SuperClass)構造出的(所以有著父類的 [[Class]]關鍵標志)

                • 所以, instance有 SubClass和 SuperClass的所有實例屬性,以及可以通過原型鏈回溯,獲取 SubClass和 SuperClass原型上的方法

                以上?就列舉了些重要信息,其它的如靜態方法的繼承沒有贅述。(靜態方法繼承實質上只需要更改下 SubClass.__proto__到 SuperClass即可)

                可以看著這張圖快速理解:

                有沒有發現呢:ES6中的步驟和本文中取巧繼承Date的方法一模一樣,不同的是ES6是語言底層的做法,有它的底層優化之處,而本文中的直接修改_proto_容易影響性能。

                ES6中在super中構建this的好處?

                因為ES6中允許我們繼承內置的類,如Date,Array,Error等。如果this先被創建出來,在傳給Array等系統內置類的構造函數,這些內置類的構造函數是不認這個this的。所以需要現在super中構建出來,這樣才能有著super中關鍵的 [[Class]]標志,才能被允許調用。(否則就算繼承了,也無法調用這些內置類的方法)

                構造函數與實例對象

                看到這里,不知道是否對上午中頻繁提到的構造函數,實例對象有所混淆與困惑呢?這里稍微描述下。

                要弄懂這一點,需要先知道 new一個對象到底發生了什么?先形象點說:

                new MyClass()中,都做了些什么工作
                1. functionMyClass() {

                2. this.abc=1;

                3. }

                4. MyClass.prototype.print=function() {

                5. console.log('this.abc:'+this.abc);

                6. };

                7. let instance=newMyClass();

                譬如,上述就是一個標準的實例對象生成,都發生了什么呢?

                步驟簡述如下:(參考MDN,還有部分關于底層的描述略去-如[[Class]]標識位等)

                1. 構造函數內部,創建一個新的對象,它繼承自 MyClass.prototype, letinstance=Object.create(MyClass.prototype);

                2. 使用指定的參數調用構造函數 MyClass,并將 this綁定到新創建的對象, MyClass.call(instance);,執行后擁有所有實例屬性

                3. 如果構造函數返回了一個“對象”,那么這個對象會取代整個 new出來的結果。如果構造函數沒有返回對象,那么new出來的結果為步驟1創建的對象。 (一般情況下構造函數不返回任何值,不過用戶如果想覆蓋這個返回值,可以自己選擇返回一個普通對象來覆蓋。當然,返回數組也會覆蓋,因為數組也是對象。)

                結合上述的描述,大概可以還原成以下代碼(簡單還原,不考慮各種其它邏輯):

                1. let instance=Object.create(MyClass.prototype);

                2. let innerConstructReturn=MyClass.call(instance);

                3. let innerConstructReturnIsObj=typeofinnerConstructReturn==='object'||typeofinnerConstructReturn==='function';

                4. returninnerConstructReturnIsObj ? innerConstructReturn : instance;

                注意?:普通的函數構建,可以簡單的認為就是上述步驟。實際上對于一些內置類(如Date等),并沒有這么簡單,還有一些自己的隱藏邏輯,譬如 [[Class]]標識位等一些重要私有屬性。譬如可以在MDN上看到,以常規函數調用Date(即不加 new 操作符)將會返回一個字符串,而不是一個日期對象,如果這樣模擬的話會無效。

                覺得看起來比較繁瑣?可以看下圖梳理:

                那現在再回頭看看。

                什么是構造函數?

                如上述中的 MyClass就是一個構造函數,在內部它構造出了 instance對象。

                什么是實例對象?

                instance就是一個實例對象,它是通過 new出來的?

                實例與構造的關系

                有時候淺顯點,可以認為構造函數是xxx就是xxx的實例。即:

                1. let instance=newMyClass();

                此時我們就可以認為 instance是 MyClass的實例,因為它的構造函數就是它。

                實例就一定是由對應的構造函數構造出的么?

                不一定,我們那ES5黑魔法來做示例。

                1. functionMyDate() {

                2. // bind屬于Function.prototype,接收的參數是:object, param1, params2...

                3. vardateInst=new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();

                4. // 更改原型指向,否則無法調用MyDate原型上的方法

                5. // ES6方案中,這里就是[[prototype]]這個隱式原型對象,在沒有標準以前就是__proto__

                6. Object.setPrototypeOf(dateInst,MyDate.prototype);

                7. dateInst.abc=1;

                8. returndateInst;

                9. }

                10. 我們可以看到 instance的最終指向的原型是 MyDate.prototype,而 MyDate.prototype的構造函數是 MyDate,因此可以認為 instance是 MyDate的實例。

                  但是,實際上, instance卻是由 Date構造的,我們可以繼續用 ES6中的 new.target來驗證。

                  注意?:關于 new.target, MDN中的定義是:new.target返回一個指向構造方法或函數的引用。

                  嗯哼,也就是說,返回的是構造函數。

                  我們可以在相應的構造中測試打印:

                  1. classMyDateextendsDate{

                  2. constructor() {

                  3. super();

                  4. this.abc=1;

                  5. console.log('~~~new.target.name:MyDate~~~~');

                  6. console.log(new.target.name);

                  7. }

                  8. }

                  9. // new操作時的打印結果是:

                  10. // ~~~new.target.name:MyDate~~~~

                  11. // MyDate

                  然后,可以在上面的示例中看到,就算是ES6的Class繼承, MyDate構造中打印 new.target也顯示 MyDate,但實際上它是由 Date來構造(有著 Date關鍵的 [[Class]]標志,因為如果不是Date構造(如沒有標志)是無法調用Date的方法的)。

                  這也算是一次小小的勘誤吧。

                  所以,實際上用 new.target是無法判斷實例對象到底是由哪一個構造構造的(這里指的是判斷底層真正的 [[Class]]標志來源的構造)。

                  再回到結論:實例對象不一定就是由它的原型上的構造函數構造的,有可能構造函數內部有著寄生等邏輯,偷偷的用另一個函數來構造了下,當然,簡單情況下,我們直接說實例對象由對應構造函數構造也沒錯(不過,在涉及到這種Date之類的分析時,我們還是得明白)。

                  [[Class]]與Internal slot

                  這一部分為補充內容。

                  前文中一直提到一個概念:Date內部的 [[Class]]標識。

                  其實,嚴格來說,不能這樣泛而稱之(前文中只是用這個概念是為了降低復雜度,便于理解),它可以分為以下兩部分:

                  在ES5中,每種內置對象都定義了 [[Class]] 內部屬性的值,[[Class]] 內部屬性的值用于內部區分對象的種類

                  • Object.prototype.toString訪問的就是這個[[Class]]

                  • 規范中除了通過 Object.prototype.toString,沒有提供任何手段使程序訪問此值。

                  • 而且Object.prototype.toString輸出無法被修改

                  而在ES5中,之前的 [[Class]] 不再使用,取而代之的是一系列的 internalslot

                  • Internal slot 對應于與對象相關聯并由各種ECMAScript規范算法使用的內部狀態,它們沒有對象屬性,也不能被繼承

                  • 根據具體的 Internal slot 規范,這種狀態可以由任何ECMAScript語言類型或特定ECMAScript規范類型值的值組成

                  • 通過 Object.prototype.toString,仍然可以輸出Internal slot值

                  • 簡單點理解(簡化理解),Object.prototype.toString的流程是:如果是基本數據類型(除去Object以外的幾大類型),則返回原本的slot,如果是Object類型(包括內置對象以及自己寫的對象),則調用 Symbol.toStringTag。 Symbol.toStringTag方法的默認實現就是返回對象的Internal slot,這個方法可以被重寫

                  這兩點是有所差異的,需要區分(不過簡單點可以統一理解為內置對象內部都有一個特殊標識,用來區分對應類型-不符合類型就不給調用)。

                  JS內置對象是這些:

                  1. Arguments,Array,Boolean,Date,Error,Function,JSON,Math,Number,Object,RegExp,String

                  ES6新增的一些,這里未提到:(如Promise對象可以輸出 [objectPromise]),而前文中提到的:

                  1. Object.defineProperty(date,Symbol.toStringTag, {

                  2. get:function() {

                  3. returnDate;

                  4. }

                  5. });

                  它的作用是重寫Symbol.toStringTag,截取date(雖然是內置對象,但是仍然屬于Object)的 Object.prototype.toString的輸出,讓這個對象輸出自己修改后的 [objectDate]。

                  但是,僅僅是做到輸出的時候變成了Date,實際上內部的 internalslot值并沒有被改變,因此仍然不被認為是Date。

                  如何快速判斷是否繼承?

                  其實,在判斷繼承時,沒有那么多的技巧,就只有關鍵的一點: [[prototype]]( __ptoto__)的指向關系。

                  譬如:

                  1. console.log(instanceinstanceofSubClass);

                  2. console.log(instanceinstanceofSuperClass);

                  實質上就是:

                  • SubClass.prototype是否出現在 instance的原型鏈上

                  • SuperClass.prototype是否出現在 instance的原型鏈上

                  然后,對照本文中列舉的一些圖,一目了然就可以看清關系。有時候,完全沒有必要弄的太復雜。

                  覺得本文對你有幫助?請分享給更多人

                  前端開發者丨JavaScript


          主站蜘蛛池模板: 亚洲AV香蕉一区区二区三区| 亚洲av区一区二区三| 国产成人精品无人区一区| 97久久精品一区二区三区| 精品一区二区三区无码视频 | 亚洲片一区二区三区| 波多野结衣一区视频在线| 中文字幕一区二区人妻性色 | 99精品国产高清一区二区| 久久精品无码一区二区app| 波多野结衣一区在线观看| 亚洲精品国产suv一区88| 91久久精品无码一区二区毛片| 内射女校花一区二区三区| 亚洲午夜精品第一区二区8050| 国产日韩视频一区| 精品国产一区二区二三区在线观看| 久久精品国产AV一区二区三区| 麻豆天美国产一区在线播放| 精品国产亚洲一区二区三区| 亚洲国产综合无码一区| 手机看片福利一区二区三区| jazzjazz国产精品一区二区| 濑亚美莉在线视频一区| 国产伦精品一区二区三区免费下载 | 久久无码一区二区三区少妇 | 亚洲一区二区三区免费在线观看 | 亚洲国产一区国产亚洲| 无码国产精品一区二区免费vr| 奇米精品一区二区三区在线观看| 夜夜嗨AV一区二区三区| 亚洲一区二区三区夜色| 国产精品高清视亚洲一区二区| 国产成人精品第一区二区| 日韩精品免费一区二区三区| 波多野结衣一区二区三区高清在线| 亚洲AⅤ无码一区二区三区在线| 亚洲一区二区三区香蕉| 搡老熟女老女人一区二区| 无码国产精品久久一区免费| 国产精品免费综合一区视频|